summaryrefslogtreecommitdiffstats
path: root/client/go/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'client/go/cmd')
-rw-r--r--client/go/cmd/api_key.go16
-rw-r--r--client/go/cmd/api_key_test.go2
-rw-r--r--client/go/cmd/cert.go195
-rw-r--r--client/go/cmd/cert_test.go42
-rw-r--r--client/go/cmd/clone_list_test.go3
-rw-r--r--client/go/cmd/clone_test.go3
-rw-r--r--client/go/cmd/command_tester_test.go (renamed from client/go/cmd/command_tester.go)57
-rw-r--r--client/go/cmd/config.go12
-rw-r--r--client/go/cmd/config_test.go13
-rw-r--r--client/go/cmd/curl.go13
-rw-r--r--client/go/cmd/curl_test.go3
-rw-r--r--client/go/cmd/deploy.go16
-rw-r--r--client/go/cmd/deploy_test.go37
-rw-r--r--client/go/cmd/document_test.go27
-rw-r--r--client/go/cmd/helpers.go199
-rw-r--r--client/go/cmd/log_test.go14
-rw-r--r--client/go/cmd/login.go10
-rw-r--r--client/go/cmd/logout.go10
-rw-r--r--client/go/cmd/prod.go23
-rw-r--r--client/go/cmd/prod_test.go20
-rw-r--r--client/go/cmd/query_test.go13
-rw-r--r--client/go/cmd/root.go1
-rw-r--r--client/go/cmd/status_test.go17
-rw-r--r--client/go/cmd/test.go23
-rw-r--r--client/go/cmd/test_test.go38
-rw-r--r--client/go/cmd/testdata/tests/production-test/illegal-reference.json2
-rw-r--r--client/go/cmd/testdata/tests/production-test/illegal-uri.json14
-rw-r--r--client/go/cmd/version_test.go5
28 files changed, 530 insertions, 298 deletions
diff --git a/client/go/cmd/api_key.go b/client/go/cmd/api_key.go
index 5cc1dab8a35..ae3f5346f4c 100644
--- a/client/go/cmd/api_key.go
+++ b/client/go/cmd/api_key.go
@@ -79,11 +79,19 @@ func doApiKey(_ *cobra.Command, _ []string) error {
if err != nil {
return err
}
+ targetType, err := getTargetType()
+ if err != nil {
+ return err
+ }
+ system, err := getSystem(targetType)
+ if err != nil {
+ return err
+ }
apiKeyFile := cfg.APIKeyPath(app.Tenant)
if util.PathExists(apiKeyFile) && !overwriteKey {
err := fmt.Errorf("refusing to overwrite %s", apiKeyFile)
printErrHint(err, "Use -f to overwrite it")
- printPublicKey(apiKeyFile, app.Tenant)
+ printPublicKey(system, apiKeyFile, app.Tenant)
return ErrCLI{error: err, quiet: true}
}
apiKey, err := vespa.CreateAPIKey()
@@ -92,13 +100,13 @@ func doApiKey(_ *cobra.Command, _ []string) error {
}
if err := ioutil.WriteFile(apiKeyFile, apiKey, 0600); err == nil {
printSuccess("API private key written to ", apiKeyFile)
- return printPublicKey(apiKeyFile, app.Tenant)
+ return printPublicKey(system, apiKeyFile, app.Tenant)
} else {
return fmt.Errorf("failed to write: %s: %w", apiKeyFile, err)
}
}
-func printPublicKey(apiKeyFile, tenant string) error {
+func printPublicKey(system vespa.System, apiKeyFile, tenant string) error {
pemKeyData, err := ioutil.ReadFile(apiKeyFile)
if err != nil {
return fmt.Errorf("failed to read: %s: %w", apiKeyFile, err)
@@ -118,7 +126,7 @@ func printPublicKey(apiKeyFile, tenant string) error {
log.Printf("\nThis is your public key:\n%s", color.Green(pemPublicKey))
log.Printf("Its fingerprint is:\n%s\n", color.Cyan(fingerprint))
log.Print("\nTo use this key in Vespa Cloud click 'Add custom key' at")
- log.Printf(color.Cyan("%s/tenant/%s/keys").String(), getConsoleURL(), tenant)
+ log.Printf(color.Cyan("%s/tenant/%s/keys").String(), system.ConsoleURL, tenant)
log.Print("and paste the entire public key including the BEGIN and END lines.")
return nil
}
diff --git a/client/go/cmd/api_key_test.go b/client/go/cmd/api_key_test.go
index 935b8676c09..ba697b69d9f 100644
--- a/client/go/cmd/api_key_test.go
+++ b/client/go/cmd/api_key_test.go
@@ -23,6 +23,8 @@ func testAPIKey(t *testing.T, subcommand []string) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
keyFile := filepath.Join(homeDir, "t1.api-key.pem")
+ execute(command{args: []string{"config", "set", "target", "cloud"}, homeDir: homeDir}, t, nil)
+
args := append(subcommand, "-a", "t1.a1.i1")
out, _ := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, out, "Success: API private key written to "+keyFile+"\n")
diff --git a/client/go/cmd/cert.go b/client/go/cmd/cert.go
index 97147d1eec0..2a28589aabb 100644
--- a/client/go/cmd/cert.go
+++ b/client/go/cmd/cert.go
@@ -4,7 +4,9 @@
package cmd
import (
+ "errors"
"fmt"
+ "io"
"os"
"path/filepath"
@@ -13,14 +15,36 @@ import (
"github.com/vespa-engine/vespa/client/go/vespa"
)
-var overwriteCertificate bool
+var (
+ noApplicationPackage bool
+ overwriteCertificate bool
+)
-const longDoc = `Create a new private key and self-signed certificate for Vespa Cloud deployment.
+func init() {
+ certCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
+ certCmd.Flags().BoolVarP(&noApplicationPackage, "no-add", "N", false, "Do not add certificate to an application package")
+ certCmd.MarkPersistentFlagRequired(applicationFlag)
+
+ deprecatedCertCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
+ deprecatedCertCmd.MarkPersistentFlagRequired(applicationFlag)
+
+ certCmd.AddCommand(certAddCmd)
+ certAddCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate")
+ certAddCmd.MarkPersistentFlagRequired(applicationFlag)
+}
+
+var certCmd = &cobra.Command{
+ Use: "cert",
+ Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
+ Long: `Create a new private key and self-signed certificate for Vespa Cloud deployment.
The private key and certificate will be stored in the Vespa CLI home directory
(see 'vespa help config'). Other commands will then automatically load the
certificate as necessary.
+The certificate will be added to your application package specified as an
+argument to this command (default '.'), unless the '--no-add' is set.
+
It's possible to override the private key and certificate used through
environment variables. This can be useful in continuous integration systems.
@@ -38,24 +62,9 @@ environment variables. This can be useful in continuous integration systems.
Note that when overriding key pair through environment variables, that key pair
will always be used for all applications. It's not possible to specify an
-application-specific key.`
-
-func init() {
- certCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
- certCmd.MarkPersistentFlagRequired(applicationFlag)
- deprecatedCertCmd.Flags().BoolVarP(&overwriteCertificate, "force", "f", false, "Force overwrite of existing certificate and private key")
- deprecatedCertCmd.MarkPersistentFlagRequired(applicationFlag)
-}
-
-func certExample() string {
- return "$ vespa auth cert -a my-tenant.my-app.my-instance"
-}
-
-var certCmd = &cobra.Command{
- Use: "cert",
- Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
- Long: longDoc,
- Example: certExample(),
+application-specific key.`,
+ Example: `$ vespa auth cert -a my-tenant.my-app.my-instance
+$ vespa auth cert -a my-tenant.my-app.my-instance path/to/application/package`,
DisableAutoGenTag: true,
SilenceUsage: true,
Args: cobra.MaximumNArgs(1),
@@ -63,9 +72,35 @@ var certCmd = &cobra.Command{
}
var deprecatedCertCmd = &cobra.Command{
- Use: "cert",
- Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
- Long: longDoc,
+ Use: "cert",
+ Short: "Create a new private key and self-signed certificate for Vespa Cloud deployment",
+ Long: `Create a new private key and self-signed certificate for Vespa Cloud deployment.
+
+The private key and certificate will be stored in the Vespa CLI home directory
+(see 'vespa help config'). Other commands will then automatically load the
+certificate as necessary.
+
+The certificate will be added to your application package specified as an
+argument to this command (default '.').
+
+It's possible to override the private key and certificate used through
+environment variables. This can be useful in continuous integration systems.
+
+* VESPA_CLI_DATA_PLANE_CERT and VESPA_CLI_DATA_PLANE_KEY containing the
+ certificate and private key directly:
+
+ export VESPA_CLI_DATA_PLANE_CERT="my cert"
+ export VESPA_CLI_DATA_PLANE_KEY="my private key"
+
+* VESPA_CLI_DATA_PLANE_CERT_FILE and VESPA_CLI_DATA_PLANE_KEY_FILE containing
+ paths to the certificate and private key:
+
+ export VESPA_CLI_DATA_PLANE_CERT_FILE=/path/to/cert
+ export VESPA_CLI_DATA_PLANE_KEY_FILE=/path/to/key
+
+Note that when overriding key pair through environment variables, that key pair
+will always be used for all applications. It's not possible to specify an
+application-specific key.`,
Example: "$ vespa cert -a my-tenant.my-app.my-instance",
DisableAutoGenTag: true,
SilenceUsage: true,
@@ -75,14 +110,35 @@ var deprecatedCertCmd = &cobra.Command{
RunE: doCert,
}
+var certAddCmd = &cobra.Command{
+ Use: "add",
+ Short: "Add certificate to application package",
+ Long: `Add an existing self-signed certificate for Vespa Cloud deployment to your application package.
+
+The certificate will be looked for in the Vespa CLI home directory (see 'vespa
+help config') by default.
+
+The location of the application package can be specified as an argument to this
+command (default '.').`,
+ Example: `$ vespa auth cert add -a my-tenant.my-app.my-instance
+$ vespa auth cert add -a my-tenant.my-app.my-instance path/to/application/package`,
+ DisableAutoGenTag: true,
+ SilenceUsage: true,
+ Args: cobra.MinimumNArgs(1),
+ RunE: doCertAdd,
+}
+
func doCert(_ *cobra.Command, args []string) error {
app, err := getApplication()
if err != nil {
return err
}
- pkg, err := vespa.FindApplicationPackage(applicationSource(args), false)
- if err != nil {
- return err
+ var pkg vespa.ApplicationPackage
+ if !noApplicationPackage {
+ pkg, err = vespa.FindApplicationPackage(applicationSource(args), false)
+ if err != nil {
+ return err
+ }
}
cfg, err := LoadConfig()
if err != nil {
@@ -99,8 +155,10 @@ func doCert(_ *cobra.Command, args []string) error {
if !overwriteCertificate {
hint := "Use -f flag to force overwriting"
- if pkg.HasCertificate() {
- return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), hint)
+ if !noApplicationPackage {
+ if pkg.HasCertificate() {
+ return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), hint)
+ }
}
if util.PathExists(privateKeyFile) {
return errHint(fmt.Errorf("private key %s already exists", color.Cyan(privateKeyFile)), hint)
@@ -109,21 +167,26 @@ func doCert(_ *cobra.Command, args []string) error {
return errHint(fmt.Errorf("certificate %s already exists", color.Cyan(certificateFile)), hint)
}
}
- if pkg.IsZip() {
- hint := "Try running 'mvn clean' before 'vespa auth cert', and then 'mvn package'"
- return errHint(fmt.Errorf("cannot add certificate to compressed application package %s", pkg.Path), hint)
+ if !noApplicationPackage {
+ if pkg.IsZip() {
+ hint := "Try running 'mvn clean' before 'vespa auth cert', and then 'mvn package'"
+ return errHint(fmt.Errorf("cannot add certificate to compressed application package %s", pkg.Path), hint)
+ }
}
keyPair, err := vespa.CreateKeyPair()
if err != nil {
return err
}
- pkgCertificateFile := filepath.Join(pkg.Path, "security", "clients.pem")
- if err := os.MkdirAll(filepath.Dir(pkgCertificateFile), 0755); err != nil {
- return fmt.Errorf("could not create security directory: %w", err)
- }
- if err := keyPair.WriteCertificateFile(pkgCertificateFile, overwriteCertificate); err != nil {
- return fmt.Errorf("could not write certificate to application package: %w", err)
+ var pkgCertificateFile string
+ if !noApplicationPackage {
+ pkgCertificateFile = filepath.Join(pkg.Path, "security", "clients.pem")
+ if err := os.MkdirAll(filepath.Dir(pkgCertificateFile), 0755); err != nil {
+ return fmt.Errorf("could not create security directory: %w", err)
+ }
+ if err := keyPair.WriteCertificateFile(pkgCertificateFile, overwriteCertificate); err != nil {
+ return fmt.Errorf("could not write certificate to application package: %w", err)
+ }
}
if err := keyPair.WriteCertificateFile(certificateFile, overwriteCertificate); err != nil {
return fmt.Errorf("could not write certificate: %w", err)
@@ -131,8 +194,64 @@ func doCert(_ *cobra.Command, args []string) error {
if err := keyPair.WritePrivateKeyFile(privateKeyFile, overwriteCertificate); err != nil {
return fmt.Errorf("could not write private key: %w", err)
}
- printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ if !noApplicationPackage {
+ printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ }
printSuccess("Certificate written to ", color.Cyan(certificateFile))
printSuccess("Private key written to ", color.Cyan(privateKeyFile))
return nil
}
+
+func doCertAdd(_ *cobra.Command, args []string) error {
+ app, err := getApplication()
+ if err != nil {
+ return err
+ }
+ pkg, err := vespa.FindApplicationPackage(applicationSource(args), false)
+ if err != nil {
+ return err
+ }
+ cfg, err := LoadConfig()
+ if err != nil {
+ return err
+ }
+ certificateFile, err := cfg.CertificatePath(app)
+ if err != nil {
+ return err
+ }
+
+ if pkg.IsZip() {
+ hint := "Try running 'mvn clean' before 'vespa auth cert add', and then 'mvn package'"
+ return errHint(fmt.Errorf("unable to add certificate to compressed application package: %s", pkg.Path), hint)
+ }
+
+ pkgCertificateFile := filepath.Join(pkg.Path, "security", "clients.pem")
+ if err := os.MkdirAll(filepath.Dir(pkgCertificateFile), 0755); err != nil {
+ return fmt.Errorf("could not create security directory: %w", err)
+ }
+ src, err := os.Open(certificateFile)
+ if errors.Is(err, os.ErrNotExist) {
+ return errHint(fmt.Errorf("there is not key pair generated for application '%s'", app), "Try running 'vespa auth cert' to generate it")
+ } else if err != nil {
+ return fmt.Errorf("could not open certificate file: %w", err)
+ }
+ defer src.Close()
+ flags := os.O_CREATE | os.O_RDWR
+ if overwriteCertificate {
+ flags |= os.O_TRUNC
+ } else {
+ flags |= os.O_EXCL
+ }
+ dst, err := os.OpenFile(pkgCertificateFile, flags, 0755)
+ if errors.Is(err, os.ErrExist) {
+ return errHint(fmt.Errorf("application package %s already contains a certificate", pkg.Path), "Use -f flag to force overwriting")
+ } else if err != nil {
+ return fmt.Errorf("could not open application certificate file for writing: %w", err)
+ }
+ if _, err := io.Copy(dst, src); err != nil {
+ return fmt.Errorf("could not copy certificate file to application: %w", err)
+ }
+
+ printSuccess("Certificate written to ", color.Cyan(pkgCertificateFile))
+ return nil
+}
diff --git a/client/go/cmd/cert_test.go b/client/go/cmd/cert_test.go
index 4a115f54c65..51e99938d2b 100644
--- a/client/go/cmd/cert_test.go
+++ b/client/go/cmd/cert_test.go
@@ -9,6 +9,8 @@ import (
"path/filepath"
"testing"
+ "github.com/stretchr/testify/require"
+
"github.com/stretchr/testify/assert"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -39,7 +41,7 @@ func testCert(t *testing.T, subcommand []string) {
assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Certificate written to %s\nSuccess: Private key written to %s\n", pkgCertificate, certificate, privateKey), out)
args = append(subcommand, "-a", "t1.a1.i1", pkgDir)
- _, outErr := execute(command{args: []string{"cert", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ _, outErr := execute(command{args: args, homeDir: homeDir}, t, nil)
assert.Contains(t, outErr, fmt.Sprintf("Error: application package %s already contains a certificate", appDir))
}
@@ -74,6 +76,44 @@ func testCertCompressedPackage(t *testing.T, subcommand []string) {
assert.Contains(t, out, "Success: Private key written to")
}
+func TestCertAdd(t *testing.T) {
+ homeDir := filepath.Join(t.TempDir(), ".vespa")
+ execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+
+ pkgDir := mockApplicationPackage(t, false)
+ out, _ := execute(command{args: []string{"auth", "cert", "add", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ appDir := filepath.Join(pkgDir, "src", "main", "application")
+ pkgCertificate := filepath.Join(appDir, "security", "clients.pem")
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\n", pkgCertificate), out)
+
+ out, outErr := execute(command{args: []string{"auth", "cert", "add", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, "", out)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: application package %s already contains a certificate", appDir))
+ out, _ = execute(command{args: []string{"auth", "cert", "add", "-f", "-a", "t1.a1.i1", pkgDir}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\n", pkgCertificate), out)
+}
+
+func TestCertNoAdd(t *testing.T) {
+ homeDir := filepath.Join(t.TempDir(), ".vespa")
+ out, _ := execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+
+ app, err := vespa.ApplicationFromString("t1.a1.i1")
+ assert.Nil(t, err)
+
+ certificate := filepath.Join(homeDir, app.String(), "data-plane-public-cert.pem")
+ privateKey := filepath.Join(homeDir, app.String(), "data-plane-private-key.pem")
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Private key written to %s\n", certificate, privateKey), out)
+
+ _, outErr := execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: private key %s already exists", privateKey))
+ require.Nil(t, os.Remove(privateKey))
+ _, outErr = execute(command{args: []string{"auth", "cert", "-N", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Contains(t, outErr, fmt.Sprintf("Error: certificate %s already exists", certificate))
+
+ out, _ = execute(command{args: []string{"auth", "cert", "-N", "-f", "-a", "t1.a1.i1"}, homeDir: homeDir}, t, nil)
+ assert.Equal(t, fmt.Sprintf("Success: Certificate written to %s\nSuccess: Private key written to %s\n", certificate, privateKey), out)
+}
+
func mockApplicationPackage(t *testing.T, java bool) string {
dir := t.TempDir()
appDir := filepath.Join(dir, "src", "main", "application")
diff --git a/client/go/cmd/clone_list_test.go b/client/go/cmd/clone_list_test.go
index 1138e5de064..2e4fc4004bd 100644
--- a/client/go/cmd/clone_list_test.go
+++ b/client/go/cmd/clone_list_test.go
@@ -7,11 +7,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
)
func TestListSampleApps(t *testing.T) {
- c := &mockHttpClient{}
+ c := &mock.HTTPClient{}
c.NextResponse(200, readTestData(t, "sample-apps-contents.json"))
c.NextResponse(200, readTestData(t, "sample-apps-news.json"))
c.NextResponse(200, readTestData(t, "sample-apps-operations.json"))
diff --git a/client/go/cmd/clone_test.go b/client/go/cmd/clone_test.go
index 18354ece0e1..332758a127a 100644
--- a/client/go/cmd/clone_test.go
+++ b/client/go/cmd/clone_test.go
@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
)
@@ -21,7 +22,7 @@ func TestClone(t *testing.T) {
func assertCreated(sampleAppName string, app string, t *testing.T) {
appCached := app + "-cache"
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
testdata, err := ioutil.ReadFile(filepath.Join("testdata", "sample-apps-master.zip"))
require.Nil(t, err)
httpClient.NextResponseBytes(200, testdata)
diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester_test.go
index a12b6a2ff69..d71cdde0e8f 100644
--- a/client/go/cmd/command_tester.go
+++ b/client/go/cmd/command_tester_test.go
@@ -6,19 +6,15 @@ package cmd
import (
"bytes"
- "crypto/tls"
"io"
- "io/ioutil"
- "net/http"
"os"
"path/filepath"
- "strconv"
"testing"
- "time"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
)
@@ -66,7 +62,7 @@ func resetEnv(env map[string]string, original map[string]string) {
}
}
-func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string) {
+func execute(cmd command, t *testing.T, client *mock.HTTPClient) (string, string) {
if client != nil {
util.ActiveHttpClient = client
}
@@ -99,6 +95,8 @@ func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string)
queryCmd.Flags().VisitAll(resetFlag)
documentCmd.Flags().VisitAll(resetFlag)
logCmd.Flags().VisitAll(resetFlag)
+ certCmd.Flags().VisitAll(resetFlag)
+ certAddCmd.Flags().VisitAll(resetFlag)
// Capture stdout and execute command
var capturedOut bytes.Buffer
@@ -120,52 +118,7 @@ func execute(cmd command, t *testing.T, client *mockHttpClient) (string, string)
return capturedOut.String(), capturedErr.String()
}
-func executeCommand(t *testing.T, client *mockHttpClient, args []string, moreArgs []string) string {
+func executeCommand(t *testing.T, client *mock.HTTPClient, args []string, moreArgs []string) string {
out, _ := execute(command{args: args, moreArgs: moreArgs}, t, client)
return out
}
-
-type mockHttpClient struct {
- // The responses to return for future requests. Once a response is consumed, it's removed from this array
- nextResponses []mockResponse
-
- // A recording of the last HTTP request made through this
- lastRequest *http.Request
-
- // All requests made through this
- requests []*http.Request
-}
-
-type mockResponse struct {
- status int
- body []byte
-}
-
-func (c *mockHttpClient) NextStatus(status int) { c.NextResponseBytes(status, nil) }
-
-func (c *mockHttpClient) NextResponse(status int, body string) {
- c.NextResponseBytes(status, []byte(body))
-}
-
-func (c *mockHttpClient) NextResponseBytes(status int, body []byte) {
- c.nextResponses = append(c.nextResponses, mockResponse{status: status, body: body})
-}
-
-func (c *mockHttpClient) Do(request *http.Request, timeout time.Duration) (*http.Response, error) {
- response := mockResponse{status: 200}
- if len(c.nextResponses) > 0 {
- response = c.nextResponses[0]
- c.nextResponses = c.nextResponses[1:]
- }
- c.lastRequest = request
- c.requests = append(c.requests, request)
- return &http.Response{
- Status: "Status " + strconv.Itoa(response.status),
- StatusCode: response.status,
- Body: ioutil.NopCloser(bytes.NewBuffer(response.body)),
- Header: make(http.Header),
- },
- nil
-}
-
-func (c *mockHttpClient) UseCertificate(certificates []tls.Certificate) {}
diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go
index 9d42f0683fd..0997be2c899 100644
--- a/client/go/cmd/config.go
+++ b/client/go/cmd/config.go
@@ -197,7 +197,7 @@ func (c *Config) ReadAPIKey(tenantName string) ([]byte, error) {
}
// UseAPIKey checks if api key should be used be checking if api-key or api-key-file has been set.
-func (c *Config) UseAPIKey(tenantName string) bool {
+func (c *Config) UseAPIKey(system vespa.System, tenantName string) bool {
if _, err := c.Get(apiKeyFlag); err == nil {
return true
}
@@ -207,15 +207,11 @@ func (c *Config) UseAPIKey(tenantName string) bool {
// If no Auth0 token is created, fall back to tenant api key, but warn that this functionality is deprecated
// TODO: Remove this when users have had time to migrate over to Auth0 device flow authentication
- a, err := auth0.GetAuth0(c.AuthConfigPath(), getSystemName(), getApiURL())
+ a, err := auth0.GetAuth0(c.AuthConfigPath(), system.Name, system.URL)
if err != nil || !a.HasSystem() {
fmt.Fprintln(stderr, "Defaulting to tenant API key is deprecated. Use Auth0 device flow: 'vespa auth login' instead")
- if !util.PathExists(c.APIKeyPath(tenantName)) {
- return false
- }
- return true
+ return util.PathExists(c.APIKeyPath(tenantName))
}
-
return false
}
@@ -283,7 +279,7 @@ func (c *Config) Set(option, value string) error {
switch option {
case targetFlag:
switch value {
- case "local", "cloud":
+ case vespa.TargetLocal, vespa.TargetCloud, vespa.TargetHosted:
viper.Set(option, value)
return nil
}
diff --git a/client/go/cmd/config_test.go b/client/go/cmd/config_test.go
index 16378b5f8ba..2f0ccbb29e1 100644
--- a/client/go/cmd/config_test.go
+++ b/client/go/cmd/config_test.go
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/vespa"
)
func TestConfig(t *testing.T) {
@@ -16,6 +17,8 @@ func TestConfig(t *testing.T) {
assertConfigCommandErr(t, "Error: invalid option or value: \"foo\": \"bar\"\n", homeDir, "config", "set", "foo", "bar")
assertConfigCommand(t, "foo = <unset>\n", homeDir, "config", "get", "foo")
assertConfigCommand(t, "target = local\n", homeDir, "config", "get", "target")
+ assertConfigCommand(t, "", homeDir, "config", "set", "target", "hosted")
+ assertConfigCommand(t, "target = hosted\n", homeDir, "config", "get", "target")
assertConfigCommand(t, "", homeDir, "config", "set", "target", "cloud")
assertConfigCommand(t, "target = cloud\n", homeDir, "config", "get", "target")
assertConfigCommand(t, "", homeDir, "config", "set", "target", "http://127.0.0.1:8080")
@@ -66,15 +69,15 @@ func TestUseAPIKey(t *testing.T) {
homeDir := t.TempDir()
c := Config{Home: homeDir}
- assert.False(t, c.UseAPIKey("t1"))
+ assert.False(t, c.UseAPIKey(vespa.PublicSystem, "t1"))
c.Set(apiKeyFileFlag, "/tmp/foo")
- assert.True(t, c.UseAPIKey("t1"))
+ assert.True(t, c.UseAPIKey(vespa.PublicSystem, "t1"))
c.Set(apiKeyFileFlag, "")
withEnv("VESPA_CLI_API_KEY", "...", func() {
require.Nil(t, c.load())
- assert.True(t, c.UseAPIKey("t1"))
+ assert.True(t, c.UseAPIKey(vespa.PublicSystem, "t1"))
})
// Test deprecated functionality
@@ -97,8 +100,8 @@ func TestUseAPIKey(t *testing.T) {
withEnv("VESPA_CLI_CLOUD_SYSTEM", "public", func() {
_, err := os.Create(filepath.Join(homeDir, "t2.api-key.pem"))
require.Nil(t, err)
- assert.True(t, c.UseAPIKey("t2"))
+ assert.True(t, c.UseAPIKey(vespa.PublicSystem, "t2"))
require.Nil(t, ioutil.WriteFile(filepath.Join(homeDir, "auth.json"), []byte(authContent), 0600))
- assert.False(t, c.UseAPIKey("t2"))
+ assert.False(t, c.UseAPIKey(vespa.PublicSystem, "t2"))
})
}
diff --git a/client/go/cmd/curl.go b/client/go/cmd/curl.go
index b66780780ed..1ede2cccae3 100644
--- a/client/go/cmd/curl.go
+++ b/client/go/cmd/curl.go
@@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/vespa-engine/vespa/client/go/auth0"
"github.com/vespa-engine/vespa/client/go/curl"
+ "github.com/vespa-engine/vespa/client/go/vespa"
)
var curlDryRun bool
@@ -61,8 +62,8 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain
if err != nil {
return err
}
- if t.Type() == "cloud" {
- if err := addCloudAuth0Authentication(cfg, c); err != nil {
+ if t.Type() == vespa.TargetCloud {
+ if err := addCloudAuth0Authentication(t.Deployment().System, cfg, c); err != nil {
return err
}
}
@@ -92,17 +93,17 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain
},
}
-func addCloudAuth0Authentication(cfg *Config, c *curl.Command) error {
- a, err := auth0.GetAuth0(cfg.AuthConfigPath(), getSystemName(), getApiURL())
+func addCloudAuth0Authentication(system vespa.System, cfg *Config, c *curl.Command) error {
+ a, err := auth0.GetAuth0(cfg.AuthConfigPath(), system.Name, system.URL)
if err != nil {
return err
}
- system, err := a.PrepareSystem(auth0.ContextWithCancel())
+ authSystem, err := a.PrepareSystem(auth0.ContextWithCancel())
if err != nil {
return err
}
- c.Header("Authorization", "Bearer "+system.AccessToken)
+ c.Header("Authorization", "Bearer "+authSystem.AccessToken)
return nil
}
diff --git a/client/go/cmd/curl_test.go b/client/go/cmd/curl_test.go
index 5709096fe37..253943f2b04 100644
--- a/client/go/cmd/curl_test.go
+++ b/client/go/cmd/curl_test.go
@@ -7,11 +7,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
)
func TestCurl(t *testing.T) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
out, _ := execute(command{homeDir: homeDir, args: []string{"curl", "-n", "-a", "t1.a1.i1", "--", "-v", "--data-urlencode", "arg=with space", "/search"}}, t, httpClient)
expected := fmt.Sprintf("curl --key %s --cert %s -v --data-urlencode 'arg=with space' http://127.0.0.1:8080/search\n",
diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go
index 14b6e969df7..13f37fa3901 100644
--- a/client/go/cmd/deploy.go
+++ b/client/go/cmd/deploy.go
@@ -26,7 +26,7 @@ func init() {
rootCmd.AddCommand(deployCmd)
rootCmd.AddCommand(prepareCmd)
rootCmd.AddCommand(activateCmd)
- deployCmd.PersistentFlags().StringVarP(&zoneArg, zoneFlag, "z", "dev.aws-us-east-1c", "The zone to use for deployment")
+ deployCmd.PersistentFlags().StringVarP(&zoneArg, zoneFlag, "z", "", "The zone to use for deployment. This defaults to a dev zone")
deployCmd.PersistentFlags().StringVarP(&logLevelArg, logLevelFlag, "l", "error", `Log level for Vespa logs. Must be "error", "warning", "info" or "debug"`)
}
@@ -64,7 +64,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
if err != nil {
return err
}
- opts, err := getDeploymentOpts(cfg, pkg, target)
+ opts, err := getDeploymentOptions(cfg, pkg, target)
if err != nil {
return err
}
@@ -73,7 +73,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
return err
}
- fmt.Print("\n")
+ log.Println()
if opts.IsCloud() {
printSuccess("Triggered deployment of ", color.Cyan(pkg.Path), " with run ID ", color.Cyan(sessionOrRunID))
} else {
@@ -82,9 +82,9 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
if opts.IsCloud() {
log.Printf("\nUse %s for deployment status, or follow this deployment at", color.Cyan("vespa status"))
log.Print(color.Cyan(fmt.Sprintf("%s/tenant/%s/application/%s/dev/instance/%s/job/%s-%s/run/%d",
- getConsoleURL(),
- opts.Deployment.Application.Tenant, opts.Deployment.Application.Application, opts.Deployment.Application.Instance,
- opts.Deployment.Zone.Environment, opts.Deployment.Zone.Region,
+ opts.Target.Deployment().System.ConsoleURL,
+ opts.Target.Deployment().Application.Tenant, opts.Target.Deployment().Application.Application, opts.Target.Deployment().Application.Instance,
+ opts.Target.Deployment().Zone.Environment, opts.Target.Deployment().Zone.Region,
sessionOrRunID)))
}
return waitForQueryService(sessionOrRunID)
@@ -110,7 +110,7 @@ var prepareCmd = &cobra.Command{
if err != nil {
return err
}
- sessionID, err := vespa.Prepare(vespa.DeploymentOpts{
+ sessionID, err := vespa.Prepare(vespa.DeploymentOptions{
ApplicationPackage: pkg,
Target: target,
})
@@ -148,7 +148,7 @@ var activateCmd = &cobra.Command{
if err != nil {
return err
}
- err = vespa.Activate(sessionID, vespa.DeploymentOpts{
+ err = vespa.Activate(sessionID, vespa.DeploymentOptions{
ApplicationPackage: pkg,
Target: target,
})
diff --git a/client/go/cmd/deploy_test.go b/client/go/cmd/deploy_test.go
index a37a433397f..f5af3751eb8 100644
--- a/client/go/cmd/deploy_test.go
+++ b/client/go/cmd/deploy_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -32,9 +33,9 @@ func TestDeployZipWithURLTargetArgument(t *testing.T) {
applicationPackage := "testdata/applications/withTarget/target/application.zip"
arguments := []string{"deploy", "testdata/applications/withTarget/target/application.zip", "-t", "http://target:19071"}
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
assert.Equal(t,
- "Success: Deployed "+applicationPackage+"\n",
+ "\nSuccess: Deployed "+applicationPackage+"\n",
executeCommand(t, client, arguments, []string{}))
assertDeployRequestMade("http://target:19071", client, t)
}
@@ -60,7 +61,7 @@ func TestDeployApplicationDirectoryWithPomAndTarget(t *testing.T) {
}
func TestDeployApplicationDirectoryWithPomAndEmptyTarget(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
_, outErr := execute(command{args: []string{"deploy", "testdata/applications/withEmptyTarget"}}, t, client)
assert.Equal(t,
"Error: pom.xml exists but no target/application.zip. Run mvn package first\n",
@@ -104,15 +105,15 @@ func TestDeployError(t *testing.T) {
}
func assertDeploy(applicationPackage string, arguments []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
assert.Equal(t,
- "Success: Deployed "+applicationPackage+"\n",
+ "\nSuccess: Deployed "+applicationPackage+"\n",
executeCommand(t, client, arguments, []string{}))
assertDeployRequestMade("http://127.0.0.1:19071", client, t)
}
func assertPrepare(applicationPackage string, arguments []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(200, `{"session-id":"42"}`)
assert.Equal(t,
"Success: Prepared "+applicationPackage+" with session 42\n",
@@ -120,12 +121,12 @@ func assertPrepare(applicationPackage string, arguments []string, t *testing.T)
assertPackageUpload(0, "http://127.0.0.1:19071/application/v2/tenant/default/session", client, t)
sessionURL := "http://127.0.0.1:19071/application/v2/tenant/default/session/42/prepared"
- assert.Equal(t, sessionURL, client.requests[1].URL.String())
- assert.Equal(t, "PUT", client.requests[1].Method)
+ assert.Equal(t, sessionURL, client.Requests[1].URL.String())
+ assert.Equal(t, "PUT", client.Requests[1].Method)
}
func assertActivate(applicationPackage string, arguments []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
homeDir := t.TempDir()
cfg := Config{Home: filepath.Join(homeDir, ".vespa"), createDirs: true}
if err := cfg.WriteSessionID(vespa.DefaultApplication, 42); err != nil {
@@ -136,14 +137,14 @@ func assertActivate(applicationPackage string, arguments []string, t *testing.T)
"Success: Activated "+applicationPackage+" with session 42\n",
out)
url := "http://127.0.0.1:19071/application/v2/tenant/default/session/42/active"
- assert.Equal(t, url, client.lastRequest.URL.String())
- assert.Equal(t, "PUT", client.lastRequest.Method)
+ assert.Equal(t, url, client.LastRequest.URL.String())
+ assert.Equal(t, "PUT", client.LastRequest.Method)
}
-func assertPackageUpload(requestNumber int, url string, client *mockHttpClient, t *testing.T) {
- req := client.lastRequest
+func assertPackageUpload(requestNumber int, url string, client *mock.HTTPClient, t *testing.T) {
+ req := client.LastRequest
if requestNumber >= 0 {
- req = client.requests[requestNumber]
+ req = client.Requests[requestNumber]
}
assert.Equal(t, url, req.URL.String())
assert.Equal(t, "application/zip", req.Header.Get("Content-Type"))
@@ -155,12 +156,12 @@ func assertPackageUpload(requestNumber int, url string, client *mockHttpClient,
assert.Equal(t, "PK\x03\x04\x14\x00\b", string(buf))
}
-func assertDeployRequestMade(target string, client *mockHttpClient, t *testing.T) {
+func assertDeployRequestMade(target string, client *mock.HTTPClient, t *testing.T) {
assertPackageUpload(-1, target+"/application/v2/tenant/default/prepareandactivate", client, t)
}
func assertApplicationPackageError(t *testing.T, cmd string, status int, expectedMessage string, returnBody string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, returnBody)
_, outErr := execute(command{args: []string{cmd, "testdata/applications/withTarget/target/application.zip"}}, t, client)
assert.Equal(t,
@@ -169,10 +170,10 @@ func assertApplicationPackageError(t *testing.T, cmd string, status int, expecte
}
func assertDeployServerError(t *testing.T, status int, errorMessage string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, errorMessage)
_, outErr := execute(command{args: []string{"deploy", "testdata/applications/withTarget/target/application.zip"}}, t, client)
assert.Equal(t,
- "Error: error from deploy service 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",
outErr)
}
diff --git a/client/go/cmd/document_test.go b/client/go/cmd/document_test.go
index 2b596e9893b..1d650f77d08 100644
--- a/client/go/cmd/document_test.go
+++ b/client/go/cmd/document_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -66,7 +67,7 @@ func TestDocumentRemoveWithoutIdArg(t *testing.T) {
func TestDocumentSendMissingId(t *testing.T) {
arguments := []string{"document", "put", "testdata/A-Head-Full-of-Dreams-Without-Operation.json"}
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
_, outErr := execute(command{args: arguments}, t, client)
assert.Equal(t,
"Error: No document id given neither as argument or as a 'put' key in the json file\n",
@@ -75,7 +76,7 @@ func TestDocumentSendMissingId(t *testing.T) {
func TestDocumentSendWithDisagreeingOperations(t *testing.T) {
arguments := []string{"document", "update", "testdata/A-Head-Full-of-Dreams-Put.json"}
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
_, outErr := execute(command{args: arguments}, t, client)
assert.Equal(t,
"Error: Wanted document operation is update but the JSON file specifies put\n",
@@ -96,7 +97,7 @@ func TestDocumentGet(t *testing.T) {
}
func assertDocumentSend(arguments []string, expectedOperation string, expectedMethod string, expectedDocumentId string, expectedPayloadFile string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
documentURL, err := documentServiceURL(client)
if err != nil {
t.Fatal(err)
@@ -116,16 +117,16 @@ func assertDocumentSend(arguments []string, expectedOperation string, expectedMe
assert.Equal(t, expectedCurl, errOut)
}
assert.Equal(t, "Success: "+expectedOperation+" "+expectedDocumentId+"\n", out)
- assert.Equal(t, expectedURL, client.lastRequest.URL.String())
- assert.Equal(t, "application/json", client.lastRequest.Header.Get("Content-Type"))
- assert.Equal(t, expectedMethod, client.lastRequest.Method)
+ assert.Equal(t, expectedURL, client.LastRequest.URL.String())
+ assert.Equal(t, "application/json", client.LastRequest.Header.Get("Content-Type"))
+ assert.Equal(t, expectedMethod, client.LastRequest.Method)
expectedPayload, _ := ioutil.ReadFile(expectedPayloadFile)
- assert.Equal(t, string(expectedPayload), util.ReaderToString(client.lastRequest.Body))
+ assert.Equal(t, string(expectedPayload), util.ReaderToString(client.LastRequest.Body))
}
func assertDocumentGet(arguments []string, documentId string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
documentURL, err := documentServiceURL(client)
if err != nil {
t.Fatal(err)
@@ -140,12 +141,12 @@ func assertDocumentGet(arguments []string, documentId string, t *testing.T) {
`,
executeCommand(t, client, arguments, []string{}))
expectedPath, _ := vespa.IdToURLPath(documentId)
- assert.Equal(t, documentURL+"/document/v1/"+expectedPath, client.lastRequest.URL.String())
- assert.Equal(t, "GET", client.lastRequest.Method)
+ assert.Equal(t, documentURL+"/document/v1/"+expectedPath, client.LastRequest.URL.String())
+ assert.Equal(t, "GET", client.LastRequest.Method)
}
func assertDocumentError(t *testing.T, status int, errorMessage string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, errorMessage)
_, outErr := execute(command{args: []string{"document", "put",
"id:mynamespace:music::a-head-full-of-dreams",
@@ -156,7 +157,7 @@ func assertDocumentError(t *testing.T, status int, errorMessage string) {
}
func assertDocumentServerError(t *testing.T, status int, errorMessage string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, errorMessage)
_, outErr := execute(command{args: []string{"document", "put",
"id:mynamespace:music::a-head-full-of-dreams",
@@ -166,7 +167,7 @@ func assertDocumentServerError(t *testing.T, status int, errorMessage string) {
outErr)
}
-func documentServiceURL(client *mockHttpClient) (string, error) {
+func documentServiceURL(client *mock.HTTPClient) (string, error) {
service, err := getService("document", 0, "")
if err != nil {
return "", err
diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go
index 547126a8156..ab47a0e6d88 100644
--- a/client/go/cmd/helpers.go
+++ b/client/go/cmd/helpers.go
@@ -5,6 +5,8 @@
package cmd
import (
+ "crypto/tls"
+ "crypto/x509"
"encoding/json"
"fmt"
"log"
@@ -29,6 +31,40 @@ func printSuccess(msg ...interface{}) {
log.Print(color.Green("Success: "), fmt.Sprint(msg...))
}
+func athenzPath(filename string) (string, error) {
+ userHome, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(userHome, ".athenz", filename), nil
+}
+
+func athenzKeyPair() (tls.Certificate, error) {
+ certFile, err := athenzPath("cert")
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ keyFile, err := athenzPath("key")
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ kp, err := tls.LoadX509KeyPair(certFile, keyFile)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ cert, err := x509.ParseCertificate(kp.Certificate[0])
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ now := time.Now()
+ expiredAt := cert.NotAfter
+ if expiredAt.Before(now) {
+ delta := now.Sub(expiredAt).Truncate(time.Second)
+ return tls.Certificate{}, errHint(fmt.Errorf("certificate %s expired at %s (%s ago)", certFile, cert.NotAfter, delta), "Try renewing certificate with 'athenz-user-cert'")
+ }
+ return kp, nil
+}
+
func vespaCliHome() (string, error) {
home := os.Getenv("VESPA_CLI_HOME")
if home == "" {
@@ -59,16 +95,20 @@ func vespaCliCacheDir() (string, error) {
return cacheDir, nil
}
-func deploymentFromArgs() (vespa.Deployment, error) {
- zone, err := vespa.ZoneFromString(zoneArg)
- if err != nil {
- return vespa.Deployment{}, err
+func deploymentFromArgs(system vespa.System) (vespa.Deployment, error) {
+ zone := system.DefaultZone
+ var err error
+ if zoneArg != "" {
+ zone, err = vespa.ZoneFromString(zoneArg)
+ if err != nil {
+ return vespa.Deployment{}, err
+ }
}
app, err := getApplication()
if err != nil {
return vespa.Deployment{}, err
}
- return vespa.Deployment{Application: app, Zone: zone}, nil
+ return vespa.Deployment{System: system, Application: app, Zone: zone}, nil
}
func applicationSource(args []string) string {
@@ -124,28 +164,18 @@ func getService(service string, sessionOrRunID int64, cluster string) (*vespa.Se
func getEndpointsOverride() string { return os.Getenv("VESPA_CLI_ENDPOINTS") }
-func getSystem() string { return os.Getenv("VESPA_CLI_CLOUD_SYSTEM") }
-
-func getSystemName() string {
- if getSystem() == "publiccd" {
- return "publiccd"
+func getSystem(targetType string) (vespa.System, error) {
+ name := os.Getenv("VESPA_CLI_CLOUD_SYSTEM")
+ if name != "" {
+ return vespa.GetSystem(name)
}
- return "public"
-}
-
-func getConsoleURL() string {
- if getSystem() == "publiccd" {
- return "https://console-cd.vespa.oath.cloud"
- }
- return "https://console.vespa.oath.cloud"
-
-}
-
-func getApiURL() string {
- if getSystem() == "publiccd" {
- return "https://api.vespa-external-cd.aws.oath.cloud:4443"
+ switch targetType {
+ case vespa.TargetHosted:
+ return vespa.MainSystem, nil
+ case vespa.TargetCloud:
+ return vespa.PublicSystem, nil
}
- return "https://api.vespa-external.aws.oath.cloud:4443"
+ return vespa.System{}, fmt.Errorf("no default system found for %s target", targetType)
}
func getTarget() (vespa.Target, error) {
@@ -172,53 +202,80 @@ func createTarget() (vespa.Target, error) {
return vespa.CustomTarget(targetType), nil
}
switch targetType {
- case "local":
+ case vespa.TargetLocal:
return vespa.LocalTarget(), nil
- case "cloud":
- cfg, err := LoadConfig()
- if err != nil {
- return nil, err
- }
- deployment, err := deploymentFromArgs()
- if err != nil {
- return nil, err
- }
- endpoints, err := getEndpointsFromEnv()
- if err != nil {
- return nil, err
- }
+ case vespa.TargetCloud, vespa.TargetHosted:
+ return createCloudTarget(targetType)
+ }
+ return nil, errHint(fmt.Errorf("invalid target: %s", targetType), "Valid targets are 'local', 'cloud', 'hosted' or an URL")
+}
- var apiKey []byte = nil
- if cfg.UseAPIKey(deployment.Application.Tenant) {
+func createCloudTarget(targetType string) (vespa.Target, error) {
+ cfg, err := LoadConfig()
+ if err != nil {
+ return nil, err
+ }
+ system, err := getSystem(targetType)
+ if err != nil {
+ return nil, err
+ }
+ deployment, err := deploymentFromArgs(system)
+ if err != nil {
+ return nil, err
+ }
+ endpoints, err := getEndpointsFromEnv()
+ if err != nil {
+ return nil, err
+ }
+ var (
+ apiKey []byte
+ authConfigPath string
+ apiTLSOptions vespa.TLSOptions
+ deploymentTLSOptions vespa.TLSOptions
+ )
+ if targetType == vespa.TargetCloud {
+ if cfg.UseAPIKey(system, deployment.Application.Tenant) {
apiKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
if err != nil {
return nil, err
}
}
+ authConfigPath = cfg.AuthConfigPath()
kp, err := cfg.X509KeyPair(deployment.Application)
if err != nil {
return nil, errHint(err, "Deployment to cloud requires a certificate. Try 'vespa auth cert'")
}
-
- return vespa.CloudTarget(
- getApiURL(),
- deployment,
- apiKey,
- vespa.TLSOptions{
- KeyPair: kp.KeyPair,
- CertificateFile: kp.CertificateFile,
- PrivateKeyFile: kp.PrivateKeyFile,
- },
- vespa.LogOptions{
- Writer: stdout,
- Level: vespa.LogLevel(logLevelArg),
- },
- cfg.AuthConfigPath(),
- getSystemName(),
- endpoints,
- ), nil
- }
- return nil, errHint(fmt.Errorf("invalid target: %s", targetType), "Valid targets are 'local', 'cloud' or an URL")
+ deploymentTLSOptions = vespa.TLSOptions{
+ KeyPair: kp.KeyPair,
+ CertificateFile: kp.CertificateFile,
+ PrivateKeyFile: kp.PrivateKeyFile,
+ }
+ } else if targetType == vespa.TargetHosted {
+ kp, err := athenzKeyPair()
+ if err != nil {
+ return nil, err
+ }
+ apiTLSOptions = vespa.TLSOptions{KeyPair: kp}
+ deploymentTLSOptions = apiTLSOptions
+ } else {
+ return nil, fmt.Errorf("invalid cloud target: %s", targetType)
+ }
+ apiOptions := vespa.APIOptions{
+ System: system,
+ TLSOptions: apiTLSOptions,
+ APIKey: apiKey,
+ AuthConfigPath: authConfigPath,
+ }
+ deploymentOptions := vespa.CloudDeploymentOptions{
+ Deployment: deployment,
+ TLSOptions: deploymentTLSOptions,
+ ClusterURLs: endpoints,
+ }
+ logOptions := vespa.LogOptions{
+ Writer: stdout,
+ Level: vespa.LogLevel(logLevelArg),
+ }
+ return vespa.CloudTarget(apiOptions, deploymentOptions, logOptions)
}
func waitForService(service string, sessionOrRunID int64) error {
@@ -242,25 +299,15 @@ func waitForService(service string, sessionOrRunID int64) error {
return nil
}
-func getDeploymentOpts(cfg *Config, pkg vespa.ApplicationPackage, target vespa.Target) (vespa.DeploymentOpts, error) {
- opts := vespa.DeploymentOpts{ApplicationPackage: pkg, Target: target}
+func getDeploymentOptions(cfg *Config, pkg vespa.ApplicationPackage, target vespa.Target) (vespa.DeploymentOptions, error) {
+ opts := vespa.DeploymentOptions{ApplicationPackage: pkg, Target: target}
if opts.IsCloud() {
- deployment, err := deploymentFromArgs()
- if err != nil {
- return vespa.DeploymentOpts{}, err
- }
- if !opts.ApplicationPackage.HasCertificate() {
+ if target.Type() == vespa.TargetCloud && !opts.ApplicationPackage.HasCertificate() {
hint := "Try 'vespa auth cert'"
- return vespa.DeploymentOpts{}, errHint(fmt.Errorf("missing certificate in application package"), "Applications in Vespa Cloud require a certificate", hint)
- }
- if cfg.UseAPIKey(deployment.Application.Tenant) {
- opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant)
- if err != nil {
- return vespa.DeploymentOpts{}, err
- }
+ return vespa.DeploymentOptions{}, errHint(fmt.Errorf("missing certificate in application package"), "Applications in Vespa Cloud require a certificate", hint)
}
- opts.Deployment = deployment
}
+ opts.Timeout = time.Duration(waitSecsArg) * time.Second
return opts, nil
}
diff --git a/client/go/cmd/log_test.go b/client/go/cmd/log_test.go
index 1208be8f80c..3f6714b0d3c 100644
--- a/client/go/cmd/log_test.go
+++ b/client/go/cmd/log_test.go
@@ -7,20 +7,23 @@ import (
"github.com/stretchr/testify/assert"
"github.com/vespa-engine/vespa/client/go/build"
+ "github.com/vespa-engine/vespa/client/go/mock"
)
func TestLog(t *testing.T) {
homeDir := filepath.Join(t.TempDir(), ".vespa")
pkgDir := mockApplicationPackage(t, false)
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
httpClient.NextResponse(200, `1632738690.905535 host1a.dev.aws-us-east-1c 806/53 logserver-container Container.com.yahoo.container.jdisc.ConfiguredApplication info Switching to the latest deployed set of configurations and components. Application config generation: 52532`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "api-key-file", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "cert", pkgDir}}, t, httpClient)
- out, _ := execute(command{homeDir: homeDir, args: []string{"log", "--from", "2021-09-27T10:00:00Z", "--to", "2021-09-27T11:00:00Z"}}, t, httpClient)
+ out, outErr := execute(command{homeDir: homeDir, args: []string{"log", "--from", "2021-09-27T10:00:00Z", "--to", "2021-09-27T11:00:00Z"}}, t, httpClient)
+ assert.Equal(t, "", outErr)
expected := "[2021-09-27 10:31:30.905535] host1a.dev.aws-us-east-1c info logserver-container Container.com.yahoo.container.jdisc.ConfiguredApplication Switching to the latest deployed set of configurations and components. Application config generation: 52532\n"
assert.Equal(t, expected, out)
@@ -34,13 +37,14 @@ func TestLogOldClient(t *testing.T) {
build.Version = "7.0.0"
homeDir := filepath.Join(t.TempDir(), ".vespa")
pkgDir := mockApplicationPackage(t, false)
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
httpClient.NextResponse(200, `{"minVersion": "8.0.0"}`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "api-key-file", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "cert", pkgDir}}, t, httpClient)
out, errOut := execute(command{homeDir: homeDir, args: []string{"log"}}, t, httpClient)
assert.Equal(t, "", out)
expected := "Error: client version 7.0.0 is less than the minimum supported version: 8.0.0\nHint: This is not a fatal error, but this version may not work as expected\nHint: Try 'vespa version' to check for a new version\n"
diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go
index 8787f1f80f5..2ac480d05f5 100644
--- a/client/go/cmd/login.go
+++ b/client/go/cmd/login.go
@@ -18,7 +18,15 @@ var loginCmd = &cobra.Command{
if err != nil {
return err
}
- a, err := auth0.GetAuth0(cfg.AuthConfigPath(), getSystemName(), getApiURL())
+ targetType, err := getTargetType()
+ if err != nil {
+ return err
+ }
+ system, err := getSystem(targetType)
+ if err != nil {
+ return err
+ }
+ a, err := auth0.GetAuth0(cfg.AuthConfigPath(), system.Name, system.URL)
if err != nil {
return err
}
diff --git a/client/go/cmd/logout.go b/client/go/cmd/logout.go
index ddc1d36d5e1..b1f2477aba4 100644
--- a/client/go/cmd/logout.go
+++ b/client/go/cmd/logout.go
@@ -17,7 +17,15 @@ var logoutCmd = &cobra.Command{
if err != nil {
return err
}
- a, err := auth0.GetAuth0(cfg.AuthConfigPath(), getSystemName(), getApiURL())
+ targetType, err := getTargetType()
+ if err != nil {
+ return err
+ }
+ system, err := getSystem(targetType)
+ if err != nil {
+ return err
+ }
+ a, err := auth0.GetAuth0(cfg.AuthConfigPath(), system.Name, system.URL)
if err != nil {
return err
}
diff --git a/client/go/cmd/prod.go b/client/go/cmd/prod.go
index 8c40eb969bf..10fc9f92368 100644
--- a/client/go/cmd/prod.go
+++ b/client/go/cmd/prod.go
@@ -73,6 +73,10 @@ https://cloud.vespa.ai/en/reference/deployment`,
if err != nil {
return fmt.Errorf("a services.xml declaring your cluster(s) must exist: %w", err)
}
+ target, err := getTarget()
+ if err != nil {
+ return err
+ }
fmt.Fprint(stdout, "This will modify any existing ", color.Yellow("deployment.xml"), " and ", color.Yellow("services.xml"),
"!\nBefore modification a backup of the original file will be created.\n\n")
@@ -80,7 +84,7 @@ https://cloud.vespa.ai/en/reference/deployment`,
fmt.Fprint(stdout, "Abort the configuration at any time by pressing Ctrl-C. The\nfiles will remain untouched.\n\n")
fmt.Fprint(stdout, "See this guide for sizing a Vespa deployment:\n", color.Green("https://docs.vespa.ai/en/performance/sizing-search.html\n\n"))
r := bufio.NewReader(stdin)
- deploymentXML, err = updateRegions(r, deploymentXML)
+ deploymentXML, err = updateRegions(r, deploymentXML, target.Deployment().System)
if err != nil {
return err
}
@@ -127,8 +131,9 @@ $ vespa prod submit`,
if err != nil {
return err
}
- if target.Type() != "cloud" {
- return fmt.Errorf("%s target cannot deploy to Vespa Cloud", target.Type())
+ if target.Type() != vespa.TargetCloud {
+ // TODO: Add support for hosted
+ return fmt.Errorf("prod submit does not support %s target", target.Type())
}
appSource := applicationSource(args)
pkg, err := vespa.FindApplicationPackage(appSource, true)
@@ -156,7 +161,7 @@ $ vespa prod submit`,
fmt.Fprintln(stderr, color.Yellow("Warning:"), "We recommend doing this only from a CD job")
printErrHint(nil, "See https://cloud.vespa.ai/en/getting-to-production")
}
- opts, err := getDeploymentOpts(cfg, pkg, target)
+ opts, err := getDeploymentOptions(cfg, pkg, target)
if err != nil {
return err
}
@@ -165,7 +170,7 @@ $ vespa prod submit`,
} else {
printSuccess("Submitted ", color.Cyan(pkg.Path), " for deployment")
log.Printf("See %s for deployment progress\n", color.Cyan(fmt.Sprintf("%s/tenant/%s/application/%s/prod/deployment",
- getConsoleURL(), opts.Deployment.Application.Tenant, opts.Deployment.Application.Application)))
+ opts.Target.Deployment().System.ConsoleURL, opts.Target.Deployment().Application.Tenant, opts.Target.Deployment().Application.Application)))
}
return nil
},
@@ -202,8 +207,8 @@ func writeWithBackup(pkg vespa.ApplicationPackage, filename, contents string) er
return ioutil.WriteFile(dst, []byte(contents), 0644)
}
-func updateRegions(r *bufio.Reader, deploymentXML xml.Deployment) (xml.Deployment, error) {
- regions, err := promptRegions(r, deploymentXML)
+func updateRegions(r *bufio.Reader, deploymentXML xml.Deployment, system vespa.System) (xml.Deployment, error) {
+ regions, err := promptRegions(r, deploymentXML, system)
if err != nil {
return xml.Deployment{}, err
}
@@ -222,7 +227,7 @@ func updateRegions(r *bufio.Reader, deploymentXML xml.Deployment) (xml.Deploymen
return deploymentXML, nil
}
-func promptRegions(r *bufio.Reader, deploymentXML xml.Deployment) (string, error) {
+func promptRegions(r *bufio.Reader, deploymentXML xml.Deployment, system vespa.System) (string, error) {
fmt.Fprintln(stdout, color.Cyan("> Deployment regions"))
fmt.Fprintf(stdout, "Documentation: %s\n", color.Green("https://cloud.vespa.ai/en/reference/zones"))
fmt.Fprintf(stdout, "Example: %s\n\n", color.Yellow("aws-us-east-1c,aws-us-west-2a"))
@@ -238,7 +243,7 @@ func promptRegions(r *bufio.Reader, deploymentXML xml.Deployment) (string, error
validator := func(input string) error {
regions := strings.Split(input, ",")
for _, r := range regions {
- if !xml.IsProdRegion(r, getSystem()) {
+ if !xml.IsProdRegion(r, system) {
return fmt.Errorf("invalid region %s", r)
}
}
diff --git a/client/go/cmd/prod_test.go b/client/go/cmd/prod_test.go
index 8fa9ef401b5..90b67af8669 100644
--- a/client/go/cmd/prod_test.go
+++ b/client/go/cmd/prod_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
)
@@ -147,12 +148,12 @@ func TestProdSubmit(t *testing.T) {
pkgDir := filepath.Join(t.TempDir(), "app")
createApplication(t, pkgDir, false)
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
httpClient.NextResponse(200, `ok`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"api-key"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "cert", pkgDir}}, t, httpClient)
// Zipping requires relative paths, so much let command run from pkgDir, then reset cwd for subsequent tests.
if cwd, err := os.Getwd(); err != nil {
@@ -166,8 +167,8 @@ func TestProdSubmit(t *testing.T) {
if err := os.Setenv("CI", "true"); err != nil {
t.Fatal(err)
}
- out, err := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
- assert.Equal(t, "", err)
+ out, outErr := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem")}}, t, httpClient)
+ assert.Equal(t, "", outErr)
assert.Contains(t, out, "Success: Submitted")
assert.Contains(t, out, "See https://console.vespa.oath.cloud/tenant/t1/application/a1/prod/deployment for deployment progress")
}
@@ -177,12 +178,12 @@ func TestProdSubmitWithJava(t *testing.T) {
pkgDir := filepath.Join(t.TempDir(), "app")
createApplication(t, pkgDir, true)
- httpClient := &mockHttpClient{}
+ httpClient := &mock.HTTPClient{}
httpClient.NextResponse(200, `ok`)
execute(command{homeDir: homeDir, args: []string{"config", "set", "application", "t1.a1.i1"}}, t, httpClient)
execute(command{homeDir: homeDir, args: []string{"config", "set", "target", "cloud"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"api-key"}}, t, httpClient)
- execute(command{homeDir: homeDir, args: []string{"cert", pkgDir}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "api-key"}}, t, httpClient)
+ execute(command{homeDir: homeDir, args: []string{"auth", "cert", pkgDir}}, t, httpClient)
// Copy an application package pre-assembled with mvn package
testAppDir := filepath.Join("testdata", "applications", "withDeployment", "target")
@@ -191,7 +192,8 @@ func TestProdSubmitWithJava(t *testing.T) {
testZipFile := filepath.Join(testAppDir, "application-test.zip")
copyFile(t, filepath.Join(pkgDir, "target", "application-test.zip"), testZipFile)
- out, _ := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem"), pkgDir}}, t, httpClient)
+ out, outErr := execute(command{homeDir: homeDir, args: []string{"prod", "submit", "-k", filepath.Join(homeDir, "t1.api-key.pem"), pkgDir}}, t, httpClient)
+ assert.Equal(t, "", outErr)
assert.Contains(t, out, "Success: Submitted")
assert.Contains(t, out, "See https://console.vespa.oath.cloud/tenant/t1/application/a1/prod/deployment for deployment progress")
}
diff --git a/client/go/cmd/query_test.go b/client/go/cmd/query_test.go
index aef963121aa..d57268b248e 100644
--- a/client/go/cmd/query_test.go
+++ b/client/go/cmd/query_test.go
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/mock"
)
func TestQuery(t *testing.T) {
@@ -19,7 +20,7 @@ func TestQuery(t *testing.T) {
}
func TestQueryVerbose(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(200, "{\"query\":\"result\"}")
cmd := command{args: []string{"query", "-v", "select from sources * where title contains 'foo'"}}
out, errOut := execute(cmd, t, client)
@@ -54,7 +55,7 @@ func TestServerError(t *testing.T) {
}
func assertQuery(t *testing.T, expectedQuery string, query ...string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(200, "{\"query\":\"result\"}")
assert.Equal(t,
"{\n \"query\": \"result\"\n}\n",
@@ -62,11 +63,11 @@ func assertQuery(t *testing.T, expectedQuery string, query ...string) {
"query output")
queryURL, err := queryServiceURL(client)
require.Nil(t, err)
- assert.Equal(t, queryURL+"/search/"+expectedQuery, client.lastRequest.URL.String())
+ assert.Equal(t, queryURL+"/search/"+expectedQuery, client.LastRequest.URL.String())
}
func assertQueryError(t *testing.T, status int, errorMessage string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, errorMessage)
_, outErr := execute(command{args: []string{"query", "yql=select from sources * where title contains 'foo'"}}, t, client)
assert.Equal(t,
@@ -76,7 +77,7 @@ func assertQueryError(t *testing.T, status int, errorMessage string) {
}
func assertQueryServiceError(t *testing.T, status int, errorMessage string) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextResponse(status, errorMessage)
_, outErr := execute(command{args: []string{"query", "yql=select from sources * where title contains 'foo'"}}, t, client)
assert.Equal(t,
@@ -85,7 +86,7 @@ func assertQueryServiceError(t *testing.T, status int, errorMessage string) {
"error output")
}
-func queryServiceURL(client *mockHttpClient) (string, error) {
+func queryServiceURL(client *mock.HTTPClient) (string, error) {
service, err := getService("query", 0, "")
if err != nil {
return "", err
diff --git a/client/go/cmd/root.go b/client/go/cmd/root.go
index f5a846536c5..cbcbb6e5d12 100644
--- a/client/go/cmd/root.go
+++ b/client/go/cmd/root.go
@@ -54,7 +54,6 @@ Vespa documentation: https://docs.vespa.ai`,
colorArg string
quietArg bool
apiKeyFileArg string
- apiKeyArg string
stdin io.ReadWriter = os.Stdin
color = aurora.NewAurora(false)
diff --git a/client/go/cmd/status_test.go b/client/go/cmd/status_test.go
index 631aa511459..fe7228697c7 100644
--- a/client/go/cmd/status_test.go
+++ b/client/go/cmd/status_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
)
func TestStatusDeployCommand(t *testing.T) {
@@ -43,40 +44,40 @@ func TestStatusErrorResponse(t *testing.T) {
}
func assertDeployStatus(target string, args []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
assert.Equal(t,
"Deploy API at "+target+" is ready\n",
executeCommand(t, client, []string{"status", "deploy"}, args),
"vespa status config-server")
- assert.Equal(t, target+"/status.html", client.lastRequest.URL.String())
+ assert.Equal(t, target+"/status.html", client.LastRequest.URL.String())
}
func assertQueryStatus(target string, args []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
assert.Equal(t,
"Container (query API) at "+target+" is ready\n",
executeCommand(t, client, []string{"status", "query"}, args),
"vespa status container")
- assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String())
+ assert.Equal(t, target+"/ApplicationStatus", client.LastRequest.URL.String())
assert.Equal(t,
"Container (query API) at "+target+" is ready\n",
executeCommand(t, client, []string{"status"}, args),
"vespa status (the default)")
- assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String())
+ assert.Equal(t, target+"/ApplicationStatus", client.LastRequest.URL.String())
}
func assertDocumentStatus(target string, args []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
assert.Equal(t,
"Container (document API) at "+target+" is ready\n",
executeCommand(t, client, []string{"status", "document"}, args),
"vespa status container")
- assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String())
+ assert.Equal(t, target+"/ApplicationStatus", client.LastRequest.URL.String())
}
func assertQueryStatusError(target string, args []string, t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextStatus(500)
cmd := []string{"status", "container"}
cmd = append(cmd, args...)
diff --git a/client/go/cmd/test.go b/client/go/cmd/test.go
index 179a8043a2a..d12059a8d12 100644
--- a/client/go/cmd/test.go
+++ b/client/go/cmd/test.go
@@ -25,7 +25,7 @@ import (
func init() {
rootCmd.AddCommand(testCmd)
- testCmd.PersistentFlags().StringVarP(&zoneArg, zoneFlag, "z", "dev.aws-us-east-1c", "The zone to use for deployment")
+ testCmd.PersistentFlags().StringVarP(&zoneArg, zoneFlag, "z", "", "The zone to use for deployment. This defaults to a dev zone")
}
var testCmd = &cobra.Command{
@@ -35,7 +35,7 @@ var testCmd = &cobra.Command{
Runs all JSON test files in the specified directory, or the single JSON test file specified.
-See https://cloud.vespa.ai/en/reference/testing.html for details.`,
+See https://docs.vespa.ai/en/reference/testing.html for details.`,
Example: `$ vespa test src/test/application/tests/system-test
$ vespa test src/test/application/tests/system-test/feed-and-query.json`,
Args: cobra.ExactArgs(1),
@@ -71,11 +71,11 @@ func runTests(rootPath string, dryRun bool) (int, []string, error) {
count := 0
failed := make([]string, 0)
if stat, err := os.Stat(rootPath); err != nil {
- return 0, nil, errHint(err, "See https://cloud.vespa.ai/en/reference/testing")
+ return 0, nil, errHint(err, "See https://docs.vespa.ai/en/reference/testing")
} else if stat.IsDir() {
tests, err := ioutil.ReadDir(rootPath) // TODO: Use os.ReadDir when >= 1.16 is required.
if err != nil {
- return 0, nil, errHint(err, "See https://cloud.vespa.ai/en/reference/testing")
+ return 0, nil, errHint(err, "See https://docs.vespa.ai/en/reference/testing")
}
context := testContext{testsPath: rootPath, dryRun: dryRun}
previousFailed := false
@@ -108,7 +108,7 @@ func runTests(rootPath string, dryRun bool) (int, []string, error) {
count++
}
if count == 0 {
- return 0, nil, errHint(fmt.Errorf("failed to find any tests at %s", rootPath), "See https://cloud.vespa.ai/en/reference/testing")
+ return 0, nil, errHint(fmt.Errorf("failed to find any tests at %s", rootPath), "See https://docs.vespa.ai/en/reference/testing")
}
return count, failed, nil
}
@@ -118,10 +118,10 @@ func runTest(testPath string, context testContext) (string, error) {
var test test
testBytes, err := ioutil.ReadFile(testPath)
if err != nil {
- return "", errHint(err, "See https://cloud.vespa.ai/en/reference/testing")
+ return "", errHint(err, "See https://docs.vespa.ai/en/reference/testing")
}
if err = json.Unmarshal(testBytes, &test); err != nil {
- return "", errHint(fmt.Errorf("failed parsing test at %s: %w", testPath, err), "See https://cloud.vespa.ai/en/reference/testing")
+ return "", errHint(fmt.Errorf("failed parsing test at %s: %w", testPath, err), "See https://docs.vespa.ai/en/reference/testing")
}
testName := test.Name
@@ -135,12 +135,12 @@ func runTest(testPath string, context testContext) (string, error) {
defaultParameters, err := getParameters(test.Defaults.ParametersRaw, filepath.Dir(testPath))
if err != nil {
fmt.Fprintln(stderr)
- return "", errHint(fmt.Errorf("invalid default parameters for %s: %w", testName, err), "See https://cloud.vespa.ai/en/reference/testing")
+ return "", errHint(fmt.Errorf("invalid default parameters for %s: %w", testName, err), "See https://docs.vespa.ai/en/reference/testing")
}
if len(test.Steps) == 0 {
fmt.Fprintln(stderr)
- return "", errHint(fmt.Errorf("a test must have at least one step, but none were found in %s", testPath), "See https://cloud.vespa.ai/en/reference/testing")
+ return "", errHint(fmt.Errorf("a test must have at least one step, but none were found in %s", testPath), "See https://docs.vespa.ai/en/reference/testing")
}
for i, step := range test.Steps {
stepName := fmt.Sprintf("Step %d", i+1)
@@ -150,7 +150,7 @@ func runTest(testPath string, context testContext) (string, error) {
failure, longFailure, err := verify(step, test.Defaults.Cluster, defaultParameters, context)
if err != nil {
fmt.Fprintln(stderr)
- return "", errHint(fmt.Errorf("error in %s: %w", stepName, err), "See https://cloud.vespa.ai/en/reference/testing")
+ return "", errHint(fmt.Errorf("error in %s: %w", stepName, err), "See https://docs.vespa.ai/en/reference/testing")
}
if !context.dryRun {
if failure != "" {
@@ -206,6 +206,9 @@ func verify(step step, defaultCluster string, defaultParameters map[string]strin
return "", "", err
}
externalEndpoint := requestUrl.IsAbs()
+ if !externalEndpoint && filepath.Base(context.testsPath) == "production-test" {
+ return "", "", fmt.Errorf("production tests may not specify requests against Vespa endpoints")
+ }
if !externalEndpoint && !context.dryRun {
target, err := context.target()
if err != nil {
diff --git a/client/go/cmd/test_test.go b/client/go/cmd/test_test.go
index 9c161c091ec..1f7d0cff7b2 100644
--- a/client/go/cmd/test_test.go
+++ b/client/go/cmd/test_test.go
@@ -15,12 +15,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
)
func TestSuite(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
searchResponse, _ := ioutil.ReadFile("testdata/tests/response.json")
client.NextStatus(200)
client.NextStatus(200)
@@ -45,16 +46,25 @@ func TestSuite(t *testing.T) {
}
func TestIllegalFileReference(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextStatus(200)
client.NextStatus(200)
_, errBytes := execute(command{args: []string{"test", "testdata/tests/production-test/illegal-reference.json"}}, t, client)
- assertRequests([]*http.Request{createRequest("GET", "http://127.0.0.1:8080/search/", "{}")}, client, t)
- assert.Equal(t, "\nError: error in Step 2: path may not point outside src/test/application, but 'foo/../../../../this-is-not-ok.json' does\nHint: See https://cloud.vespa.ai/en/reference/testing\n", errBytes)
+ assertRequests([]*http.Request{createRequest("GET", "https://domain.tld", "{}")}, client, t)
+ assert.Equal(t, "\nError: error in Step 2: path may not point outside src/test/application, but 'foo/../../../../this-is-not-ok.json' does\nHint: See https://docs.vespa.ai/en/reference/testing\n", errBytes)
+}
+
+func TestIllegalRequestUri(t *testing.T) {
+ client := &mock.HTTPClient{}
+ client.NextStatus(200)
+ client.NextStatus(200)
+ _, errBytes := execute(command{args: []string{"test", "testdata/tests/production-test/illegal-uri.json"}}, t, client)
+ assertRequests([]*http.Request{createRequest("GET", "https://domain.tld/my-api", "")}, client, t)
+ assert.Equal(t, "\nError: error in Step 2: production tests may not specify requests against Vespa endpoints\nHint: See https://docs.vespa.ai/en/reference/testing\n", errBytes)
}
func TestProductionTest(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
client.NextStatus(200)
outBytes, errBytes := execute(command{args: []string{"test", "testdata/tests/production-test/external.json"}}, t, client)
assert.Equal(t, "external.json: . OK\n\nSuccess: 1 test OK\n", outBytes)
@@ -63,19 +73,19 @@ func TestProductionTest(t *testing.T) {
}
func TestTestWithoutAssertions(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
_, errBytes := execute(command{args: []string{"test", "testdata/tests/system-test/foo/query.json"}}, t, client)
- assert.Equal(t, "\nError: a test must have at least one step, but none were found in testdata/tests/system-test/foo/query.json\nHint: See https://cloud.vespa.ai/en/reference/testing\n", errBytes)
+ assert.Equal(t, "\nError: a test must have at least one step, but none were found in testdata/tests/system-test/foo/query.json\nHint: See https://docs.vespa.ai/en/reference/testing\n", errBytes)
}
func TestSuiteWithoutTests(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
_, errBytes := execute(command{args: []string{"test", "testdata/tests/staging-test"}}, t, client)
- assert.Equal(t, "Error: failed to find any tests at testdata/tests/staging-test\nHint: See https://cloud.vespa.ai/en/reference/testing\n", errBytes)
+ assert.Equal(t, "Error: failed to find any tests at testdata/tests/staging-test\nHint: See https://docs.vespa.ai/en/reference/testing\n", errBytes)
}
func TestSingleTest(t *testing.T) {
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
searchResponse, _ := ioutil.ReadFile("testdata/tests/response.json")
client.NextStatus(200)
client.NextStatus(200)
@@ -112,7 +122,7 @@ func TestSingleTestWithCloudAndEndpoints(t *testing.T) {
ioutil.WriteFile(keyFile, kp.PrivateKey, 0600)
ioutil.WriteFile(certFile, kp.Certificate, 0600)
- client := &mockHttpClient{}
+ client := &mock.HTTPClient{}
searchResponse, _ := ioutil.ReadFile("testdata/tests/response.json")
client.NextStatus(200)
client.NextStatus(200)
@@ -149,10 +159,10 @@ func createRequest(method string, uri string, body string) *http.Request {
}
}
-func assertRequests(requests []*http.Request, client *mockHttpClient, t *testing.T) {
- if assert.Equal(t, len(requests), len(client.requests)) {
+func assertRequests(requests []*http.Request, client *mock.HTTPClient, t *testing.T) {
+ if assert.Equal(t, len(requests), len(client.Requests)) {
for i, e := range requests {
- a := client.requests[i]
+ a := client.Requests[i]
assert.Equal(t, e.URL.String(), a.URL.String())
assert.Equal(t, e.Method, a.Method)
assert.Equal(t, util.ReaderToJSON(e.Body), util.ReaderToJSON(a.Body))
diff --git a/client/go/cmd/testdata/tests/production-test/illegal-reference.json b/client/go/cmd/testdata/tests/production-test/illegal-reference.json
index edd8a2fafeb..ced4a86dd6c 100644
--- a/client/go/cmd/testdata/tests/production-test/illegal-reference.json
+++ b/client/go/cmd/testdata/tests/production-test/illegal-reference.json
@@ -2,11 +2,13 @@
"steps": [
{
"request": {
+ "uri": "https://domain.tld",
"body": "foo/../../../empty.json"
}
},
{
"request": {
+ "uri": "https://domain.tld",
"body": "foo/../../../../this-is-not-ok.json"
}
}
diff --git a/client/go/cmd/testdata/tests/production-test/illegal-uri.json b/client/go/cmd/testdata/tests/production-test/illegal-uri.json
new file mode 100644
index 00000000000..fe4fadfa93d
--- /dev/null
+++ b/client/go/cmd/testdata/tests/production-test/illegal-uri.json
@@ -0,0 +1,14 @@
+{
+ "steps": [
+ {
+ "request": {
+ "uri": "https://domain.tld/my-api"
+ }
+ },
+ {
+ "request": {
+ "uri": "/my-api"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/go/cmd/version_test.go b/client/go/cmd/version_test.go
index 039f75a6ecd..9c05b130e84 100644
--- a/client/go/cmd/version_test.go
+++ b/client/go/cmd/version_test.go
@@ -6,11 +6,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/vespa-engine/vespa/client/go/mock"
"github.com/vespa-engine/vespa/client/go/util"
)
func TestVersion(t *testing.T) {
- c := &mockHttpClient{}
+ c := &mock.HTTPClient{}
c.NextResponse(200, `[{"tag_name": "v1.2.3", "published_at": "2021-09-10T12:00:00Z"}]`)
util.ActiveHttpClient = c
@@ -21,7 +22,7 @@ func TestVersion(t *testing.T) {
}
func TestVersionCheckHomebrew(t *testing.T) {
- c := &mockHttpClient{}
+ c := &mock.HTTPClient{}
c.NextResponse(200, `[{"tag_name": "v1.2.3", "published_at": "2021-09-10T12:00:00Z"}]`)
util.ActiveHttpClient = c