diff --git a/actor/v7action/deployment.go b/actor/v7action/deployment.go index e21873d8f4..95faadb6f1 100644 --- a/actor/v7action/deployment.go +++ b/actor/v7action/deployment.go @@ -29,7 +29,7 @@ func (actor Actor) GetLatestActiveDeploymentForApp(appGUID string) (resources.De return resources.Deployment{}, Warnings(warnings), actionerror.ActiveDeploymentNotFoundError{} } - return resources.Deployment(ccDeployments[0]), Warnings(warnings), nil + return ccDeployments[0], Warnings(warnings), nil } func (actor Actor) CancelDeployment(deploymentGUID string) (Warnings, error) { diff --git a/api/cloudcontroller/ccv3/application_test.go b/api/cloudcontroller/ccv3/application_test.go index 46002b20b0..e85901cbdd 100644 --- a/api/cloudcontroller/ccv3/application_test.go +++ b/api/cloudcontroller/ccv3/application_test.go @@ -217,7 +217,6 @@ var _ = Describe("Application", func() { }) When("lifecycle type buildpack is provided", func() { - When("other buildpacks are provided", func() { BeforeEach(func() { appBytes = []byte(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`) diff --git a/api/cloudcontroller/ccv3/deployment_test.go b/api/cloudcontroller/ccv3/deployment_test.go index 87b37f8c7c..dea408b876 100644 --- a/api/cloudcontroller/ccv3/deployment_test.go +++ b/api/cloudcontroller/ccv3/deployment_test.go @@ -339,7 +339,13 @@ var _ = Describe("Deployment", func() { "strategy": "canary", "status": { "value": "FINALIZED", - "reason": "SUPERSEDED" + "reason": "SUPERSEDED", + "canary": { + "steps": { + "current": 4, + "total": 5 + } + } }, "droplet": { "guid": "some-droplet-guid" @@ -374,6 +380,8 @@ var _ = Describe("Deployment", func() { Expect(deployment.StatusValue).To(Equal(constant.DeploymentStatusValueFinalized)) Expect(deployment.StatusReason).To(Equal(constant.DeploymentStatusReasonSuperseded)) Expect(deployment.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(deployment.CanaryStatus.Steps.CurrentStep).To(Equal(4)) + Expect(deployment.CanaryStatus.Steps.TotalSteps).To(Equal(5)) }) }) diff --git a/command/v7/shared/app_summary_displayer.go b/command/v7/shared/app_summary_displayer.go index fc1768c57e..90daf128e6 100644 --- a/command/v7/shared/app_summary_displayer.go +++ b/command/v7/shared/app_summary_displayer.go @@ -158,10 +158,17 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed if maxInFlight > 0 { maxInFlightRow = append(maxInFlightRow, display.UI.TranslateText("max-in-flight:"), strconv.Itoa(maxInFlight)) } + var canaryStepsRow []string + if summary.Deployment.CanaryStatus.Steps.TotalSteps > 0 { + stepStatus := summary.Deployment.CanaryStatus.Steps + canaryStepsRow = []string{display.UI.TranslateText("canary-steps:"), fmt.Sprintf("%d/%d", stepStatus.CurrentStep, stepStatus.TotalSteps)} + + } keyValueTable := [][]string{ {display.UI.TranslateText("strategy:"), strings.ToLower(string(summary.Deployment.Strategy))}, maxInFlightRow, + canaryStepsRow, } display.UI.DisplayKeyValueTable("", keyValueTable, ui.DefaultTableSpacePadding) diff --git a/command/v7/shared/app_summary_displayer_test.go b/command/v7/shared/app_summary_displayer_test.go index 59f4d28bc2..92f6a52b6a 100644 --- a/command/v7/shared/app_summary_displayer_test.go +++ b/command/v7/shared/app_summary_displayer_test.go @@ -887,9 +887,15 @@ var _ = Describe("app summary displayer", func() { BeforeEach(func() { summary = v7action.DetailedApplicationSummary{ Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonDeploying, + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + CanaryStatus: resources.CanaryStatus{ + Steps: resources.CanaryStepStatus{ + CurrentStep: 2, + TotalSteps: 5, + }, + }, LastStatusChange: LastStatusChangeTimeString, Options: resources.DeploymentOpts{ MaxInFlight: 2, @@ -907,15 +913,25 @@ var _ = Describe("app summary displayer", func() { It("displays max-in-flight value", func() { Expect(testUI.Out).To(Say(`max-in-flight: 2`)) }) + + It("displays the step the deployment is currently in", func() { + Expect(testUI.Out).To(Say(`canary-steps: 2/5`)) + }) }) When("max-in-flight value is default", func() { BeforeEach(func() { summary = v7action.DetailedApplicationSummary{ Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonDeploying, + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + CanaryStatus: resources.CanaryStatus{ + Steps: resources.CanaryStepStatus{ + CurrentStep: 2, + TotalSteps: 5, + }, + }, LastStatusChange: LastStatusChangeTimeString, Options: resources.DeploymentOpts{ MaxInFlight: maxInFlightDefaultValue, @@ -943,9 +959,15 @@ var _ = Describe("app summary displayer", func() { }, }, Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonPaused, + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonPaused, + CanaryStatus: resources.CanaryStatus{ + Steps: resources.CanaryStepStatus{ + CurrentStep: 2, + TotalSteps: 5, + }, + }, LastStatusChange: LastStatusChangeTimeString, Options: resources.DeploymentOpts{ MaxInFlight: 2, @@ -974,9 +996,15 @@ var _ = Describe("app summary displayer", func() { }, }, Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonPaused, + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonPaused, + CanaryStatus: resources.CanaryStatus{ + Steps: resources.CanaryStepStatus{ + CurrentStep: 2, + TotalSteps: 5, + }, + }, LastStatusChange: LastStatusChangeTimeString, Options: resources.DeploymentOpts{ MaxInFlight: maxInFlightDefaultValue, @@ -999,9 +1027,15 @@ var _ = Describe("app summary displayer", func() { BeforeEach(func() { summary = v7action.DetailedApplicationSummary{ Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonCanceling, + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonCanceling, + CanaryStatus: resources.CanaryStatus{ + Steps: resources.CanaryStepStatus{ + CurrentStep: 2, + TotalSteps: 5, + }, + }, LastStatusChange: LastStatusChangeTimeString, Options: resources.DeploymentOpts{ MaxInFlight: 2, diff --git a/integration/v7/isolated/app_command_test.go b/integration/v7/isolated/app_command_test.go index fa31c65486..febfed7314 100644 --- a/integration/v7/isolated/app_command_test.go +++ b/integration/v7/isolated/app_command_test.go @@ -285,6 +285,7 @@ applications: session1 := helpers.CF("app", appName) Eventually(session1).Should(Say("Active deployment with status PAUSED")) Eventually(session1).Should(Say("strategy: canary")) + Expect(session1).To(Say("canary-steps: 0/1")) Eventually(session1).Should(Exit(0)) }) }) diff --git a/integration/v7/isolated/continue_deployment_test.go b/integration/v7/isolated/continue_deployment_test.go index 29f0362363..d191f28d2f 100644 --- a/integration/v7/isolated/continue_deployment_test.go +++ b/integration/v7/isolated/continue_deployment_test.go @@ -89,15 +89,29 @@ var _ = Describe("Continue Deployment", func() { Context("when the continue is successful", func() { When("There is a canary deployment", func() { - It("succeeds", func() { - helpers.WithHelloWorldApp(func(appDir string) { - helpers.CF("push", appName, "-p", appDir, "--strategy=canary").Wait() + When("instance steps are provided", func() { + It("displays the number of steps", func() { + helpers.WithHelloWorldApp(func(appDir string) { + helpers.CF("push", appName, "-p", appDir, "--strategy=canary", "--instance-steps 10,20,30,70", "-i 5").Wait() + }) + + session := helpers.CF("continue-deployment", appName) + Eventually(session).Should(Say("canary-steps: 1/4")) + Eventually(session).Should(Exit(0)) }) + }) + + When("instance steps are NOT provided", func() { + It("succeeds", func() { + helpers.WithHelloWorldApp(func(appDir string) { + helpers.CF("push", appName, "-p", appDir, "--strategy=canary").Wait() + }) - session := helpers.CF("continue-deployment", appName) - Eventually(session).Should(Say(fmt.Sprintf("Continuing deployment for app %s in org %s / space %s as %s...", appName, orgName, spaceName, userName))) - Eventually(session).Should(Say(fmt.Sprintf(`TIP: Run 'cf app %s' to view app status.`, appName))) - Eventually(session).Should(Exit(0)) + session := helpers.CF("continue-deployment", appName) + Eventually(session).Should(Say(fmt.Sprintf("Continuing deployment for app %s in org %s / space %s as %s...", appName, orgName, spaceName, userName))) + Eventually(session).Should(Say(fmt.Sprintf(`TIP: Run 'cf app %s' to view app status.`, appName))) + Eventually(session).Should(Exit(0)) + }) }) }) }) diff --git a/integration/v7/push/canary_push_test.go b/integration/v7/push/canary_push_test.go index 58a9e5cf58..d156225bea 100644 --- a/integration/v7/push/canary_push_test.go +++ b/integration/v7/push/canary_push_test.go @@ -34,7 +34,7 @@ var _ = Describe("push with --strategy canary", func() { It("pushes the app and creates a new deployment and notes the max-in-flight value", func() { helpers.WithHelloWorldApp(func(appDir string) { session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, - PushCommandName, appName, "--strategy", "canary", + PushCommandName, appName, "--strategy", "canary", "--instance-steps=10,60", ) Eventually(session).Should(Exit(0)) @@ -55,6 +55,7 @@ var _ = Describe("push with --strategy canary", func() { Expect(session).To(Say("Active deployment with status PAUSED")) Expect(session).To(Say("strategy: canary")) Expect(session).To(Say("max-in-flight: 1")) + Expect(session).To(Say("canary-steps: 1/2")) Expect(session).To(Say("Please run `cf continue-deployment %s` to promote the canary deployment, or `cf cancel-deployment %s` to rollback to the previous version.", appName, appName)) Expect(session).To(Exit(0)) }) @@ -86,6 +87,7 @@ var _ = Describe("push with --strategy canary", func() { Expect(session).To(Say("Active deployment with status PAUSED")) Expect(session).To(Say("strategy: canary")) Expect(session).To(Say("max-in-flight: 2")) + Expect(session).To(Say("canary-steps: 0/1")) Expect(session).To(Say("Please run `cf continue-deployment %s` to promote the canary deployment, or `cf cancel-deployment %s` to rollback to the previous version.", appName, appName)) Expect(session).To(Exit(0)) }) diff --git a/resources/deployment_resource.go b/resources/deployment_resource.go index 60dbdf4b9f..16aae5bfa0 100644 --- a/resources/deployment_resource.go +++ b/resources/deployment_resource.go @@ -12,6 +12,7 @@ type Deployment struct { State constant.DeploymentState StatusValue constant.DeploymentStatusValue StatusReason constant.DeploymentStatusReason + CanaryStatus CanaryStatus LastStatusChange string Options DeploymentOpts RevisionGUID string @@ -78,6 +79,15 @@ func (d Deployment) MarshalJSON() ([]byte, error) { return json.Marshal(ccDeployment) } +type CanaryStepStatus struct { + CurrentStep int `json:"current"` + TotalSteps int `json:"total"` +} + +type CanaryStatus struct { + Steps CanaryStepStatus `json:"steps"` +} + // UnmarshalJSON helps unmarshal a Cloud Controller Deployment response. func (d *Deployment) UnmarshalJSON(data []byte) error { var ccDeployment struct { @@ -89,8 +99,9 @@ func (d *Deployment) UnmarshalJSON(data []byte) error { Details struct { LastStatusChange string `json:"last_status_change"` } - Value constant.DeploymentStatusValue `json:"value"` - Reason constant.DeploymentStatusReason `json:"reason"` + Value constant.DeploymentStatusValue `json:"value"` + Reason constant.DeploymentStatusReason `json:"reason"` + CanaryStatus CanaryStatus `json:"canary,omitempty"` } `json:"status"` Droplet Droplet `json:"droplet,omitempty"` NewProcesses []Process `json:"new_processes,omitempty"` @@ -109,6 +120,7 @@ func (d *Deployment) UnmarshalJSON(data []byte) error { d.State = ccDeployment.State d.StatusValue = ccDeployment.Status.Value d.StatusReason = ccDeployment.Status.Reason + d.CanaryStatus = ccDeployment.Status.CanaryStatus d.LastStatusChange = ccDeployment.Status.Details.LastStatusChange d.DropletGUID = ccDeployment.Droplet.GUID d.NewProcesses = ccDeployment.NewProcesses