diff --git a/.github/workflows/release-update-repos.yml b/.github/workflows/release-update-repos.yml index 65c56a1045..b6a0569342 100644 --- a/.github/workflows/release-update-repos.yml +++ b/.github/workflows/release-update-repos.yml @@ -310,7 +310,6 @@ jobs: image: - debian:11 - debian:12 - - ubuntu:20.04 - ubuntu:22.04 - ubuntu runs-on: ubuntu-latest @@ -602,6 +601,7 @@ jobs: with: context: docker load: true + no-cache: true tags: ${{ env.VERSION_BUILD }} - name: Test docker container @@ -632,31 +632,12 @@ jobs: docker run -v $(pwd):/clidir -w /clidir \ --rm ${{ env.VERSION_BUILD }} sh test.sh - # If bash reaches this point, it means that tests passed, setting latest - if [[ ( ${{ github.ref_name }} == "v8" ) ]]; then - echo "LATEST_DOCKER=v8" >> "$GITHUB_ENV" - fi - - if [[ ( ${{ github.ref_name }} == "v7" ) ]]; then - echo "LATEST_DOCKER=v7" >> "$GITHUB_ENV" - fi - rm test.sh - - name: Push image if its from v8 - if: ${{ env.LATEST_DOCKER }} == v8 + - name: Push image uses: docker/build-push-action@v6 with: context: docker push: true tags: cloudfoundry/cli:${{ env.VERSION_BUILD }}, cloudfoundry/cli:latest - - - name: Push image if its from v7 - if: ${{ env.LATEST_DOCKER }} == v7 - uses: docker/build-push-action@v6 - with: - context: docker - push: true - tags: cloudfoundry/cli:${{ env.VERSION_BUILD }} - -# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: + \ No newline at end of file diff --git a/api/cloudcontroller/ccversion/minimum_version.go b/api/cloudcontroller/ccversion/minimum_version.go index fcee5cdbdc..68f6d37a1e 100644 --- a/api/cloudcontroller/ccversion/minimum_version.go +++ b/api/cloudcontroller/ccversion/minimum_version.go @@ -18,4 +18,6 @@ const ( MinVersionCNB = "3.168.0" MinVersionPerRouteOpts = "3.183.0" + + MinVersionCanarySteps = "3.189.0" ) diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index c4ae96eeef..c7406524b5 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -1,13 +1,18 @@ package v7 import ( + "strconv" + "strings" + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" + "code.cloudfoundry.org/cli/resources" "code.cloudfoundry.org/cli/util/configv3" ) @@ -16,12 +21,13 @@ type CopySourceCommand struct { RequiredArgs flag.CopySourceArgs `positional-args:"yes"` usage interface{} `usage:"CF_NAME copy-source SOURCE_APP DESTINATION_APP [-s TARGET_SPACE [-o TARGET_ORG]] [--no-restart] [--strategy STRATEGY] [--no-wait]"` - Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null"` + InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"` MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being started. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` NoRestart bool `long:"no-restart" description:"Do not restage the destination application"` Organization string `short:"o" long:"organization" description:"Org that contains the destination application"` Space string `short:"s" long:"space" description:"Space that contains the destination application"` + Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null"` relatedCommands interface{} `related_commands:"apps, push, restage, restart, target"` envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"` envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` @@ -57,6 +63,18 @@ func (cmd *CopySourceCommand) ValidateFlags() error { return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} } + if cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "" { + return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"} + } + + if len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps) { + return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"} + } + + if len(cmd.InstanceSteps) > 0 { + return command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionCanarySteps, "--instance-steps") + } + return nil } @@ -178,6 +196,18 @@ func (cmd CopySourceCommand) Execute(args []string) error { opts.MaxInFlight = *cmd.MaxInFlight } + if cmd.InstanceSteps != "" { + if len(cmd.InstanceSteps) > 0 { + for _, v := range strings.Split(cmd.InstanceSteps, ",") { + parsedInt, err := strconv.ParseInt(v, 0, 64) + if err != nil { + return err + } + opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt}) + } + } + } + err = cmd.Stager.StageAndStart(targetApp, targetSpace, targetOrg, pkg.GUID, opts) if err != nil { return mapErr(cmd.Config, targetApp.Name, err) diff --git a/command/v7/copy_source_command_test.go b/command/v7/copy_source_command_test.go index 9d3787dc6f..b24d5f2340 100644 --- a/command/v7/copy_source_command_test.go +++ b/command/v7/copy_source_command_test.go @@ -324,6 +324,29 @@ var _ = Describe("copy-source Command", func() { Expect(opts.NoWait).To(Equal(false)) Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) }) + + When("instance steps is provided", func() { + BeforeEach(func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "1,2,4" + + fakeConfig.APIVersionReturns("3.999.0") + }) + + It("starts the new app", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StageAndStartArgsForCall(0) + Expect(inputApp).To(Equal(targetApp)) + Expect(inputDropletGuid).To(Equal("target-package-guid")) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}})) + }) + }) }) When("the no-wait flag is set", func() { @@ -440,5 +463,38 @@ var _ = Describe("copy-source Command", func() { translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", }), + + Entry("instance-steps no strategy provided", + func() { + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps a valid list of ints", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "some,thing,not,right" + }, + translatableerror.ParseArgumentError{ + ArgumentName: "--instance-steps", + ExpectedType: "list of weights", + }), + + Entry("instance-steps used when CAPI does not support canary steps", + func() { + cmd.InstanceSteps = "1,2,3" + cmd.Strategy.Name = constant.DeploymentStrategyCanary + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("3.0.0") + cmd.Config = fakeConfig + }, + translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--instance-steps", + CurrentVersion: "3.0.0", + MinimumVersion: "3.189.0", + }), ) }) diff --git a/command/v7/push_command.go b/command/v7/push_command.go index c8c16fd367..869a310197 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -580,15 +580,19 @@ func (cmd PushCommand) ValidateFlags() error { return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} case len(cmd.InstanceSteps) > 0 && cmd.Strategy.Name != constant.DeploymentStrategyCanary: return translatableerror.ArgumentCombinationError{Args: []string{"--instance-steps", "--strategy=rolling or --strategy not provided"}} - case len(cmd.InstanceSteps) > 0 && !cmd.validateInstanceSteps(): + case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps): return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"} } + if len(cmd.InstanceSteps) > 0 { + return command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionCanarySteps, "--instance-steps") + } + return nil } -func (cmd PushCommand) validateInstanceSteps() bool { - for _, v := range strings.Split(cmd.InstanceSteps, ",") { +func validateInstanceSteps(instanceSteps string) bool { + for _, v := range strings.Split(instanceSteps, ",") { _, err := strconv.ParseInt(v, 0, 64) if err != nil { return false diff --git a/command/v7/push_command_test.go b/command/v7/push_command_test.go index a8866d24c6..8dc629c232 100644 --- a/command/v7/push_command_test.go +++ b/command/v7/push_command_test.go @@ -628,6 +628,9 @@ var _ = Describe("push Command", func() { When("canary strategy is provided", func() { BeforeEach(func() { cmd.Strategy = flag.DeploymentStrategy{Name: "canary"} + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("4.0.0") + cmd.Config = fakeConfig }) It("should succeed", func() { @@ -1440,5 +1443,19 @@ var _ = Describe("push Command", func() { Args: []string{ "--instance-steps", "--strategy=rolling or --strategy not provided", }}), + + Entry("instance-steps used when CAPI does not support canary steps", + func() { + cmd.InstanceSteps = "1,2,3" + cmd.Strategy.Name = constant.DeploymentStrategyCanary + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("3.0.0") + cmd.Config = fakeConfig + }, + translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--instance-steps", + CurrentVersion: "3.0.0", + MinimumVersion: "3.189.0", + }), ) }) diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index 6fa503b2d8..27ff08ec87 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -1,23 +1,29 @@ package v7 import ( + "strconv" + "strings" + "code.cloudfoundry.org/cli/actor/actionerror" "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" + "code.cloudfoundry.org/cli/resources" ) type RestageCommand struct { BaseCommand RequiredArgs flag.AppName `positional-args:"yes"` - Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` + InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"` MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being restaged. Only applies when --strategy flag is specified."` NoWait bool `long:"no-wait" description:"Exit when the first instance of the web process is healthy"` + Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` usage interface{} `usage:"CF_NAME restage APP_NAME\n\n This command will cause downtime unless you use '--strategy' flag.\n\nEXAMPLES:\n CF_NAME restage APP_NAME\n CF_NAME restage APP_NAME --strategy rolling\n CF_NAME restage APP_NAME --strategy canary --no-wait"` relatedCommands interface{} `related_commands:"restart"` envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"` @@ -93,6 +99,18 @@ func (cmd RestageCommand) Execute(args []string) error { opts.MaxInFlight = *cmd.MaxInFlight } + if cmd.InstanceSteps != "" { + if len(cmd.InstanceSteps) > 0 { + for _, v := range strings.Split(cmd.InstanceSteps, ",") { + parsedInt, err := strconv.ParseInt(v, 0, 64) + if err != nil { + return err + } + opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt}) + } + } + } + err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), pkg.GUID, opts) if err != nil { return mapErr(cmd.Config, cmd.RequiredArgs.AppName, err) @@ -107,6 +125,14 @@ func (cmd RestageCommand) ValidateFlags() error { return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + case cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "": + return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"} + case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps): + return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"} + } + + if len(cmd.InstanceSteps) > 0 { + return command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionCanarySteps, "--instance-steps") } return nil diff --git a/command/v7/restage_command_test.go b/command/v7/restage_command_test.go index 8cddad4ca5..c48727376e 100644 --- a/command/v7/restage_command_test.go +++ b/command/v7/restage_command_test.go @@ -120,6 +120,30 @@ var _ = Describe("restage Command", func() { }) }) + When("canary strategy is provided", func() { + BeforeEach(func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "1,2,4" + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("4.0.0") + cmd.Config = fakeConfig + }) + + It("starts the app with the current droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StageAndStartArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputDropletGuid).To(Equal("earliest-package-guid")) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}})) + }) + }) + It("displays that it's restaging", func() { Expect(testUI.Out).To(Say("Restaging app some-app in org some-org / space some-space as steve...")) }) @@ -226,5 +250,48 @@ var _ = Describe("restage Command", func() { translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", }), + + Entry("instance-steps provided with rolling deployment", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps no strategy provided", + func() { + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps a valid list of ints", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "some,thing,not,right" + }, + translatableerror.ParseArgumentError{ + ArgumentName: "--instance-steps", + ExpectedType: "list of weights", + }), + + Entry("instance-steps used when CAPI does not support canary steps", + func() { + cmd.InstanceSteps = "1,2,3" + cmd.Strategy.Name = constant.DeploymentStrategyCanary + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("3.0.0") + cmd.Config = fakeConfig + }, + translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--instance-steps", + CurrentVersion: "3.0.0", + MinimumVersion: "3.189.0", + }), ) }) diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index dfbe9c5e4a..847c2f316f 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -1,18 +1,24 @@ package v7 import ( + "strconv" + "strings" + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" + "code.cloudfoundry.org/cli/resources" ) type RestartCommand struct { BaseCommand + InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"` MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively restarted at any given time. Only applies when --strategy flag is specified."` RequiredArgs flag.AppName `positional-args:"yes"` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary, rolling or null."` @@ -89,6 +95,18 @@ func (cmd RestartCommand) Execute(args []string) error { opts.MaxInFlight = *cmd.MaxInFlight } + if cmd.InstanceSteps != "" { + if len(cmd.InstanceSteps) > 0 { + for _, v := range strings.Split(cmd.InstanceSteps, ",") { + parsedInt, err := strconv.ParseInt(v, 0, 64) + if err != nil { + return err + } + opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt}) + } + } + } + if packageGUID != "" { err = cmd.Stager.StageAndStart(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), packageGUID, opts) if err != nil { @@ -110,6 +128,14 @@ func (cmd RestartCommand) ValidateFlags() error { return translatableerror.RequiredFlagsError{Arg1: "--max-in-flight", Arg2: "--strategy"} case cmd.Strategy.Name != constant.DeploymentStrategyDefault && cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + case cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "": + return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"} + case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps): + return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"} + } + + if len(cmd.InstanceSteps) > 0 { + return command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionCanarySteps, "--instance-steps") } return nil diff --git a/command/v7/restart_command_test.go b/command/v7/restart_command_test.go index 4d4f8e828b..1d409709c4 100644 --- a/command/v7/restart_command_test.go +++ b/command/v7/restart_command_test.go @@ -133,18 +133,46 @@ var _ = Describe("restart Command", func() { fakeActor.GetUnstagedNewestPackageGUIDReturns("package-guid", v7action.Warnings{}, nil) }) - It("stages the new package and starts the app with the new droplet", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1)) + When("no strategy is provided", func() { + It("stages the new package and starts the app with the new droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputPkgGUID, opts := fakeAppStager.StageAndStartArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(inputPkgGUID).To(Equal("package-guid")) + Expect(opts.Strategy).To(Equal(strategy)) + Expect(opts.NoWait).To(Equal(noWait)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(HaveLen(0)) + }) + }) + + When("canary strategy is provided", func() { + BeforeEach(func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "1,2,4" + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("4.0.0") + cmd.Config = fakeConfig + }) - inputApp, inputSpace, inputOrg, inputPkgGUID, opts := fakeAppStager.StageAndStartArgsForCall(0) - Expect(inputApp).To(Equal(app)) - Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) - Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) - Expect(inputPkgGUID).To(Equal("package-guid")) - Expect(opts.Strategy).To(Equal(strategy)) - Expect(opts.NoWait).To(Equal(noWait)) - Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + It("starts the app with the current droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StageAndStartCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputPkgGUID, opts := fakeAppStager.StageAndStartArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(inputPkgGUID).To(Equal("package-guid")) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(opts.NoWait).To(Equal(noWait)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}})) + }) }) Context("staging and starting the app returns an error", func() { @@ -163,18 +191,47 @@ var _ = Describe("restart Command", func() { fakeActor.GetUnstagedNewestPackageGUIDReturns("", v7action.Warnings{}, nil) }) - It("starts the app with the current droplet", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeAppStager.StartAppCallCount()).To(Equal(1)) + When("no strategy is provided", func() { + It("starts the app with the current droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StartAppCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StartAppArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputDropletGuid).To(Equal("")) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(opts.Strategy).To(Equal(strategy)) + Expect(opts.NoWait).To(Equal(noWait)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(HaveLen(0)) + }) + }) + + When("canary strategy is provided", func() { + BeforeEach(func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "1,2,4" + + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("4.0.0") + cmd.Config = fakeConfig + }) - inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StartAppArgsForCall(0) - Expect(inputApp).To(Equal(app)) - Expect(inputDropletGuid).To(Equal("")) - Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) - Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) - Expect(opts.Strategy).To(Equal(strategy)) - Expect(opts.NoWait).To(Equal(noWait)) - Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + It("starts the app with the current droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StartAppCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StartAppArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputDropletGuid).To(Equal("")) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(opts.NoWait).To(Equal(noWait)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRestarting)) + Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}})) + }) }) When("starting the app returns an error", func() { @@ -218,5 +275,48 @@ var _ = Describe("restart Command", func() { translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", }), + + Entry("instance-steps provided with rolling deployment", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling} + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps no strategy provided", + func() { + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps a valid list of ints", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "some,thing,not,right" + }, + translatableerror.ParseArgumentError{ + ArgumentName: "--instance-steps", + ExpectedType: "list of weights", + }), + + Entry("instance-steps used when CAPI does not support canary steps", + func() { + cmd.InstanceSteps = "1,2,3" + cmd.Strategy.Name = constant.DeploymentStrategyCanary + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("3.0.0") + cmd.Config = fakeConfig + }, + translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--instance-steps", + CurrentVersion: "3.0.0", + MinimumVersion: "3.189.0", + }), ) }) diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index 477b75bdee..17e5d7b496 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -2,21 +2,26 @@ package v7 import ( "fmt" + "strconv" + "strings" "code.cloudfoundry.org/cli/actor/sharedaction" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/cf/errors" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" + "code.cloudfoundry.org/cli/resources" ) type RollbackCommand struct { BaseCommand - Force bool `short:"f" description:"Force rollback without confirmation"` RequiredArgs flag.AppName `positional-args:"yes"` + Force bool `short:"f" description:"Force rollback without confirmation"` + InstanceSteps string `long:"instance-steps" description:"An array of percentage steps to deploy when using deployment strategy canary. (e.g. 20,40,60)"` MaxInFlight *int `long:"max-in-flight" description:"Defines the maximum number of instances that will be actively being rolled back."` Strategy flag.DeploymentStrategy `long:"strategy" description:"Deployment strategy can be canary or rolling. When not specified, it defaults to rolling."` Version flag.Revision `long:"version" required:"true" description:"Roll back to the specified revision"` @@ -120,6 +125,18 @@ func (cmd RollbackCommand) Execute(args []string) error { opts.Strategy = cmd.Strategy.Name } + if cmd.InstanceSteps != "" { + if len(cmd.InstanceSteps) > 0 { + for _, v := range strings.Split(cmd.InstanceSteps, ",") { + parsedInt, err := strconv.ParseInt(v, 0, 64) + if err != nil { + return err + } + opts.CanarySteps = append(opts.CanarySteps, resources.CanaryStep{InstanceWeight: parsedInt}) + } + } + } + startAppErr := cmd.Stager.StartApp(app, cmd.Config.TargetedSpace(), cmd.Config.TargetedOrganization(), revision.GUID, opts) if startAppErr != nil { return startAppErr @@ -134,6 +151,14 @@ func (cmd RollbackCommand) ValidateFlags() error { switch { case cmd.MaxInFlight != nil && *cmd.MaxInFlight < 1: return translatableerror.IncorrectUsageError{Message: "--max-in-flight must be greater than or equal to 1"} + case cmd.Strategy.Name != constant.DeploymentStrategyCanary && cmd.InstanceSteps != "": + return translatableerror.RequiredFlagsError{Arg1: "--instance-steps", Arg2: "--strategy=canary"} + case len(cmd.InstanceSteps) > 0 && !validateInstanceSteps(cmd.InstanceSteps): + return translatableerror.ParseArgumentError{ArgumentName: "--instance-steps", ExpectedType: "list of weights"} + } + + if len(cmd.InstanceSteps) > 0 { + return command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionCanarySteps, "--instance-steps") } return nil diff --git a/command/v7/rollback_command_test.go b/command/v7/rollback_command_test.go index 3218d02a9b..d226176184 100644 --- a/command/v7/rollback_command_test.go +++ b/command/v7/rollback_command_test.go @@ -23,7 +23,7 @@ import ( var _ = Describe("rollback Command", func() { var ( - app string + appName string binaryName string executeErr error fakeActor *v7fakes.FakeActor @@ -31,13 +31,14 @@ var _ = Describe("rollback Command", func() { fakeSharedActor *commandfakes.FakeSharedActor input *Buffer testUI *ui.UI + app resources.Application fakeAppStager *sharedfakes.FakeAppStager cmd v7.RollbackCommand ) BeforeEach(func() { - app = "some-app" + appName = "some-app" binaryName = "faceman" fakeActor = new(v7fakes.FakeActor) fakeAppStager = new(sharedfakes.FakeAppStager) @@ -50,6 +51,10 @@ var _ = Describe("rollback Command", func() { resources.Revision{Version: 2}, resources.Revision{Version: 1}, } + app = resources.Application{ + GUID: "123", + Name: "some-app", + } fakeActor.GetRevisionsByApplicationNameAndSpaceReturns( revisions, v7action.Warnings{"warning-2"}, nil, @@ -68,7 +73,7 @@ var _ = Describe("rollback Command", func() { }) cmd = v7.RollbackCommand{ - RequiredArgs: flag.AppName{AppName: app}, + RequiredArgs: flag.AppName{AppName: appName}, BaseCommand: v7.BaseCommand{ UI: testUI, Config: fakeConfig, @@ -150,7 +155,7 @@ var _ = Describe("rollback Command", func() { When("the app has at least one revision", func() { BeforeEach(func() { fakeActor.GetApplicationByNameAndSpaceReturns( - resources.Application{GUID: "123"}, + app, v7action.Warnings{"app-warning-1"}, nil, ) @@ -169,7 +174,7 @@ var _ = Describe("rollback Command", func() { It("fetches the app and revision revision", func() { Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1), "GetApplicationByNameAndSpace call count") appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) - Expect(appName).To(Equal(app)) + Expect(appName).To(Equal(appName)) Expect(spaceGUID).To(Equal("some-space-guid")) Expect(fakeActor.GetRevisionByApplicationAndVersionCallCount()).To(Equal(1), "GetRevisionByApplicationAndVersion call count") @@ -192,7 +197,7 @@ var _ = Describe("rollback Command", func() { Expect(revisionGUID).To(Equal("some-1-guid")) Expect(opts.AppAction).To(Equal(constant.ApplicationRollingBack)) - Expect(testUI.Out).ToNot(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision '3' will use the settings from revision '1'.", app)) + Expect(testUI.Out).ToNot(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision '3' will use the settings from revision '1'.", appName)) Expect(testUI.Out).ToNot(Say("Are you sure you want to continue?")) Expect(testUI.Out).To(Say("Rolling back to revision 1 for app some-app in org some-org / space some-space as steve...")) @@ -221,7 +226,7 @@ var _ = Describe("rollback Command", func() { Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyRolling)) Expect(opts.MaxInFlight).To(Equal(5)) - Expect(testUI.Out).To(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision will use the settings from revision '1'.", app)) + Expect(testUI.Out).To(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision will use the settings from revision '1'.", appName)) Expect(testUI.Out).To(Say("Are you sure you want to continue?")) Expect(testUI.Out).To(Say("Rolling back to revision 1 for app some-app in org some-org / space some-space as steve...")) @@ -252,6 +257,34 @@ var _ = Describe("rollback Command", func() { }) }) + When("canary strategy is provided", func() { + BeforeEach(func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "1,2,4" + + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("4.0.0") + cmd.Config = fakeConfig + + _, err := input.Write([]byte("y\n")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("starts the app with the current droplet", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeAppStager.StartAppCallCount()).To(Equal(1)) + + inputApp, inputSpace, inputOrg, inputDropletGuid, opts := fakeAppStager.StartAppArgsForCall(0) + Expect(inputApp).To(Equal(app)) + Expect(inputDropletGuid).To(Equal("some-1-guid")) + Expect(inputSpace).To(Equal(cmd.Config.TargetedSpace())) + Expect(inputOrg).To(Equal(cmd.Config.TargetedOrganization())) + Expect(opts.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(opts.AppAction).To(Equal(constant.ApplicationRollingBack)) + Expect(opts.CanarySteps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 4}})) + }) + }) + When("user says no to prompt", func() { BeforeEach(func() { _, err := input.Write([]byte("n\n")) @@ -261,8 +294,8 @@ var _ = Describe("rollback Command", func() { It("does not execute the command and outputs warnings", func() { Expect(fakeAppStager.StartAppCallCount()).To(Equal(0), "GetStartApp call count") - Expect(testUI.Out).To(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision will use the settings from revision '1'.", app)) - Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", app)) + Expect(testUI.Out).To(Say("Rolling '%s' back to revision '1' will create a new revision. The new revision will use the settings from revision '1'.", appName)) + Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", appName)) Expect(testUI.Err).To(Say("app-warning-1")) Expect(testUI.Err).To(Say("revision-warning-3")) @@ -280,7 +313,7 @@ var _ = Describe("rollback Command", func() { Expect(fakeAppStager.StartAppCallCount()).To(Equal(0), "GetStartApp call count") - Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", app)) + Expect(testUI.Out).To(Say("App '%s' has not been rolled back to revision '1'.", appName)) }) }) }) @@ -305,5 +338,38 @@ var _ = Describe("rollback Command", func() { translatableerror.IncorrectUsageError{ Message: "--max-in-flight must be greater than or equal to 1", }), + + Entry("instance-steps no strategy provided", + func() { + cmd.InstanceSteps = "1,2,3" + }, + translatableerror.RequiredFlagsError{ + Arg1: "--instance-steps", + Arg2: "--strategy=canary", + }), + + Entry("instance-steps a valid list of ints", + func() { + cmd.Strategy = flag.DeploymentStrategy{Name: constant.DeploymentStrategyCanary} + cmd.InstanceSteps = "some,thing,not,right" + }, + translatableerror.ParseArgumentError{ + ArgumentName: "--instance-steps", + ExpectedType: "list of weights", + }), + + Entry("instance-steps used when CAPI does not support canary steps", + func() { + cmd.InstanceSteps = "1,2,3" + cmd.Strategy.Name = constant.DeploymentStrategyCanary + fakeConfig = &commandfakes.FakeConfig{} + fakeConfig.APIVersionReturns("3.0.0") + cmd.Config = fakeConfig + }, + translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--instance-steps", + CurrentVersion: "3.0.0", + MinimumVersion: "3.189.0", + }), ) }) diff --git a/command/v7/shared/app_stager.go b/command/v7/shared/app_stager.go index 2aed583033..68479dd387 100644 --- a/command/v7/shared/app_stager.go +++ b/command/v7/shared/app_stager.go @@ -33,6 +33,7 @@ type AppStartOpts struct { MaxInFlight int NoWait bool Strategy constant.DeploymentStrategy + CanarySteps []resources.CanaryStep } type Stager struct { @@ -65,7 +66,6 @@ func NewAppStager(actor stagingAndStartActor, ui command.UI, config command.Conf } func (stager *Stager) StageAndStart(app resources.Application, space configv3.Space, organization configv3.Organization, packageGUID string, opts AppStartOpts) error { - droplet, err := stager.StageApp(app, packageGUID, space) if err != nil { return err @@ -122,6 +122,10 @@ func (stager *Stager) StartApp(app resources.Application, space configv3.Space, Strategy: opts.Strategy, Relationships: resources.Relationships{constant.RelationshipTypeApplication: resources.Relationship{GUID: app.GUID}}, } + + if opts.Strategy == constant.DeploymentStrategyCanary && len(opts.CanarySteps) > 0 { + dep.Options = resources.DeploymentOpts{CanaryDeploymentOptions: &resources.CanaryDeploymentOptions{Steps: opts.CanarySteps}} + } switch opts.AppAction { case constant.ApplicationRollingBack: dep.RevisionGUID = resourceGuid diff --git a/command/v7/shared/app_stager_test.go b/command/v7/shared/app_stager_test.go index 067ddb9631..4e048c3e60 100644 --- a/command/v7/shared/app_stager_test.go +++ b/command/v7/shared/app_stager_test.go @@ -30,14 +30,15 @@ var _ = Describe("app stager", func() { fakeActor *v7fakes.FakeActor fakeLogCacheClient *sharedactionfakes.FakeLogCacheClient - app resources.Application - space configv3.Space - organization configv3.Organization - pkgGUID string - strategy constant.DeploymentStrategy - maxInFlight int - noWait bool - appAction constant.ApplicationAction + app resources.Application + space configv3.Space + organization configv3.Organization + pkgGUID string + strategy constant.DeploymentStrategy + maxInFlight int + noWait bool + appAction constant.ApplicationAction + canaryWeightSteps []resources.CanaryStep allLogsWritten chan bool closedTheStreams bool @@ -113,6 +114,7 @@ var _ = Describe("app stager", func() { MaxInFlight: maxInFlight, NoWait: noWait, Strategy: strategy, + CanarySteps: canaryWeightSteps, } executeErr = appStager.StageAndStart(app, space, organization, pkgGUID, opts) }) @@ -196,6 +198,24 @@ var _ = Describe("app stager", func() { Expect(string(dep.Strategy)).To(Equal("rolling")) }) }) + + When("deployment strategy is canary", func() { + BeforeEach(func() { + strategy = constant.DeploymentStrategyCanary + noWait = true + maxInFlight = 5 + canaryWeightSteps = []resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 3}} + appStager = shared.NewAppStager(fakeActor, testUI, fakeConfig, fakeLogCacheClient) + }) + + It("creates expected deployment", func() { + Expect(fakeActor.CreateDeploymentCallCount()).To(Equal(1), "CreateDeployment...") + dep := fakeActor.CreateDeploymentArgsForCall(0) + Expect(dep.Options.MaxInFlight).To(Equal(5)) + Expect(string(dep.Strategy)).To(Equal("canary")) + Expect(dep.Options.CanaryDeploymentOptions.Steps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 3}})) + }) + }) }) Context("StageApp", func() { @@ -351,6 +371,7 @@ var _ = Describe("app stager", func() { noWait = true maxInFlight = 2 appAction = constant.ApplicationRestarting + canaryWeightSteps = nil app = resources.Application{GUID: "app-guid", Name: "app-name", State: constant.ApplicationStarted} space = configv3.Space{Name: "some-space", GUID: "some-space-guid"} @@ -368,6 +389,7 @@ var _ = Describe("app stager", func() { NoWait: noWait, MaxInFlight: maxInFlight, AppAction: appAction, + CanarySteps: canaryWeightSteps, } executeErr = appStager.StartApp(app, space, organization, resourceGUID, opts) }) @@ -517,6 +539,24 @@ var _ = Describe("app stager", func() { }) }) + When("the app action is rollback", func() { + BeforeEach(func() { + appAction = constant.ApplicationRollingBack + strategy = constant.DeploymentStrategyCanary + canaryWeightSteps = []resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}} + app = resources.Application{GUID: "app-guid", Name: "app-name", State: constant.ApplicationStarted} + }) + + It("displays output for each step of starting", func() { + Expect(executeErr).To(BeNil()) + + Expect(fakeActor.CreateDeploymentCallCount()).To(Equal(1)) + deployment := fakeActor.CreateDeploymentArgsForCall(0) + Expect(deployment.Strategy).To(Equal(constant.DeploymentStrategyCanary)) + Expect(deployment.Options.CanaryDeploymentOptions.Steps).To(Equal(canaryWeightSteps)) + }) + }) + When("the app action is restarting", func() { It("displays output for each step of restarting", func() { Expect(executeErr).To(BeNil()) @@ -564,6 +604,24 @@ var _ = Describe("app stager", func() { }) }) + When("deployment is canary and steps are provided", func() { + BeforeEach(func() { + appAction = constant.ApplicationStarting + strategy = constant.DeploymentStrategyCanary + canaryWeightSteps = []resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 3}} + app = resources.Application{GUID: "app-guid", Name: "app-name", State: constant.ApplicationStopped} + }) + + It("displays output for each step of starting", func() { + Expect(executeErr).To(BeNil()) + Expect(fakeActor.CreateDeploymentCallCount()).To(Equal(1), "CreateDeployment...") + dep := fakeActor.CreateDeploymentArgsForCall(0) + Expect(dep.Options.MaxInFlight).To(Equal(2)) + Expect(string(dep.Strategy)).To(Equal("canary")) + Expect(dep.Options.CanaryDeploymentOptions.Steps).To(Equal([]resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}, {InstanceWeight: 3}})) + }) + }) + When("a droplet guid is not provided", func() { BeforeEach(func() { resourceGUID = "" diff --git a/go.mod b/go.mod index f056ee11c2..b428b176c1 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,18 @@ module code.cloudfoundry.org/cli go 1.23.6 require ( - code.cloudfoundry.org/bytefmt v0.33.0 + code.cloudfoundry.org/bytefmt v0.35.0 code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6 code.cloudfoundry.org/cli-plugin-repo v0.0.0-20200304195157-af98c4be9b85 code.cloudfoundry.org/cli/integration/assets/hydrabroker v0.0.0-20201002233634-81722a1144e4 - code.cloudfoundry.org/clock v1.31.0 + code.cloudfoundry.org/clock v1.33.0 code.cloudfoundry.org/diego-ssh v0.0.0-20230810200140-af9d79fe9c82 code.cloudfoundry.org/go-log-cache/v2 v2.0.7 code.cloudfoundry.org/go-loggregator/v9 v9.2.1 code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f code.cloudfoundry.org/jsonry v1.1.4 - code.cloudfoundry.org/lager/v3 v3.30.0 - code.cloudfoundry.org/tlsconfig v0.21.0 + code.cloudfoundry.org/lager/v3 v3.32.0 + code.cloudfoundry.org/tlsconfig v0.23.0 code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 github.com/blang/semver/v4 v4.0.0 @@ -32,18 +32,18 @@ require ( github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 github.com/moby/term v0.5.2 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d - github.com/onsi/ginkgo/v2 v2.23.3 - github.com/onsi/gomega v1.36.3 + github.com/onsi/ginkgo/v2 v2.23.4 + github.com/onsi/gomega v1.37.0 github.com/pkg/errors v0.9.1 github.com/sabhiram/go-gitignore v0.0.0-20171017070213-362f9845770f github.com/sajari/fuzzy v1.0.0 github.com/sirupsen/logrus v1.9.3 github.com/tedsuo/rata v1.0.1-0.20170830210128-07d200713958 github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec - golang.org/x/crypto v0.36.0 - golang.org/x/net v0.37.0 - golang.org/x/term v0.30.0 - golang.org/x/text v0.23.0 + golang.org/x/crypto v0.37.0 + golang.org/x/net v0.38.0 + golang.org/x/term v0.31.0 + golang.org/x/text v0.24.0 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/yaml.v2 v2.4.0 k8s.io/apimachinery v0.32.3 @@ -66,7 +66,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kr/pty v1.1.8 // indirect @@ -79,10 +79,11 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.31.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect diff --git a/go.sum b/go.sum index dca7482abb..a55f8ccf78 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -code.cloudfoundry.org/bytefmt v0.33.0 h1:hZ0mt27SdNJUTuLuuW50DKnBHUyF6sNtptY5xmCCMyc= -code.cloudfoundry.org/bytefmt v0.33.0/go.mod h1:GO0oYvd7hv9KZVucWDn/ZbzGT+yblD4COtRLK2lL0XU= +code.cloudfoundry.org/bytefmt v0.35.0 h1:ZbGfHvOzJLpgC+N1Uy7duYmrRpvMkOC8gLqpfGWlNp8= +code.cloudfoundry.org/bytefmt v0.35.0/go.mod h1:4Z4NBcvSttXNZRFfH6yKUBBUljAgZw//9zD9w8BvhCk= code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6 h1:Yc9r1p21kEpni9WlG4mwOZw87TB2QlyS9sAEebZ3+ak= code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6/go.mod h1:u5FovqC5GGAEbFPz+IdjycDA+gIjhUwqxnu0vbHwVeM= code.cloudfoundry.org/cli-plugin-repo v0.0.0-20200304195157-af98c4be9b85 h1:jaHWw9opYjKPrDT19uydBBWSxl+g5F4Hv030fqMsalo= code.cloudfoundry.org/cli-plugin-repo v0.0.0-20200304195157-af98c4be9b85/go.mod h1:R1EiyOAr7lW0l/YkZNqItUNZ01Q/dYUfbTn4X4Z+82M= code.cloudfoundry.org/cli/integration/assets/hydrabroker v0.0.0-20201002233634-81722a1144e4 h1:O+j8afQWaDuxzKGcculsjgHGK+biBTn6iMjgFpBf54c= code.cloudfoundry.org/cli/integration/assets/hydrabroker v0.0.0-20201002233634-81722a1144e4/go.mod h1:dVTgo9kQbYns/QM4A1C2GtxqUnFSvJTk2Qhw+M0/uzk= -code.cloudfoundry.org/clock v1.31.0 h1:OLquPj0Q/g8cY0T4G5yt4YT+xc71emf7p3LScKr05xs= -code.cloudfoundry.org/clock v1.31.0/go.mod h1:KjKZM/0vwB+AL7JpCR28Y70H8iFL99A0kAAXKLzEzeI= +code.cloudfoundry.org/clock v1.33.0 h1:hC2VXUzs39keCGtljMeCA4W4Dtt2qdkQ5S5PP6Kq5WE= +code.cloudfoundry.org/clock v1.33.0/go.mod h1:WIc/keHWGp5G4I+JXuLwWek7DwXlZbgh+vvQkTYNBBI= code.cloudfoundry.org/diego-ssh v0.0.0-20230810200140-af9d79fe9c82 h1:Bns1y0jSlcvfP0u8ael+TUlnyNHsNX808zuo58bf5so= code.cloudfoundry.org/diego-ssh v0.0.0-20230810200140-af9d79fe9c82/go.mod h1:L2/glHnSK+wKnsG8oZZqdV2sgYY9NDo/I1aDJGhcWaM= code.cloudfoundry.org/go-log-cache/v2 v2.0.7 h1:yR/JjQ/RscO1n4xVAT9HDYcpx5ET/3Cq2/RhpJml6ZU= @@ -21,10 +21,10 @@ code.cloudfoundry.org/inigo v0.0.0-20230612153013-b300679e6ed6/go.mod h1:1ZB1JCh code.cloudfoundry.org/jsonry v1.1.4 h1:P9N7IlH1/4aRCLcXLgLFj1hkcBmV7muijJzY+K6U4hE= code.cloudfoundry.org/jsonry v1.1.4/go.mod h1:6aKilShQP7w/Ez76h1El2/n9y2OkHuU56nKSBB9Gp0A= code.cloudfoundry.org/lager v1.1.1-0.20191008172124-a9afc05ee5be/go.mod h1:O2sS7gKP3HM2iemG+EnwvyNQK7pTSC6Foi4QiMp9sSk= -code.cloudfoundry.org/lager/v3 v3.30.0 h1:doVyqsEPz+SnM2DgOfKGcNXEvYbjKr6PrR88iv2JvUU= -code.cloudfoundry.org/lager/v3 v3.30.0/go.mod h1:3nPT1LzO+fPChWArPw1gzHB2F7CY9snqMKNIKAnt+r4= -code.cloudfoundry.org/tlsconfig v0.21.0 h1:Uk3jABsAdt8n4N1DNvWnKgcDvX5pEJZdwRTMnTxH0vc= -code.cloudfoundry.org/tlsconfig v0.21.0/go.mod h1:rD6EMIAgNGgdUxg37BiWQxgn/1zKJcB+XVA2uWCsAO8= +code.cloudfoundry.org/lager/v3 v3.32.0 h1:2OmBpTkX17PSFhkPny1P9K642Anwq5OsdpU5vGyerVc= +code.cloudfoundry.org/lager/v3 v3.32.0/go.mod h1:8agmjq9SQseFS7XGTm4iAf6Rn76V7JKfRe60wEMvkCQ= +code.cloudfoundry.org/tlsconfig v0.23.0 h1:1wca1WPUj5p/1KISAFQxlQPzDO7SGoXw0FV6rC/zo2s= +code.cloudfoundry.org/tlsconfig v0.23.0/go.mod h1:cFCgtxwP9+ZQ9InkQhWX0g6GZKJxs1et4zJCF9TCO2A= code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d h1:M+zXqtXJqcsmpL76aU0tdl1ho23eYa4axYoM4gD62UA= code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d/go.mod h1:YUJiVOr5xl0N/RjMxM1tHmgSpBbi5UM+KoVR5AoejO0= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -124,8 +124,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -194,8 +194,8 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/gomega v0.0.0-20171105031654-1eecca0ba8e6/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -204,8 +204,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= -github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= @@ -217,6 +217,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -254,15 +256,17 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.step.sm/crypto v0.59.1 h1:jUL+5p19YS9YJKLaPUgkS2OdGm7s0+hwP7AqTFyF9Cg= -go.step.sm/crypto v0.59.1/go.mod h1:XHavmnzfTyPpQE/n4YokEtjiBzP3LZI9/1O061f5y0o= +go.step.sm/crypto v0.60.0 h1:UgSw8DFG5xUOGB3GUID17UA32G4j1iNQ4qoMhBmsVFw= +go.step.sm/crypto v0.60.0/go.mod h1:Ep83Lv818L4gV0vhFTdPWRKnL6/5fRMpi8SaoP5ArSw= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -285,8 +289,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -294,8 +298,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180419222023-a2a45943ae67/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -319,19 +323,19 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/integration/v7/isolated/copy_source_command_test.go b/integration/v7/isolated/copy_source_command_test.go index a073cb64c9..020c8e5e7c 100644 --- a/integration/v7/isolated/copy_source_command_test.go +++ b/integration/v7/isolated/copy_source_command_test.go @@ -384,17 +384,20 @@ var _ = Describe("copy-source command", func() { It("copies the app to the provided space using a canary deploy", func() { username, _ := helpers.GetCredentials() - session := helpers.CF("copy-source", sourceAppName, targetAppName, "--strategy", "canary") + session := helpers.CF("copy-source", sourceAppName, targetAppName, "--strategy", "canary", "--instance-steps", "20,60") Eventually(session).Should(Say("Copying source from app %s to target app %s in org %s / space %s as %s...", sourceAppName, targetAppName, orgName, spaceName, username)) Eventually(session).Should(Say("Staging app %s in org %s / space %s as %s...", targetAppName, orgName, spaceName, username)) Eventually(session).Should(Say("Waiting for app to deploy...")) Eventually(session).Should(Say("Active deployment with status PAUSED")) Eventually(session).Should(Say("strategy: canary")) Eventually(session).Should(Say("max-in-flight: 1")) + Eventually(session).Should(Say("canary-steps: 1/2")) Eventually(session).Should(Say("Please run `cf continue-deployment %s` to promote the canary deployment, or `cf cancel-deployment %s` to rollback to the previous version.", targetAppName, targetAppName)) Eventually(session).Should(Exit(0)) Eventually(helpers.CF("continue-deployment", targetAppName)).Should(Exit(0)) + Eventually(helpers.CF("continue-deployment", targetAppName)).Should(Exit(0)) + resp, err := http.Get(fmt.Sprintf("http://%s.%s", targetAppName, helpers.DefaultSharedDomain())) Expect(err).ToNot(HaveOccurred()) defer resp.Body.Close() @@ -473,12 +476,13 @@ func helpText(session *Session) { Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`cf copy-source SOURCE_APP DESTINATION_APP \[-s TARGET_SPACE \[-o TARGET_ORG\]\] \[--no-restart\] \[--strategy STRATEGY\] \[--no-wait\]`)) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) + Eventually(session).Should(Say(`--instance-steps`)) Eventually(session).Should(Say(`--max-in-flight\s+Defines the maximum number of instances`)) Eventually(session).Should(Say(`--no-wait\s+ Exit when the first instance of the web process is healthy`)) Eventually(session).Should(Say(`--no-restart\s+Do not restage the destination application`)) Eventually(session).Should(Say(`--organization, -o\s+Org that contains the destination application`)) Eventually(session).Should(Say(`--space, -s\s+Space that contains the destination application`)) + Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index b40ebe07a6..cf61389c17 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -31,9 +31,10 @@ var _ = Describe("restage command", func() { Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("rg")) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) + Eventually(session).Should(Say("--instance-steps")) Eventually(session).Should(Say("--max-in-flight")) Eventually(session).Should(Say(`--no-wait\s+Exit when the first instance of the web process is healthy`)) + Eventually(session).Should(Say(`--strategy\s+Deployment strategy can be canary, rolling or null`)) Eventually(session).Should(Say("ENVIRONMENT:")) Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`)) Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`)) @@ -254,10 +255,10 @@ applications: }) }) - When("strategy canary is given with a non-default max-in-flight value", func() { + When("strategy canary is given with a non-default max-in-flight value and instance-steps", func() { It("restages successfully and notes the max-in-flight value", func() { userName, _ := helpers.GetCredentials() - session := helpers.CF("restage", appName, "--strategy", "canary", "--max-in-flight", "2") + session := helpers.CF("restage", appName, "--strategy", "canary", "--max-in-flight", "2", "--instance-steps", "1,20") Consistently(session.Err).ShouldNot(Say(`This action will cause app downtime\.`)) Eventually(session).Should(Say(`Restaging app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) Eventually(session).Should(Say(`Creating deployment for app %s\.\.\.`, appName)) diff --git a/integration/v7/isolated/rollback_command_test.go b/integration/v7/isolated/rollback_command_test.go index e2bbc7b4f7..f99f4e0b6a 100644 --- a/integration/v7/isolated/rollback_command_test.go +++ b/integration/v7/isolated/rollback_command_test.go @@ -35,6 +35,7 @@ var _ = Describe("rollback command", func() { Expect(session).To(Say(`cf rollback APP_NAME \[--version VERSION\]`)) Expect(session).To(Say("OPTIONS:")) Expect(session).To(Say(`-f\s+Force rollback without confirmation`)) + Expect(session).To(Say("--instance-steps")) Expect(session).To(Say("--max-in-flight")) Expect(session).To(Say(`--strategy\s+Deployment strategy can be canary or rolling. When not specified, it defaults to rolling.`)) Expect(session).To(Say(`--version\s+Roll back to the specified revision`)) @@ -158,7 +159,7 @@ applications: }) It("uses the given strategy to rollback and notes the max-in-flight value", func() { - session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1", "--strategy", "canary") + session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1", "--strategy", "canary", "--instance-steps", "10,50") Eventually(session).Should(Exit(0)) Expect(session).To(HaveRollbackPrompt())