aboutsummaryrefslogtreecommitdiffstats
path: root/client/go/internal/cli
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-08-17 14:57:42 +0200
committerMartin Polden <mpolden@mpolden.no>2023-08-21 14:27:17 +0200
commit8fbbe2b40cd7e4e00d9694e31534ea51dd47f264 (patch)
tree6186dbab5cb9a054386a2807e797cc820b9a0404 /client/go/internal/cli
parentb1863768b512a7200496f8646fe239d3786d4443 (diff)
Add deployment status command
Diffstat (limited to 'client/go/internal/cli')
-rw-r--r--client/go/internal/cli/cmd/root.go1
-rw-r--r--client/go/internal/cli/cmd/status.go53
-rw-r--r--client/go/internal/cli/cmd/status_test.go65
3 files changed, 118 insertions, 1 deletions
diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go
index 2598bff4246..2829dce2b21 100644
--- a/client/go/internal/cli/cmd/root.go
+++ b/client/go/internal/cli/cmd/root.go
@@ -268,6 +268,7 @@ func (c *CLI) configureCommands() {
rootCmd.AddCommand(prodCmd) // prod
rootCmd.AddCommand(newQueryCmd(c)) // query
statusCmd.AddCommand(newStatusDeployCmd(c)) // status deploy
+ statusCmd.AddCommand(newStatusDeploymentCmd(c)) // status deployment
rootCmd.AddCommand(statusCmd) // status
rootCmd.AddCommand(newTestCmd(c)) // test
rootCmd.AddCommand(newVersionCmd(c)) // version
diff --git a/client/go/internal/cli/cmd/status.go b/client/go/internal/cli/cmd/status.go
index 7d17cce97fa..c912a0fa712 100644
--- a/client/go/internal/cli/cmd/status.go
+++ b/client/go/internal/cli/cmd/status.go
@@ -7,6 +7,7 @@ package cmd
import (
"fmt"
"log"
+ "strconv"
"strings"
"time"
@@ -38,7 +39,7 @@ $ vespa status --cluster mycluster`,
}
if cluster == "" {
timeout := time.Duration(waitSecs) * time.Second
- services, err := t.ContainerServices(timeout)
+ services, err := cli.services(t, timeout)
if err != nil {
return err
}
@@ -86,6 +87,56 @@ func newStatusDeployCmd(cli *CLI) *cobra.Command {
return cmd
}
+func newStatusDeploymentCmd(cli *CLI) *cobra.Command {
+ var waitSecs int
+ cmd := &cobra.Command{
+ Use: "deployment",
+ Short: "Verify that deployment has converged on latest, or given, ID",
+ Example: `$ vespa status deployment
+$ vespa status deployment -t cloud [run-id]
+$ vespa status deployment -t local [session-id]
+`,
+ DisableAutoGenTag: true,
+ SilenceUsage: true,
+ Args: cobra.MaximumNArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ wantedID := vespa.LatestDeployment
+ if len(args) > 0 {
+ n, err := strconv.ParseInt(args[0], 10, 64)
+ if err != nil {
+ return fmt.Errorf("invalid id: %s: %w", args[0], err)
+ }
+ wantedID = n
+ }
+ t, err := cli.target(targetOptions{logLevel: "none"})
+ if err != nil {
+ return err
+ }
+ timeout := time.Duration(waitSecs) * time.Second
+ if timeout > 0 {
+ cli.printInfo("Waiting up to ", color.CyanString(timeout.String()), " for deployment to converge ...")
+ }
+ id, err := t.AwaitDeployment(wantedID, timeout)
+ if err != nil {
+ return err
+ }
+ if t.IsCloud() {
+ log.Printf("Deployment run %s has completed", color.CyanString(strconv.FormatInt(id, 10)))
+ log.Printf("See %s for more details", color.CyanString(fmt.Sprintf("%s/tenant/%s/application/%s/%s/instance/%s/job/%s-%s/run/%d",
+ t.Deployment().System.ConsoleURL,
+ t.Deployment().Application.Tenant, t.Deployment().Application.Application, t.Deployment().Zone.Environment,
+ t.Deployment().Application.Instance, t.Deployment().Zone.Environment, t.Deployment().Zone.Region,
+ id)))
+ } else {
+ log.Printf("Deployment is %s on config generation %s", color.GreenString("ready"), color.CyanString(strconv.FormatInt(id, 10)))
+ }
+ return nil
+ },
+ }
+ cli.bindWaitFlag(cmd, 0, &waitSecs)
+ return cmd
+}
+
func printServiceStatus(s *vespa.Service, waitErr error, cli *CLI) error {
if waitErr != nil {
return waitErr
diff --git a/client/go/internal/cli/cmd/status_test.go b/client/go/internal/cli/cmd/status_test.go
index 15ec9280587..36f51ff5073 100644
--- a/client/go/internal/cli/cmd/status_test.go
+++ b/client/go/internal/cli/cmd/status_test.go
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/vespa-engine/vespa/client/go/internal/mock"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
)
func TestStatusDeployCommand(t *testing.T) {
@@ -76,6 +77,70 @@ func TestStatusError(t *testing.T) {
stderr.String())
}
+func TestStatusLocalDeployment(t *testing.T) {
+ client := &mock.HTTPClient{}
+ cli, stdout, stderr := newTestCLI(t)
+ cli.httpClient = client
+ resp := mock.HTTPResponse{
+ URI: "/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/serviceconverge",
+ Status: 200,
+ }
+ // Latest generation
+ resp.Body = []byte(`{"currentGeneration": 42, "converged": true}`)
+ client.NextResponse(resp)
+ assert.Nil(t, cli.Run("status", "deployment"))
+ assert.Equal(t, "", stderr.String())
+ assert.Equal(t, "Deployment is ready on config generation 42\n", stdout.String())
+
+ // Latest generation without convergence
+ resp.Body = []byte(`{"currentGeneration": 42, "converged": false}`)
+ client.NextResponse(resp)
+ assert.NotNil(t, cli.Run("status", "deployment"))
+ assert.Equal(t, "Error: deployment not converged on latest generation after waiting 0s: wait timed out\n", stderr.String())
+
+ // Explicit generation
+ stderr.Reset()
+ client.NextResponse(resp)
+ assert.NotNil(t, cli.Run("status", "deployment", "41"))
+ assert.Equal(t, "Error: deployment not converged on generation 41 after waiting 0s: wait timed out\n", stderr.String())
+}
+
+func TestStatusCloudDeployment(t *testing.T) {
+ cli, stdout, stderr := newTestCLI(t, "CI=true")
+ app := vespa.ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"}
+ assert.Nil(t, cli.Run("config", "set", "application", app.String()))
+ assert.Nil(t, cli.Run("config", "set", "target", "cloud"))
+ assert.Nil(t, cli.Run("config", "set", "zone", "dev.us-north-1"))
+ assert.Nil(t, cli.Run("auth", "api-key"))
+ stdout.Reset()
+ client := &mock.HTTPClient{}
+ cli.httpClient = client
+ // Latest run
+ client.NextResponse(mock.HTTPResponse{
+ URI: "/application/v4/tenant/t1/application/a1/instance/i1/job/dev-us-north-1?limit=1",
+ Status: 200,
+ Body: []byte(`{"runs": [{"id": 1337}]}`),
+ })
+ client.NextResponse(mock.HTTPResponse{
+ URI: "/application/v4/tenant/t1/application/a1/instance/i1/job/dev-us-north-1/run/1337?after=-1",
+ Status: 200,
+ Body: []byte(`{"active": false, "status": "success"}`),
+ })
+ assert.Nil(t, cli.Run("status", "deployment"))
+ assert.Equal(t, "", stderr.String())
+ assert.Equal(t,
+ "Deployment run 1337 has completed\nSee https://console.vespa-cloud.com/tenant/t1/application/a1/dev/instance/i1/job/dev-us-north-1/run/1337 for more details\n",
+ stdout.String())
+ // Explicit run
+ client.NextResponse(mock.HTTPResponse{
+ URI: "/application/v4/tenant/t1/application/a1/instance/i1/job/dev-us-north-1/run/42?after=-1",
+ Status: 200,
+ Body: []byte(`{"active": false, "status": "failure"}`),
+ })
+ assert.NotNil(t, cli.Run("status", "deployment", "42"))
+ assert.Equal(t, "Error: deployment run 42 incomplete after waiting 0s: run 42 ended with unsuccessful status: failure\n", stderr.String())
+}
+
func isLocalTarget(args []string) bool {
for i := 0; i < len(args)-1; i++ {
if args[i] == "-t" {