diff --git a/api/cloudcontroller/ccv3/constant/deployment.go b/api/cloudcontroller/ccv3/constant/deployment.go index 2ae6159ebb7..b35baa374c8 100644 --- a/api/cloudcontroller/ccv3/constant/deployment.go +++ b/api/cloudcontroller/ccv3/constant/deployment.go @@ -31,3 +31,5 @@ const ( DeploymentStatusValueActive DeploymentStatusValue = "ACTIVE" DeploymentStatusValueFinalized DeploymentStatusValue = "FINALIZED" ) + +const DeploymentMaxInFlightDefaultValue int = 1 diff --git a/command/v7/shared/app_summary_displayer.go b/command/v7/shared/app_summary_displayer.go index c784e8f4194..23d9affc4e0 100644 --- a/command/v7/shared/app_summary_displayer.go +++ b/command/v7/shared/app_summary_displayer.go @@ -162,6 +162,12 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed if summary.Deployment.StatusValue == constant.DeploymentStatusValueActive { display.UI.DisplayNewline() display.UI.DisplayText(display.getDeploymentStatusText(summary)) + + var maxInFlight = summary.Deployment.Options.MaxInFlight + if maxInFlight > 0 && maxInFlight != constant.DeploymentMaxInFlightDefaultValue { + display.UI.DisplayText(fmt.Sprintf("max-in-flight: %d", maxInFlight)) + } + if summary.Deployment.Strategy == constant.DeploymentStrategyCanary && summary.Deployment.StatusReason == constant.DeploymentStatusReasonPaused { display.UI.DisplayNewline() display.UI.DisplayText(fmt.Sprintf("Please run `cf continue-deployment %s` to promote the canary deployment, or `cf cancel-deployment %s` to rollback to the previous version.", summary.Application.Name, summary.Application.Name)) @@ -171,25 +177,15 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed func (display AppSummaryDisplayer) getDeploymentStatusText(summary v7action.DetailedApplicationSummary) string { var lastStatusChangeTime = display.getLastStatusChangeTime(summary) - if lastStatusChangeTime != "" { return fmt.Sprintf("%s deployment currently %s (since %s)", cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)), summary.Deployment.StatusReason, lastStatusChangeTime) } else { - var sb strings.Builder - sb.WriteString(fmt.Sprintf("%s deployment currently %s.", + return fmt.Sprintf("%s deployment currently %s.", cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)), - summary.Deployment.StatusReason)) - - if summary.Deployment.Strategy == constant.DeploymentStrategyCanary && summary.Deployment.StatusReason == constant.DeploymentStatusReasonPaused { - sb.WriteString("\n") - sb.WriteString(fmt.Sprintf( - "Please run `cf continue-deployment %s` to promote the canary deployment, or `cf cancel-deployment %s` to rollback to the previous version.", - summary.Application.Name, summary.Application.Name)) - } - return sb.String() + summary.Deployment.StatusReason) } } diff --git a/command/v7/shared/app_summary_displayer_test.go b/command/v7/shared/app_summary_displayer_test.go index b95ece0a046..6494b5956e7 100644 --- a/command/v7/shared/app_summary_displayer_test.go +++ b/command/v7/shared/app_summary_displayer_test.go @@ -698,10 +698,59 @@ var _ = Describe("app summary displayer", func() { When("there is an active deployment", func() { var LastStatusChangeTimeString = "2024-07-29T17:32:29Z" var dateTimeRegexPattern = `[a-zA-Z]{3}\s\d{2}\s[a-zA-Z]{3}\s\d{2}\:\d{2}\:\d{2}\s[A-Z]{3}\s\d{4}` + var maxInFlightDefaultValue = 1 When("the deployment strategy is rolling", func() { When("the deployment is in progress", func() { - When("last status change has a timestamp", func() { + When("last status change has a timestamp and max-in-flight is non-default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyRolling, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + }) + + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) + }) + + When("last status change has a timestamp and max-in-flight is default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyRolling, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) + }) + + When("an older version of CAPI does not return max-in-flight", func() { BeforeEach(func() { summary = v7action.DetailedApplicationSummary{ Deployment: resources.Deployment{ @@ -714,12 +763,15 @@ var _ = Describe("app summary displayer", func() { }) It("displays the message", func() { - var actualOut = fmt.Sprintf("%s", testUI.Out) - Expect(actualOut).To(MatchRegexp(`Rolling deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) }) }) - When("last status change is an empty string", func() { + When("last status change is an empty string and max-in-flight is non-default", func() { BeforeEach(func() { summary = v7action.DetailedApplicationSummary{ Deployment: resources.Deployment{ @@ -727,6 +779,9 @@ var _ = Describe("app summary displayer", func() { StatusValue: constant.DeploymentStatusValueActive, StatusReason: constant.DeploymentStatusReasonDeploying, LastStatusChange: "", + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, }, } }) @@ -735,126 +790,253 @@ var _ = Describe("app summary displayer", func() { Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING`)) Expect(testUI.Out).NotTo(Say(`\(since`)) }) + + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) + }) + + When("last status change is an empty string and max-in-flight is default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyRolling, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + LastStatusChange: "", + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING`)) + Expect(testUI.Out).NotTo(Say(`\(since`)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) When("the deployment is cancelled", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyRolling, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonCanceling, - LastStatusChange: LastStatusChangeTimeString, - }, - } + When("max-in-flight value is non-default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyRolling, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonCanceling, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Rolling deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) + }) + + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) }) - It("displays the message", func() { - var actualOut = fmt.Sprintf("%s", testUI.Out) - Expect(actualOut).To(MatchRegexp(`Rolling deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) + When("max-in-flight value is default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyRolling, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonCanceling, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Rolling deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) }) + When("the deployment strategy is canary", func() { When("the deployment is in progress", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonDeploying, - LastStatusChange: LastStatusChangeTimeString, - }, - } + When("max-in-flight value is non-default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonDeploying, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) + }) + + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) }) - It("displays the message", func() { - var actualOut = fmt.Sprintf("%s", testUI.Out) - Expect(actualOut).To(MatchRegexp(`Canary deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) - Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) + 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, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently DEPLOYING \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) When("the deployment is paused", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - ApplicationSummary: v7action.ApplicationSummary{ - Application: resources.Application{ - Name: "foobar", + When("max-in-flight value is non-default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + ApplicationSummary: v7action.ApplicationSummary{ + Application: resources.Application{ + Name: "foobar", + }, }, - }, - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonPaused, - LastStatusChange: LastStatusChangeTimeString, - }, - } - }) + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonPaused, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, + }, + } + }) - It("displays the message", func() { - var actualOut = fmt.Sprintf("%s", testUI.Out) - Expect(actualOut).To(MatchRegexp(`Canary deployment currently PAUSED \(since %s\)`, dateTimeRegexPattern)) - Expect(testUI.Out).To(Say("Please run `cf continue-deployment foobar` to promote the canary deployment, or `cf cancel-deployment foobar` to rollback to the previous version.")) - }) - }) + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently PAUSED \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).To(Say("Please run `cf continue-deployment foobar` to promote the canary deployment, or `cf cancel-deployment foobar` to rollback to the previous version.")) + }) - When("the deployment is canceling", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonCanceling, - LastStatusChange: LastStatusChangeTimeString, - }, - } + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) }) - It("displays the message", func() { - var actualOut = fmt.Sprintf("%s", testUI.Out) - Expect(actualOut).To(MatchRegexp(`Canary deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) - Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) - }) - }) - }) - When("the deployment strategy is canary", func() { - When("the deployment is paused", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - ApplicationSummary: v7action.ApplicationSummary{ - Application: resources.Application{ - Name: "some-app", + When("max-in-flight value is default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + ApplicationSummary: v7action.ApplicationSummary{ + Application: resources.Application{ + Name: "foobar", + }, }, - }, - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonPaused, - }, - } - }) + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonPaused, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) - It("displays the message", func() { - Expect(testUI.Out).To(Say("Canary deployment currently PAUSED.")) - Expect(testUI.Out).To(Say("Please run `cf continue-deployment some-app` to promote the canary deployment, or `cf cancel-deployment some-app` to rollback to the previous version.")) + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently PAUSED \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).To(Say("Please run `cf continue-deployment foobar` to promote the canary deployment, or `cf cancel-deployment foobar` to rollback to the previous version.")) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) - When("the deployment is cancelled", func() { - BeforeEach(func() { - summary = v7action.DetailedApplicationSummary{ - Deployment: resources.Deployment{ - Strategy: constant.DeploymentStrategyCanary, - StatusValue: constant.DeploymentStatusValueActive, - StatusReason: constant.DeploymentStatusReasonCanceling, - }, - } + When("the deployment is canceling", func() { + When("max-in-flight value is non-default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonCanceling, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: 2, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) + }) + + It("displays max-in-flight value", func() { + Expect(testUI.Out).To(Say(`max-in-flight: 2`)) + }) }) - It("displays the message", func() { - Expect(testUI.Out).To(Say("Canary deployment currently CANCELING.")) + When("max-in-flight value is default", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + Deployment: resources.Deployment{ + Strategy: constant.DeploymentStrategyCanary, + StatusValue: constant.DeploymentStatusValueActive, + StatusReason: constant.DeploymentStatusReasonCanceling, + LastStatusChange: LastStatusChangeTimeString, + Options: resources.DeploymentOpts{ + MaxInFlight: maxInFlightDefaultValue, + }, + }, + } + }) + + It("displays the message", func() { + Expect(testUI.Out).To(Say(`Canary deployment currently CANCELING \(since %s\)`, dateTimeRegexPattern)) + Expect(testUI.Out).NotTo(Say(`promote the canary deployment`)) + }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) }) @@ -876,6 +1058,10 @@ var _ = Describe("app summary displayer", func() { cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)), summary.Deployment.StatusReason))) }) + + It("does not display max-in-flight", func() { + Expect(testUI.Out).NotTo(Say(`max-in-flight`)) + }) }) }) }) diff --git a/integration/v7/isolated/app_command_test.go b/integration/v7/isolated/app_command_test.go index 4fe317aa157..d262d96a3c6 100644 --- a/integration/v7/isolated/app_command_test.go +++ b/integration/v7/isolated/app_command_test.go @@ -267,7 +267,7 @@ applications: session := helpers.CF("restart", appName, "--strategy", "rolling") session1 := helpers.CF("app", appName) - Eventually(session1).Should(Say("Rolling deployment currently DEPLOYING.")) + Eventually(session1).Should(Say("Rolling deployment currently DEPLOYING")) Eventually(session).Should(Exit(0)) Eventually(session1).Should(Exit(0)) }) @@ -279,9 +279,11 @@ applications: return helpers.CF("cancel-deployment", appName).Wait() }).Should(Exit(0)) - session2 := helpers.CF("app", appName) - Eventually(session2).Should(Say("Rolling deployment currently CANCELING.")) - Eventually(session2).Should(Exit(0)) + Eventually(func(g Gomega) { + session := helpers.CF("app", appName).Wait() + g.Expect(session).Should(Say("Rolling deployment currently CANCELING")) + g.Expect(session).Should(Exit(0)) + }).Should(Succeed()) }) }) }) @@ -292,19 +294,21 @@ applications: Eventually(helpers.CF("restart", appName, "--strategy", "canary")).Should(Exit(0)) session1 := helpers.CF("app", appName) - Eventually(session1).Should(Say("Canary deployment currently PAUSED.")) + Eventually(session1).Should(Say("Canary deployment currently PAUSED")) Eventually(session1).Should(Exit(0)) }) }) When("the deployment is cancelled after it is paused", func() { - It("no deployment information is displayed", func() { + It("displays the message", func() { Eventually(helpers.CF("restart", appName, "--strategy", "canary")).Should(Exit(0)) Eventually(helpers.CF("cancel-deployment", appName)).Should(Exit(0)) - session2 := helpers.CF("app", appName) - Eventually(session2).ShouldNot(Say("Canary deployment currently CANCELING.")) - Eventually(session2).Should(Exit(0)) + Eventually(func(g Gomega) { + session := helpers.CF("app", appName).Wait() + g.Expect(session).Should(Say("Canary deployment currently CANCELING")) + g.Expect(session).Should(Exit(0)) + }).Should(Succeed()) }) }) })