Skip to content

Commit

Permalink
View Canary deployment status when using cf app [v8] (#3066)
Browse files Browse the repository at this point in the history
* 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 <al.berez@broadcom.com>
  • Loading branch information
weresch and a-b authored Jul 31, 2024
1 parent e22adbf commit 6e0a003
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 22 deletions.
4 changes: 4 additions & 0 deletions api/cloudcontroller/ccv3/constant/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const (
// DeploymentStatusReasonSuperseded means the deployment's status.value is
// 'SUPERSEDED'
DeploymentStatusReasonSuperseded DeploymentStatusReason = "SUPERSEDED"

// DeploymentStatusReasonPaused means the deployment's status.value is
// 'PAUSED'
DeploymentStatusReasonPaused DeploymentStatusReason = "PAUSED"
)

// DeploymentStatusValue describes the status values a deployment can have
Expand Down
3 changes: 3 additions & 0 deletions api/cloudcontroller/ccv3/constant/deployment_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ const (

// Rolling means a new web process will be created for the app and instances will roll from the old one to the new one.
DeploymentStrategyRolling DeploymentStrategy = "rolling"

// Canary means after a web process is created for the app the deployment will pause for evaluation until it is continued or canceled.
DeploymentStrategyCanary DeploymentStrategy = "canary"
)
34 changes: 32 additions & 2 deletions command/v7/shared/app_summary_displayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,26 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed

if summary.Deployment.StatusValue == constant.DeploymentStatusValueActive {
display.UI.DisplayNewline()
display.UI.DisplayText(fmt.Sprintf("%s deployment currently %s.",
display.UI.DisplayText(display.getDeploymentStatusText(summary))
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))
}
}
}

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 {
return fmt.Sprintf("%s deployment currently %s",
cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)),
summary.Deployment.StatusReason))
summary.Deployment.StatusReason)
}
}

Expand All @@ -183,6 +200,19 @@ func (display AppSummaryDisplayer) getCreatedTime(summary v7action.DetailedAppli
return ""
}

func (display AppSummaryDisplayer) getLastStatusChangeTime(summary v7action.DetailedApplicationSummary) string {
if summary.Deployment.LastStatusChange != "" {
timestamp, err := time.Parse(time.RFC3339, summary.Deployment.LastStatusChange)
if err != nil {
log.WithField("last_status_change", summary.Deployment.LastStatusChange).Errorln("error parsing last status change:", err)
}

return display.UI.UserFriendlyDate(timestamp)
}

return ""
}

func (AppSummaryDisplayer) appInstanceDate(input time.Time) string {
return input.UTC().Format(time.RFC3339)
}
Expand Down
100 changes: 91 additions & 9 deletions command/v7/shared/app_summary_displayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,35 +658,117 @@ var _ = Describe("app summary displayer", func() {

When("there is an active deployment", func() {
When("the deployment strategy is rolling", func() {
When("the deployment is in progress", func() {
When("last status change has a timestamp", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Strategy: constant.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonDeploying,
LastStatusChange: "2024-07-29T17:32:29Z",
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
})
})

When("last status change is an empty string", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Strategy: constant.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonDeploying,
LastStatusChange: "",
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say(`Rolling deployment currently DEPLOYING\n`))
Expect(testUI.Out).NotTo(Say(`\(since`))
})
})
})

When("the deployment is cancelled", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Strategy: constant.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonCanceling,
LastStatusChange: "2024-07-29T17:32:29Z",
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say(`Rolling deployment currently CANCELING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
})
})
})
When("the deployment strategy is canary", func() {
When("the deployment is in progress", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Strategy: constant.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonDeploying,
Strategy: constant.DeploymentStrategyCanary,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonDeploying,
LastStatusChange: "2024-07-29T17:32:29Z",
},
}
})

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

When("the deployment is cancelled", func() {
When("the deployment is paused", 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: "2024-07-29T17:32:29Z",
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say(`Canary deployment currently PAUSED \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
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.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonCanceling,
Strategy: constant.DeploymentStrategyCanary,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonCanceling,
LastStatusChange: "2024-07-29T17:32:29Z",
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say("Rolling deployment currently CANCELING."))
Expect(testUI.Out).To(Say(`Canary deployment currently CANCELING \(since Mon 29 Jul 13:32:29 EDT 2024\)`))
Expect(testUI.Out).NotTo(Say(`promote the canary deployment`))
})
})
})
Expand Down
27 changes: 16 additions & 11 deletions resources/deployment_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import (
)

type Deployment struct {
GUID string
State constant.DeploymentState
StatusValue constant.DeploymentStatusValue
StatusReason constant.DeploymentStatusReason
RevisionGUID string
DropletGUID string
CreatedAt string
UpdatedAt string
Relationships Relationships
NewProcesses []Process
Strategy constant.DeploymentStrategy
GUID string
State constant.DeploymentState
StatusValue constant.DeploymentStatusValue
StatusReason constant.DeploymentStatusReason
LastStatusChange string
RevisionGUID string
DropletGUID string
CreatedAt string
UpdatedAt string
Relationships Relationships
NewProcesses []Process
Strategy constant.DeploymentStrategy
}

// MarshalJSON converts a Deployment into a Cloud Controller Deployment.
Expand Down Expand Up @@ -57,6 +58,9 @@ func (d *Deployment) UnmarshalJSON(data []byte) error {
Relationships Relationships `json:"relationships,omitempty"`
State constant.DeploymentState `json:"state,omitempty"`
Status struct {
Details struct {
LastStatusChange string `json:"last_status_change"`
}
Value constant.DeploymentStatusValue `json:"value"`
Reason constant.DeploymentStatusReason `json:"reason"`
} `json:"status"`
Expand All @@ -76,6 +80,7 @@ func (d *Deployment) UnmarshalJSON(data []byte) error {
d.State = ccDeployment.State
d.StatusValue = ccDeployment.Status.Value
d.StatusReason = ccDeployment.Status.Reason
d.LastStatusChange = ccDeployment.Status.Details.LastStatusChange
d.DropletGUID = ccDeployment.Droplet.GUID
d.NewProcesses = ccDeployment.NewProcesses
d.Strategy = ccDeployment.Strategy
Expand Down

0 comments on commit 6e0a003

Please sign in to comment.