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

Display deployment status for cf app command [v8] #3041

Merged
merged 7 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions .github/ops-files/replace-redis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- type: replace
path: /instance_groups/name=api/jobs/name=redis?
value:
name: valkey
release: capi
1 change: 1 addition & 0 deletions .github/workflows/tests-integration-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ jobs:
bosh -d cf manifest > /tmp/manifest.yml
bosh interpolate /tmp/manifest.yml \
-o .github/ops-files/use-cflinuxfs3.yml \
-o .github/ops-files/replace-redis.yml \
-o cf-deployment/operations/use-internal-lookup-for-route-services.yml \
-o cf-deployment/operations/add-persistent-isolation-segment-diego-cell.yml \
-o cli-ci/ci/infrastructure/operations/use-latest-capi.yml \
Expand Down
22 changes: 22 additions & 0 deletions actor/v7action/application_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ApplicationSummary struct {
type DetailedApplicationSummary struct {
ApplicationSummary
CurrentDroplet resources.Droplet
Deployment resources.Deployment
}

func (a ApplicationSummary) GetIsolationSegmentName() (string, bool) {
Expand Down Expand Up @@ -120,6 +121,12 @@ func (actor Actor) GetDetailedAppSummary(appName, spaceGUID string, withObfuscat
return DetailedApplicationSummary{}, allWarnings, err
}

detailedSummary, warnings, err = actor.addDeployment(detailedSummary)
allWarnings = append(allWarnings, warnings...)
if err != nil {
return DetailedApplicationSummary{}, allWarnings, err
}

return detailedSummary, allWarnings, err
}

Expand Down Expand Up @@ -206,6 +213,21 @@ func (actor Actor) addDroplet(summary ApplicationSummary) (DetailedApplicationSu
}, allWarnings, nil
}

func (actor Actor) addDeployment(detailedSummary DetailedApplicationSummary) (DetailedApplicationSummary, Warnings, error) {
var allWarnings Warnings

deployment, warnings, err := actor.GetLatestActiveDeploymentForApp(detailedSummary.GUID)
allWarnings = append(allWarnings, warnings...)
if err != nil {
if _, ok := err.(actionerror.ActiveDeploymentNotFoundError); !ok {
Samze marked this conversation as resolved.
Show resolved Hide resolved
return DetailedApplicationSummary{}, allWarnings, err
}
}

detailedSummary.Deployment = deployment
return detailedSummary, allWarnings, nil
}

func toAppGUIDs(apps []resources.Application) []string {
guids := make([]string, len(apps))

Expand Down
114 changes: 111 additions & 3 deletions actor/v7action/application_summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"fmt"

"code.cloudfoundry.org/cli/actor/v7action"
. "code.cloudfoundry.org/cli/actor/v7action"
"code.cloudfoundry.org/cli/actor/v7action/v7actionfakes"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
Expand Down Expand Up @@ -574,6 +573,115 @@ var _ = Describe("Application Summary Actions", func() {
)
})

When("getting application deployment succeeds", func() {
When("the deployment is active", func() {
When("the deployment strategy is rolling", func() {
When("the deployment is in progress", func() {
BeforeEach(func() {
fakeCloudControllerClient.GetDeploymentsReturns(
[]resources.Deployment{
{
GUID: "some-deployment-guid",
Strategy: "rolling",
StatusValue: "ACTIVE",
StatusReason: "DEPLOYING",
},
},
nil,
nil,
)
})
It("returns the deployment information", func() {
Expect(summary.Deployment).To(Equal(resources.Deployment{
GUID: "some-deployment-guid",
Strategy: "rolling",
StatusValue: "ACTIVE",
StatusReason: "DEPLOYING",
}))
})
})

When("the deployment is canceled", func() {
When("the deployment is in progress", func() {
BeforeEach(func() {
fakeCloudControllerClient.GetDeploymentsReturns(
[]resources.Deployment{
{
GUID: "some-deployment-guid",
Strategy: "rolling",
StatusValue: "ACTIVE",
StatusReason: "CANCELLING",
},
},
nil,
nil,
)
})
It("returns the deployment information", func() {
Expect(summary.Deployment).To(Equal(resources.Deployment{
GUID: "some-deployment-guid",
Strategy: "rolling",
StatusValue: "ACTIVE",
StatusReason: "CANCELLING",
}))
})
})
})
})
})

When("the deployment is finalized", func() {
BeforeEach(func() {
fakeCloudControllerClient.GetDeploymentsReturns(
[]resources.Deployment{
gururajsh marked this conversation as resolved.
Show resolved Hide resolved
{
GUID: "",
Strategy: "",
StatusValue: "",
StatusReason: "",
},
},
nil,
nil,
)
})
It("returns no deployment information", func() {
Expect(summary.Deployment).To(Equal(resources.Deployment{
GUID: "",
Strategy: "",
StatusValue: "",
StatusReason: "",
}))
})
})
})

