Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View Canary deployment status when using cf app [v8] #3066

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading