summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/go/cmd/clone.go16
-rw-r--r--client/go/cmd/clone_test.go3
-rw-r--r--client/go/cmd/command_tester.go4
-rw-r--r--client/go/cmd/deploy.go61
-rw-r--r--client/go/cmd/deploy_test.go57
-rw-r--r--client/go/cmd/document.go2
-rw-r--r--client/go/cmd/print.go2
-rw-r--r--client/go/cmd/query.go2
-rw-r--r--client/go/cmd/status.go8
-rw-r--r--client/go/util/io.go2
-rw-r--r--client/go/vespa/deploy.go99
11 files changed, 189 insertions, 67 deletions
diff --git a/client/go/cmd/clone.go b/client/go/cmd/clone.go
index 8d4b382db76..9604476df4e 100644
--- a/client/go/cmd/clone.go
+++ b/client/go/cmd/clone.go
@@ -52,7 +52,7 @@ func cloneApplication(source string, name string) {
zipReader, zipOpenError := zip.OpenReader(zipFile.Name())
if zipOpenError != nil {
log.Print(color.Red("Error: "), "Could not open sample apps zip '", color.Cyan(zipFile.Name()), "'")
- log.Print(color.Brown(zipOpenError))
+ log.Print(color.Yellow(zipOpenError))
}
defer zipReader.Close()
@@ -64,7 +64,7 @@ func cloneApplication(source string, name string) {
createErr := os.Mkdir(name, 0755)
if createErr != nil {
log.Print(color.Red("Error: "), "Could not create directory '", color.Cyan(name), "'")
- log.Print(color.Brown(createErr))
+ log.Print(color.Yellow(createErr))
return
}
}
@@ -73,7 +73,7 @@ func cloneApplication(source string, name string) {
copyError := copy(f, name, zipEntryPrefix)
if copyError != nil {
log.Print(color.Red("Error: "), "Could not copy zip entry '", color.Cyan(f.Name), "' to ", color.Cyan(name))
- log.Print(color.Brown(copyError))
+ log.Print(color.Yellow(copyError))
return
}
}
@@ -90,13 +90,13 @@ func getSampleAppsZip() *os.File {
existing, openExistingError := os.Open(existingSampleAppsZip)
if openExistingError != nil {
log.Print(color.Red("Error: "), "Could not open existing sample apps zip file '", color.Cyan(existingSampleAppsZip), "'")
- log.Print(color.Brown(openExistingError))
+ log.Print(color.Yellow(openExistingError))
}
return existing
}
// TODO: Cache it?
- log.Print(color.Brown("Downloading sample apps ...")) // TODO: Spawn thread to indicate progress
+ log.Print(color.Yellow("Downloading sample apps ...")) // TODO: Spawn thread to indicate progress
zipUrl, _ := url.Parse("https://github.com/vespa-engine/sample-apps/archive/refs/heads/master.zip")
request := &http.Request{
URL: zipUrl,
@@ -105,7 +105,7 @@ func getSampleAppsZip() *os.File {
response, reqErr := util.HttpDo(request, time.Minute*60, "GitHub")
if reqErr != nil {
log.Print(color.Red("Error: "), "Could not download sample apps from github")
- log.Print(color.Brown(reqErr))
+ log.Print(color.Yellow(reqErr))
return nil
}
defer response.Body.Close()
@@ -117,14 +117,14 @@ func getSampleAppsZip() *os.File {
destination, tempFileError := ioutil.TempFile("", "prefix")
if tempFileError != nil {
log.Print(color.Red("Error: "), "Could not create a temp file to hold sample apps")
- log.Print(color.Brown(tempFileError))
+ log.Print(color.Yellow(tempFileError))
}
// destination, _ := os.Create("./" + name + "/sample-apps.zip")
// defer destination.Close()
_, err := io.Copy(destination, response.Body)
if err != nil {
log.Print(color.Red("Error: "), "Could not download sample apps from GitHub")
- log.Print(color.Brown(err))
+ log.Print(color.Yellow(err))
return nil
}
return destination
diff --git a/client/go/cmd/clone_test.go b/client/go/cmd/clone_test.go
index a98fed694fe..313ad849038 100644
--- a/client/go/cmd/clone_test.go
+++ b/client/go/cmd/clone_test.go
@@ -27,7 +27,6 @@ func assertCreated(sampleAppName string, app string, t *testing.T) {
assert.True(t, util.IsDirectory(filepath.Join(app, "src", "main", "application")))
servicesStat, _ := os.Stat(filepath.Join(app, "src", "main", "application", "services.xml"))
- var servicesSize int64
- servicesSize = 2474
+ servicesSize := int64(2474)
assert.Equal(t, servicesSize, servicesStat.Size())
}
diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester.go
index 074089b399d..f40594c288e 100644
--- a/client/go/cmd/command_tester.go
+++ b/client/go/cmd/command_tester.go
@@ -78,6 +78,9 @@ type mockHttpClient struct {
// A recording of the last HTTP request made through this
lastRequest *http.Request
+
+ // All requests made through this
+ requests []*http.Request
}
func (c *mockHttpClient) Do(request *http.Request, timeout time.Duration) (response *http.Response, error error) {
@@ -85,6 +88,7 @@ func (c *mockHttpClient) Do(request *http.Request, timeout time.Duration) (respo
c.nextStatus = 200
}
c.lastRequest = request
+ c.requests = append(c.requests, request)
return &http.Response{
Status: "Status " + strconv.Itoa(c.nextStatus),
StatusCode: c.nextStatus,
diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go
index 4d8caacce22..0dffe42cc5c 100644
--- a/client/go/cmd/deploy.go
+++ b/client/go/cmd/deploy.go
@@ -9,6 +9,8 @@ import (
"log"
"os"
"path/filepath"
+ "strconv"
+ "strings"
"github.com/spf13/cobra"
"github.com/vespa-engine/vespa/vespa"
@@ -64,15 +66,14 @@ var deployCmd = &cobra.Command{
if configDir == "" {
return
}
- d.APIKey = loadApiKey(configDir, d.Application.Tenant)
+ d.APIKey = readAPIKey(configDir, d.Application.Tenant)
if d.APIKey == nil {
printErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'")
return
}
}
- resolvedSrc, err := vespa.Deploy(d)
- if err == nil {
- printSuccess("Deployed ", color.Cyan(resolvedSrc))
+ if err := vespa.Deploy(d); err == nil {
+ printSuccess("Deployed ", color.Cyan(pkg.Path))
if d.IsCloud() {
log.Print("See ", color.Cyan(fmt.Sprintf("https://console.vespa.oath.cloud/tenant/%s/application/%s/dev/instance/%s", d.Application.Tenant, d.Application.Application, d.Application.Instance)), " for deployment status")
}
@@ -92,9 +93,21 @@ var prepareCmd = &cobra.Command{
printErr(err, "Could not find application package")
return
}
- resolvedSrc, err := vespa.Prepare(vespa.Deployment{ApplicationPackage: pkg})
+ configDir := configDir("default")
+ if configDir == "" {
+ return
+ }
+ sessionID, err := vespa.Prepare(vespa.Deployment{
+ ApplicationPackage: pkg,
+ TargetType: getTargetType(),
+ TargetURL: deployTarget(),
+ })
if err == nil {
- printSuccess("Prepared ", color.Cyan(resolvedSrc))
+ if err := writeSessionID(configDir, sessionID); err != nil {
+ printErr(err, "Could not write session ID")
+ return
+ }
+ printSuccess("Prepared ", color.Cyan(pkg.Path), " with session ", sessionID)
} else {
printErr(nil, err.Error())
}
@@ -111,16 +124,46 @@ var activateCmd = &cobra.Command{
printErr(err, "Could not find application package")
return
}
- resolvedSrc, err := vespa.Activate(vespa.Deployment{ApplicationPackage: pkg})
+ configDir := configDir("default")
+ if configDir == "" {
+ return
+ }
+ sessionID, err := readSessionID(configDir)
+ if err != nil {
+ printErr(err, "Could not read session ID")
+ return
+ }
+ err = vespa.Activate(sessionID, vespa.Deployment{
+ ApplicationPackage: pkg,
+ TargetType: getTargetType(),
+ TargetURL: deployTarget(),
+ })
if err == nil {
- printSuccess("Activated ", color.Cyan(resolvedSrc))
+ printSuccess("Activated ", color.Cyan(pkg.Path), " with session ", sessionID)
} else {
printErr(nil, err.Error())
}
},
}
-func loadApiKey(configDir, tenant string) []byte {
+func writeSessionID(appConfigDir string, sessionID int64) error {
+ if err := os.MkdirAll(appConfigDir, 0755); err != nil {
+ return err
+ }
+ return os.WriteFile(sessionIDFile(appConfigDir), []byte(fmt.Sprintf("%d\n", sessionID)), 0600)
+}
+
+func readSessionID(appConfigDir string) (int64, error) {
+ b, err := os.ReadFile(sessionIDFile(appConfigDir))
+ if err != nil {
+ return 0, err
+ }
+ return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64)
+}
+
+func sessionIDFile(appConfigDir string) string { return filepath.Join(appConfigDir, "session_id") }
+
+func readAPIKey(configDir, tenant string) []byte {
apiKeyPath := filepath.Join(configDir, tenant+".api-key.pem")
key, err := os.ReadFile(apiKeyPath)
if err != nil {
diff --git a/client/go/cmd/deploy_test.go b/client/go/cmd/deploy_test.go
index d4fddf11f99..0a8db5f4fd2 100644
--- a/client/go/cmd/deploy_test.go
+++ b/client/go/cmd/deploy_test.go
@@ -5,12 +5,23 @@
package cmd
import (
+ "path/filepath"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
+func TestPrepareZip(t *testing.T) {
+ assertPrepare("testdata/applications/withTarget/target/application.zip",
+ []string{"prepare", "testdata/applications/withTarget/target/application.zip"}, t)
+}
+
+func TestActivateZip(t *testing.T) {
+ assertActivate("testdata/applications/withTarget/target/application.zip",
+ []string{"activate", "testdata/applications/withTarget/target/application.zip"}, t)
+}
+
func TestDeployZip(t *testing.T) {
assertDeploy("testdata/applications/withTarget/target/application.zip",
[]string{"deploy", "testdata/applications/withTarget/target/application.zip"}, t)
@@ -89,17 +100,53 @@ func assertDeploy(applicationPackage string, arguments []string, t *testing.T) {
assertDeployRequestMade("http://127.0.0.1:19071", client, t)
}
-func assertDeployRequestMade(target string, client *mockHttpClient, t *testing.T) {
- assert.Equal(t, target+"/application/v2/tenant/default/prepareandactivate", client.lastRequest.URL.String())
- assert.Equal(t, "application/zip", client.lastRequest.Header.Get("Content-Type"))
- assert.Equal(t, "POST", client.lastRequest.Method)
- var body = client.lastRequest.Body
+func assertPrepare(applicationPackage string, arguments []string, t *testing.T) {
+ client := &mockHttpClient{}
+ client.nextBody = `{"session-id":"42"}`
+ assert.Equal(t,
+ "Success: Prepared "+applicationPackage+" with session 42\n",
+ executeCommand(t, client, arguments, []string{}))
+
+ 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)
+}
+
+func assertActivate(applicationPackage string, arguments []string, t *testing.T) {
+ client := &mockHttpClient{}
+ configDir := t.TempDir()
+ appConfigDir := filepath.Join(configDir, ".vespa", "default")
+ if err := writeSessionID(appConfigDir, 42); err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t,
+ "Success: Activated "+applicationPackage+" with session 42\n",
+ execute(command{args: arguments, configDir: configDir}, t, client))
+ 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)
+}
+
+func assertPackageUpload(requestNumber int, url string, client *mockHttpClient, t *testing.T) {
+ req := client.lastRequest
+ if requestNumber >= 0 {
+ req = client.requests[requestNumber]
+ }
+ assert.Equal(t, url, req.URL.String())
+ assert.Equal(t, "application/zip", req.Header.Get("Content-Type"))
+ assert.Equal(t, "POST", req.Method)
+ var body = req.Body
assert.NotNil(t, body)
buf := make([]byte, 7) // Just check the first few bytes
body.Read(buf)
assert.Equal(t, "PK\x03\x04\x14\x00\b", string(buf))
}
+func assertDeployRequestMade(target string, client *mockHttpClient, t *testing.T) {
+ assertPackageUpload(-1, target+"/application/v2/tenant/default/prepareandactivate", client, t)
+}
+
func assertApplicationPackageError(t *testing.T, status int, expectedMessage string, returnBody string) {
client := &mockHttpClient{nextStatus: status, nextBody: returnBody}
assert.Equal(t,
diff --git a/client/go/cmd/document.go b/client/go/cmd/document.go
index a709da222bd..4819d89fb7e 100644
--- a/client/go/cmd/document.go
+++ b/client/go/cmd/document.go
@@ -107,7 +107,7 @@ func printResult(result util.OperationResult, payloadOnlyOnSuccess bool) {
}
if result.Detail != "" {
- log.Print(color.Brown(result.Detail))
+ log.Print(color.Yellow(result.Detail))
}
if result.Payload != "" {
diff --git a/client/go/cmd/print.go b/client/go/cmd/print.go
index 55d74536393..b6394dbe340 100644
--- a/client/go/cmd/print.go
+++ b/client/go/cmd/print.go
@@ -19,7 +19,7 @@ func printErr(err error, msg ...interface{}) {
log.Print(color.Red("Error: "), fmt.Sprint(msg...))
}
if err != nil {
- log.Print(color.Brown(err))
+ log.Print(color.Yellow(err))
}
}
diff --git a/client/go/cmd/query.go b/client/go/cmd/query.go
index 5143dcf6f36..e49b51b79b8 100644
--- a/client/go/cmd/query.go
+++ b/client/go/cmd/query.go
@@ -62,6 +62,6 @@ func splitArg(argument string) (string, string) {
if equalsIndex < 1 {
return "yql", argument
} else {
- return argument[0:equalsIndex], argument[equalsIndex+1 : len(argument)]
+ return argument[0:equalsIndex], argument[equalsIndex+1:]
}
}
diff --git a/client/go/cmd/status.go b/client/go/cmd/status.go
index 4d0b44ee826..92e86573198 100644
--- a/client/go/cmd/status.go
+++ b/client/go/cmd/status.go
@@ -29,7 +29,7 @@ var statusCmd = &cobra.Command{
var statusQueryCmd = &cobra.Command{
Use: "query",
- Short: "Verify that your Vespa query API container endpoint is ready [Default]",
+ Short: "Verify that your Vespa query API container endpoint is ready (default)",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
status(queryTarget(), "Query API")
@@ -38,7 +38,7 @@ var statusQueryCmd = &cobra.Command{
var statusDocumentCmd = &cobra.Command{
Use: "document",
- Short: "Verify that your Vespa document API container endpoint is ready [Default]",
+ Short: "Verify that your Vespa document API container endpoint is ready",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
status(documentTarget(), "Document API")
@@ -59,14 +59,14 @@ func status(target string, description string) {
response, err := util.HttpGet(target, path, description)
if err != nil {
log.Print(description, " at ", color.Cyan(target), " is ", color.Red("not ready"))
- log.Print(color.Brown(err))
+ log.Print(color.Yellow(err))
return
}
defer response.Body.Close()
if response.StatusCode != 200 {
log.Print(description, " at ", color.Cyan(target), " is ", color.Red("not ready"))
- log.Print(color.Brown(response.Status))
+ log.Print(color.Yellow(response.Status))
} else {
log.Print(description, " at ", color.Cyan(target), " is ", color.Green("ready"))
}
diff --git a/client/go/util/io.go b/client/go/util/io.go
index 5ce9708ed7a..51361e344f0 100644
--- a/client/go/util/io.go
+++ b/client/go/util/io.go
@@ -47,5 +47,5 @@ func ReaderToJSON(reader io.Reader) string {
if parseError != nil { // Not JSON: Print plainly
return string(bodyBytes)
}
- return string(prettyJSON.Bytes())
+ return prettyJSON.String()
}
diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go
index d81b8decdde..2bebeb92e0c 100644
--- a/client/go/vespa/deploy.go
+++ b/client/go/vespa/deploy.go
@@ -16,6 +16,7 @@ import (
"net/url"
"os"
"path/filepath"
+ "strconv"
"strings"
"time"
@@ -127,43 +128,69 @@ func ZoneFromString(s string) (ZoneID, error) {
return ZoneID{Environment: parts[0], Region: parts[1]}, nil
}
-func Prepare(deployment Deployment) (string, error) {
+// Prepare deployment and return the session ID
+func Prepare(deployment Deployment) (int64, error) {
if deployment.IsCloud() {
- return "", fmt.Errorf("%s: prepare is not supported", deployment)
+ return 0, fmt.Errorf("%s: prepare is not supported", deployment)
}
- // TODO: Save session id in .vespa
- // https://docs.vespa.ai/en/cloudconfig/deploy-rest-api-v2.html
- u, err := url.Parse(deployment.TargetURL + "/application/v2/tenant/default/prepare")
+ sessionURL, err := url.Parse(deployment.TargetURL + "/application/v2/tenant/default/session")
if err != nil {
- return "", err
+ return 0, err
}
- return deploy(u, deployment)
+ sessionID, err := uploadApplicationPackage(sessionURL, deployment)
+ if err != nil {
+ return 0, err
+ }
+ prepareURL, err := url.Parse(fmt.Sprintf("%s/application/v2/tenant/default/session/%d/prepared", deployment.TargetURL, sessionID))
+ if err != nil {
+ return 0, err
+ }
+ req, err := http.NewRequest("PUT", prepareURL.String(), nil)
+ if err != nil {
+ return 0, err
+ }
+ serviceDescription := "Deploy service"
+ response, err := util.HttpDo(req, time.Second*30, serviceDescription)
+ if err != nil {
+ return 0, err
+ }
+ defer response.Body.Close()
+ return sessionID, nil
}
-func Activate(deployment Deployment) (string, error) {
+// Activate deployment with sessionID from a past prepare
+func Activate(sessionID int64, deployment Deployment) error {
if deployment.IsCloud() {
- return "", fmt.Errorf("%s: activate is not supported", deployment)
+ return fmt.Errorf("%s: activate is not supported", deployment)
+ }
+ u, err := url.Parse(fmt.Sprintf("%s/application/v2/tenant/default/session/%d/active", deployment.TargetURL, sessionID))
+ if err != nil {
+ return err
+ }
+ req, err := http.NewRequest("PUT", u.String(), nil)
+ if err != nil {
+ return err
}
- // TODO: Look up session id in .vespa
- // https://docs.vespa.ai/en/cloudconfig/deploy-rest-api-v2.html
- u, err := url.Parse(deployment.TargetURL + "/application/v2/tenant/default/activate")
+ serviceDescription := "Deploy service"
+ response, err := util.HttpDo(req, time.Second*30, serviceDescription)
if err != nil {
- return "", err
+ return err
}
- return deploy(u, deployment)
+ defer response.Body.Close()
+ return nil
}
-func Deploy(deployment Deployment) (string, error) {
+func Deploy(deployment Deployment) error {
path := "/application/v2/tenant/default/prepareandactivate"
if deployment.IsCloud() {
if !deployment.ApplicationPackage.HasCertificate() {
- return "", fmt.Errorf("%s: missing certificate in package", deployment)
+ return fmt.Errorf("%s: missing certificate in package", deployment)
}
if deployment.APIKey == nil {
- return "", fmt.Errorf("%s: missing api key", deployment.String())
+ return fmt.Errorf("%s: missing api key", deployment.String())
}
if deployment.Zone.Environment == "" || deployment.Zone.Region == "" {
- return "", fmt.Errorf("%s: missing zone", deployment)
+ return fmt.Errorf("%s: missing zone", deployment)
}
path = fmt.Sprintf("/application/v4/tenant/%s/application/%s/instance/%s/deploy/%s-%s",
deployment.Application.Tenant,
@@ -174,23 +201,17 @@ func Deploy(deployment Deployment) (string, error) {
}
u, err := url.Parse(deployment.TargetURL + path)
if err != nil {
- return "", err
+ return err
}
- return deploy(u, deployment)
+ _, err = uploadApplicationPackage(u, deployment)
+ return err
}
-func deploy(url *url.URL, deployment Deployment) (string, error) {
+func uploadApplicationPackage(url *url.URL, deployment Deployment) (int64, error) {
zipReader, err := deployment.ApplicationPackage.zipReader()
if err != nil {
- return "", err
+ return 0, err
}
- if err := postApplicationPackage(url, zipReader, deployment); err != nil {
- return "", err
- }
- return deployment.ApplicationPackage.Path, nil
-}
-
-func postApplicationPackage(url *url.URL, zipReader io.Reader, deployment Deployment) error {
header := http.Header{}
header.Add("Content-Type", "application/zip")
request := &http.Request{
@@ -202,22 +223,30 @@ func postApplicationPackage(url *url.URL, zipReader io.Reader, deployment Deploy
if deployment.APIKey != nil {
signer := NewRequestSigner(deployment.Application.SerializedForm(), deployment.APIKey)
if err := signer.SignRequest(request); err != nil {
- return err
+ return 0, err
}
}
serviceDescription := "Deploy service"
response, err := util.HttpDo(request, time.Minute*10, serviceDescription)
if err != nil {
- return err
+ return 0, err
}
defer response.Body.Close()
+ var sessionResponse struct {
+ SessionID string `json:"session-id"`
+ }
if response.StatusCode/100 == 4 {
- return fmt.Errorf("Invalid application package (%s)\n\n%s", response.Status, extractError(response.Body))
+ return 0, fmt.Errorf("Invalid application package (%s)\n\n%s", response.Status, extractError(response.Body))
} else if response.StatusCode != 200 {
- return fmt.Errorf("Error from %s at %s (%s):\n%s", strings.ToLower(serviceDescription), request.URL.Host, response.Status, util.ReaderToJSON(response.Body))
+ return 0, fmt.Errorf("Error from %s at %s (%s):\n%s", strings.ToLower(serviceDescription), request.URL.Host, response.Status, util.ReaderToJSON(response.Body))
+ } else {
+ jsonDec := json.NewDecoder(response.Body)
+ if err := jsonDec.Decode(&sessionResponse); err != nil {
+ sessionResponse.SessionID = "0" // No JSON in response
+ }
}
- return nil
+ return strconv.ParseInt(sessionResponse.SessionID, 10, 64)
}
func isZip(filename string) bool { return filepath.Ext(filename) == ".zip" }
@@ -287,6 +316,6 @@ func extractError(reader io.Reader) string {
if parseError != nil { // Not JSON: Print plainly
return string(responseData)
}
- return string(prettyJSON.Bytes())
+ return prettyJSON.String()
}
}