diff options
-rw-r--r-- | client/go/internal/cli/cmd/root.go | 1 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/status.go | 53 | ||||
-rw-r--r-- | client/go/internal/cli/cmd/status_test.go | 65 |
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" { |