When("getting application deployment fails", func() {
BeforeEach(func() {
fakeCloudControllerClient.GetDeploymentsReturns(
nil,
ccv3.Warnings{"get-deployments-warning"},
errors.New("some-error"),
)
})

It("returns the warnings and error", func() {
Expect(executeErr).To(MatchError("some-error"))
Expect(warnings).To(ConsistOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QUESTION

Are all of these warnings relevant to this scenario? If not, maybe the ContainElement matcher is more appropriate — otherwise this assertion will need to be updated any time any of the other, unrelated warnings are changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is because of when the test is happening (after all other function stubs are invoked) as we saw earlier.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that these warnings are all returned in this scenario. But IMHO they're not all relevant to this scenario, and therefore should probably not be enumerated.

If some logic changes for one of the other warnings we'd need to rewrite this assertion to match. The test becomes more robust — and more expressive — by limiting the assertion to checking for just the presence of the single, relevant warning and not the exhaustive contents of the full array.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. We discussed this while working on these tests and moved it around to suit the process when we were pairing. If something changes for these warning, most of the tests in this will need to change.

"get-apps-warning",
"get-app-processes-warning",
"get-process-by-type-warning",
"get-process-sidecars-warning",
"get-process-instances-warning",
"get-process-by-type-warning",
"get-process-sidecars-warning",
"get-process-instances-warning",
"get-app-droplet-warning",
"get-deployments-warning",
))
})
})

