Skip to content

Commit e1b1dbc

Browse files
Greg Werescha-b
andauthored
View Canary deployment status when using cf app [main] (#3067)
* Show last status change timestamp for an active deployment * Add unit tests for timestamp in app command output * Add a string to inform the user how to promote the canary deployment Co-authored-by: Al Berez <[email protected]>
1 parent bb51f38 commit e1b1dbc

File tree

5 files changed

+146
-22
lines changed

5 files changed

+146
-22
lines changed

api/cloudcontroller/ccv3/constant/deployment.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ const (
4545
// DeploymentStatusReasonSuperseded means the deployment's status.value is
4646
// 'SUPERSEDED'
4747
DeploymentStatusReasonSuperseded DeploymentStatusReason = "SUPERSEDED"
48+
49+
// DeploymentStatusReasonPaused means the deployment's status.value is
50+
// 'PAUSED'
51+
DeploymentStatusReasonPaused DeploymentStatusReason = "PAUSED"
4852
)
4953

5054
// DeploymentStatusValue describes the status values a deployment can have

api/cloudcontroller/ccv3/constant/deployment_strategy.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ const (
99

1010
// Rolling means a new web process will be created for the app and instances will roll from the old one to the new one.
1111
DeploymentStrategyRolling DeploymentStrategy = "rolling"
12+
13+
// Canary means after a web process is created for the app the deployment will pause for evaluation until it is continued or canceled.
14+
DeploymentStrategyCanary DeploymentStrategy = "canary"
1215
)

command/v7/shared/app_summary_displayer.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,26 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed
161161

162162
if summary.Deployment.StatusValue == constant.DeploymentStatusValueActive {
163163
display.UI.DisplayNewline()
164-
display.UI.DisplayText(fmt.Sprintf("%s deployment currently %s.",
164+
display.UI.DisplayText(display.getDeploymentStatusText(summary))
165+
if summary.Deployment.Strategy == constant.DeploymentStrategyCanary && summary.Deployment.StatusReason == constant.DeploymentStatusReasonPaused {
166+
display.UI.DisplayNewline()
167+
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))
168+
}
169+
}
170+
}
171+
172+
func (display AppSummaryDisplayer) getDeploymentStatusText(summary v7action.DetailedApplicationSummary) string {
173+
var lastStatusChangeTime = display.getLastStatusChangeTime(summary)
174+
175+
if lastStatusChangeTime != "" {
176+
return fmt.Sprintf("%s deployment currently %s (since %s)",
177+
cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)),
178+
summary.Deployment.StatusReason,
179+
lastStatusChangeTime)
180+
} else {
181+
return fmt.Sprintf("%s deployment currently %s",
165182
cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)),
166-
summary.Deployment.StatusReason))
183+
summary.Deployment.StatusReason)
167184
}
168185
}
169186

@@ -180,6 +197,19 @@ func (display AppSummaryDisplayer) getCreatedTime(summary v7action.DetailedAppli
180197
return ""
181198
}
182199

200+
func (display AppSummaryDisplayer) getLastStatusChangeTime(summary v7action.DetailedApplicationSummary) string {
201+
if summary.Deployment.LastStatusChange != "" {
202+
timestamp, err := time.Parse(time.RFC3339, summary.Deployment.LastStatusChange)
203+
if err != nil {
204+
log.WithField("last_status_change", summary.Deployment.LastStatusChange).Errorln("error parsing last status change:", err)
205+
}
206+
207+
return display.UI.UserFriendlyDate(timestamp)
208+
}
209+
210+
return ""
211+
}
212+
183213
func (AppSummaryDisplayer) appInstanceDate(input time.Time) string {
184214
return input.UTC().Format(time.RFC3339)
185215
}