When("getting application routes succeeds", func() {
BeforeEach(func() {
fakeCloudControllerClient.GetApplicationRoutesReturns(
Expand All @@ -589,7 +697,7 @@ var _ = Describe("Application Summary Actions", func() {
It("returns the summary and warnings with droplet information", func() {
Expect(executeErr).ToNot(HaveOccurred())
Expect(summary).To(Equal(DetailedApplicationSummary{
ApplicationSummary: v7action.ApplicationSummary{
ApplicationSummary: ApplicationSummary{
Application: resources.Application{
Name: "some-app-name",
GUID: "some-app-guid",
Expand Down Expand Up @@ -733,7 +841,7 @@ var _ = Describe("Application Summary Actions", func() {
It("returns the summary and warnings without droplet information", func() {
Expect(executeErr).ToNot(HaveOccurred())
Expect(summary).To(Equal(DetailedApplicationSummary{
ApplicationSummary: v7action.ApplicationSummary{
ApplicationSummary: ApplicationSummary{
Application: resources.Application{
Name: "some-app-name",
GUID: "some-app-guid",
Expand Down
6 changes: 6 additions & 0 deletions api/cloudcontroller/ccv3/constant/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ const (
type DeploymentStatusReason string

const (
// DeploymentStatusReasonDeploying means the deployment is in state 'DEPLOYING'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION

These comments don't really add much information on top of the names. Just delete them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with it. Just placed them for consistency sake.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would actually suggest deleting all the other ones, then. None of them add any information beyond what the identifier itself does.

DeploymentStatusReasonDeploying DeploymentStatusReason = "DEPLOYING"

// DeploymentCanceling means the deployment is in state 'CANCELING'
DeploymentStatusReasonCanceling DeploymentStatusReason = "CANCELING"

// DeploymentStatusReasonDeployed means the deployment's status.value is
// 'DEPLOYED'
DeploymentStatusReasonDeployed DeploymentStatusReason = "DEPLOYED"
Expand Down
8 changes: 8 additions & 0 deletions command/v7/shared/app_summary_displayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"code.cloudfoundry.org/cli/types"
"code.cloudfoundry.org/cli/util/ui"
log "github.com/sirupsen/logrus"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

type AppSummaryDisplayer struct {
Expand Down Expand Up @@ -159,6 +161,12 @@ func (display AppSummaryDisplayer) displayProcessTable(summary v7action.Detailed
}
display.displayAppInstancesTable(process)
}

if summary.Deployment.StatusValue == constant.DeploymentStatusValueActive {
Samze marked this conversation as resolved.
Show resolved Hide resolved
display.UI.DisplayText(fmt.Sprintf("%s deployment currently %s",
cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)),
summary.Deployment.StatusReason))
}
}

func (display AppSummaryDisplayer) getCreatedTime(summary v7action.DetailedApplicationSummary) string {
Expand Down
57 changes: 57 additions & 0 deletions command/v7/shared/app_summary_displayer_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package shared_test

import (
"fmt"
"time"

"code.cloudfoundry.org/cli/actor/v7action"
Expand All @@ -13,6 +14,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gbytes"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

var _ = Describe("app summary displayer", func() {
Expand Down Expand Up @@ -652,5 +655,59 @@ var _ = Describe("app summary displayer", func() {
Expect(testUI.Out).To(Say(`some-buildpack`))
})
})

When("there is an active deployment", func() {
When("the deployment strategy is rolling", 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,
},
}
})

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

When("the deployment is cancelled", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Strategy: constant.DeploymentStrategyRolling,
StatusValue: constant.DeploymentStatusValueActive,
StatusReason: constant.DeploymentStatusReasonCanceling,
},
}
})

It("displays the message", func() {
Expect(testUI.Out).To(Say("Rolling deployment currently CANCELING"))
})
})
})
})

When("there is no active deployment", func() {
BeforeEach(func() {
summary = v7action.DetailedApplicationSummary{
Deployment: resources.Deployment{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QUESTION

Same as above:

  1. Does this represent an active Deployment? What does that actually look like when it comes over the wire?
  2. Is this even a valid state for the Deployment DTO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same response as above. #3041 (comment)

Strategy: "",
StatusValue: "",
StatusReason: "",
},
}
})

It("does not display deployment info", func() {
Expect(testUI.Out).NotTo(Say(fmt.Sprintf("%s deployment currently %s",
cases.Title(language.English, cases.NoLower).String(string(summary.Deployment.Strategy)),
summary.Deployment.StatusReason)))
})
})
})
})
48 changes: 48 additions & 0 deletions integration/v7/isolated/app_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path"
"path/filepath"
"time"

. "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers"
"code.cloudfoundry.org/cli/integration/helpers"
Expand Down Expand Up @@ -253,6 +254,53 @@ applications:
Eventually(session).Should(Exit(0))
})
})

When("there is an active deployment", func() {
BeforeEach(func() {
helpers.WithHelloWorldApp(func(appDir string) {
Eventually(helpers.CF("push", appName, "-p", appDir, "-b", "staticfile_buildpack")).Should(Exit(0))
})
})

When("the deployment strategy is rolling", func() {
When("the deployment is in progress", func() {
It("displays the message", func() {
session := helpers.CF("restart", appName, "--strategy", "rolling")

session1 := helpers.CF("app", appName)
Eventually(session1).Should(Say("Rolling deployment currently DEPLOYING"))
Eventually(session).Should(Exit(0))
Eventually(session1).Should(Exit(0))
})
})
When("the deployment is cancelled", func() {
It("displays the message", func() {
helpers.CF("restart", appName, "--strategy", "rolling")
time.Sleep(3 * time.Second)
Samze marked this conversation as resolved.
Show resolved Hide resolved
session1 := helpers.CF("cancel-deployment", appName)
Eventually(session1).Should(Exit(0))

session2 := helpers.CF("app", appName)
Eventually(session2).Should(Say("Rolling deployment currently CANCELING"))
Eventually(session2).Should(Exit(0))
})
})
})
})

When("there is no active deployment", func() {
BeforeEach(func() {
helpers.WithHelloWorldApp(func(appDir string) {
Eventually(helpers.CF("push", appName, "-p", appDir, "-b", "staticfile_buildpack")).Should(Exit(0))
})
})

It("doesn not display the message", func() {
gururajsh marked this conversation as resolved.
Show resolved Hide resolved
session := helpers.CF("app", appName)
Eventually(session).Should(Exit(0))
Eventually(session).ShouldNot(Say(`\w+ deployment currently \w+`))
})
})
})

Describe("version independent display", func() {
Expand Down
Loading
Loading