command/v7/shared/app_summary_displayer_test.go

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -697,35 +697,117 @@ var _ = Describe("app summary displayer", func() {
697697

698698
When("there is an active deployment", func() {
699699
When("the deployment strategy is rolling", func() {
700+
When("the deployment is in progress", func() {
701+
When("last status change has a timestamp", func() {
702+
BeforeEach(func() {
703+
summary = v7action.DetailedApplicationSummary{
704+
Deployment: resources.Deployment{
705+
Strategy: constant.DeploymentStrategyRolling,
706+
StatusValue: constant.DeploymentStatusValueActive,
707+
StatusReason: constant.DeploymentStatusReasonDeploying,
708+
LastStatusChange: "2024-07-29T17:32:29Z",
709+
},
710+
}
711+
})
712+
713+
It("displays the message", func() {
714+
Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
715+
})
716+
})
717+
718+
When("last status change is an empty string", func() {
719+
BeforeEach(func() {
720+
summary = v7action.DetailedApplicationSummary{
721+
Deployment: resources.Deployment{
722+
Strategy: constant.DeploymentStrategyRolling,
723+
StatusValue: constant.DeploymentStatusValueActive,
724+
StatusReason: constant.DeploymentStatusReasonDeploying,
725+
LastStatusChange: "",
726+
},
727+
}
728+
})
729+
730+
It("displays the message", func() {
731+
Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING\n`))
732+
Expect(testUI.Out).NotTo(Say(`\(since`))
733+
})
734+
})
735+
})
736+
737+
When("the deployment is cancelled", func() {
738+
BeforeEach(func() {
739+
summary = v7action.DetailedApplicationSummary{
740+
Deployment: resources.Deployment{
741+
Strategy: constant.DeploymentStrategyRolling,
742+
StatusValue: constant.DeploymentStatusValueActive,
743+
StatusReason: constant.DeploymentStatusReasonCanceling,
744+
LastStatusChange: "2024-07-29T17:32:29Z",
745+
},
746+
}
747+
})
748+
749+
It("displays the message", func() {
750+
Expect(testUI.Out).To(Say(`Rolling deployment currently CANCELING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
751+
})
752+
})
753+
})
754+
When("the deployment strategy is canary", func() {
700755
When("the deployment is in progress", func() {
701756
BeforeEach(func() {
702757
summary = v7action.DetailedApplicationSummary{
703758
Deployment: resources.Deployment{
704-
Strategy: constant.DeploymentStrategyRolling,
705-
StatusValue: constant.DeploymentStatusValueActive,
706-
StatusReason: constant.DeploymentStatusReasonDeploying,
759+
Strategy: constant.DeploymentStrategyCanary,
760+
StatusValue: constant.DeploymentStatusValueActive,
761+
StatusReason: constant.DeploymentStatusReasonDeploying,
762+
LastStatusChange: "2024-07-29T17:32:29Z",
707763
},
708764
}
709765
})
710766

711767
It("displays the message", func() {
712-
Expect(testUI.Out).To(Say("Rolling deployment currently DEPLOYING."))
768+
Expect(testUI.Out).To(Say(`Canary deployment currently DEPLOYING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
769+
Expect(testUI.Out).NotTo(Say(`promote the canary deployment`))
713770
})
714771
})
715772

716-
When("the deployment is cancelled", func() {
773+
When("the deployment is paused", func() {
774+
BeforeEach(func() {
775+
summary = v7action.DetailedApplicationSummary{
776+
ApplicationSummary: v7action.ApplicationSummary{
777+
Application: resources.Application{
778+
Name: "foobar",
779+
},
780+
},
781+
Deployment: resources.Deployment{
782+
Strategy: constant.DeploymentStrategyCanary,
783+
StatusValue: constant.DeploymentStatusValueActive,
784+
StatusReason: constant.DeploymentStatusReasonPaused,
785+
LastStatusChange: "2024-07-29T17:32:29Z",
786+
},
787+
}
788+
})
789+
790+
It("displays the message", func() {
791+
Expect(testUI.Out).To(Say(`Canary deployment currently PAUSED \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
792+
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."))
793+
})
794+
})
795+
796+
When("the deployment is canceling", func() {
717797
BeforeEach(func() {
718798
summary = v7action.DetailedApplicationSummary{
719799
Deployment: resources.Deployment{
720-
Strategy: constant.DeploymentStrategyRolling,
721-
StatusValue: constant.DeploymentStatusValueActive,
722-
StatusReason: constant.DeploymentStatusReasonCanceling,
800+
Strategy: constant.DeploymentStrategyCanary,
801+
StatusValue: constant.DeploymentStatusValueActive,
802+
StatusReason: constant.DeploymentStatusReasonCanceling,
803+
LastStatusChange: "2024-07-29T17:32:29Z",
723804
},
724805
}
725806
})
726807

727808
It("displays the message", func() {
728-
Expect(testUI.Out).To(Say("Rolling deployment currently CANCELING."))
809+
Expect(testUI.Out).To(Say(`Canary deployment currently CANCELING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
810+
Expect(testUI.Out).NotTo(Say(`promote the canary deployment`))
729811
})
730812
})
731813
})

resources/deployment_resource.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ import (
88
)
99

1010
type Deployment struct {
11-
GUID string
12-
State constant.DeploymentState
13-
StatusValue constant.DeploymentStatusValue
14-
StatusReason constant.DeploymentStatusReason
15-
RevisionGUID string
16-
DropletGUID string
17-
CreatedAt string
18-
UpdatedAt string
19-
Relationships Relationships
20-
NewProcesses []Process
21-
Strategy constant.DeploymentStrategy
11+
GUID string
12+
State constant.DeploymentState
13+
StatusValue constant.DeploymentStatusValue
14+
StatusReason constant.DeploymentStatusReason
15+
LastStatusChange string
16+
RevisionGUID string
17+
DropletGUID string
18+
CreatedAt string
19+
UpdatedAt string
20+
Relationships Relationships
21+
NewProcesses []Process
22+
Strategy constant.DeploymentStrategy
2223
}
2324

2425
// MarshalJSON converts a Deployment into a Cloud Controller Deployment.
@@ -57,6 +58,9 @@ func (d *Deployment) UnmarshalJSON(data []byte) error {
5758
Relationships Relationships `json:"relationships,omitempty"`
5859
State constant.DeploymentState `json:"state,omitempty"`
5960
Status struct {
61+
Details struct {
62+
LastStatusChange string `json:"last_status_change"`
63+
}
6064
Value constant.DeploymentStatusValue `json:"value"`
6165
Reason constant.DeploymentStatusReason `json:"reason"`
6266
} `json:"status"`
@@ -76,6 +80,7 @@ func (d *Deployment) UnmarshalJSON(data []byte) error {
7680
d.State = ccDeployment.State
7781
d.StatusValue = ccDeployment.Status.Value
7882
d.StatusReason = ccDeployment.Status.Reason
83+
d.LastStatusChange = ccDeployment.Status.Details.LastStatusChange
7984
d.DropletGUID = ccDeployment.Droplet.GUID
8085
d.NewProcesses = ccDeployment.NewProcesses
8186
d.Strategy = ccDeployment.Strategy

0 commit comments

Comments
 (0)