From 31000a8d72f1255b4153e4ed99d16b6f8dcc417c Mon Sep 17 00:00:00 2001 From: Prem Kumar Kalle Date: Tue, 8 Jul 2025 22:57:31 -0700 Subject: [PATCH] cf push to use deployment scale options for rolling and canary deployments --- actor/v7pushaction/actor.go | 19 +- actor/v7pushaction/actor_test.go | 11 + .../create_deployment_for_push_plan.go | 5 + .../create_deployment_for_push_plan_test.go | 12 +- .../handle_deployment_scale_flag_overrides.go | 22 ++ ...le_deployment_scale_flag_overrides_test.go | 46 +++ actor/v7pushaction/handle_disk_override.go | 27 ++ .../v7pushaction/handle_disk_override_test.go | 264 ++++++++++++++++++ .../v7pushaction/handle_instances_override.go | 27 ++ .../handle_instances_override_test.go | 236 ++++++++++++++++ .../handle_log_rate_limit_override.go | 27 ++ .../handle_log_rate_limit_override_test.go | 263 +++++++++++++++++ actor/v7pushaction/handle_memory_override.go | 27 ++ .../handle_memory_override_test.go | 247 ++++++++++++++++ actor/v7pushaction/push_plan.go | 4 + ...up_deployment_information_for_push_plan.go | 35 ++- ...ployment_information_for_push_plan_test.go | 233 ++++++++++++++-- api/cloudcontroller/ccv3/deployment_test.go | 9 +- command/v7/push_command.go | 71 +++-- command/v7/push_command_test.go | 18 +- command/v7/v7fakes/fake_push_actor.go | 102 ++++++- resources/deployment_resource.go | 8 +- types/null_uint64.go | 8 + 23 files changed, 1645 insertions(+), 76 deletions(-) create mode 100644 actor/v7pushaction/handle_deployment_scale_flag_overrides.go create mode 100644 actor/v7pushaction/handle_deployment_scale_flag_overrides_test.go diff --git a/actor/v7pushaction/actor.go b/actor/v7pushaction/actor.go index 4d2b2822cf2..53abbd200f0 100644 --- a/actor/v7pushaction/actor.go +++ b/actor/v7pushaction/actor.go @@ -14,12 +14,12 @@ type Actor struct { SharedActor SharedActor V7Actor V7Actor - PreparePushPlanSequence []UpdatePushPlanFunc - ChangeApplicationSequence func(plan PushPlan) []ChangeApplicationFunc - TransformManifestSequence []HandleFlagOverrideFunc - - startWithProtocol *regexp.Regexp - urlValidator *regexp.Regexp + PreparePushPlanSequence []UpdatePushPlanFunc + ChangeApplicationSequence func(plan PushPlan) []ChangeApplicationFunc + TransformManifestSequence []HandleFlagOverrideFunc + TransformManifestSequenceForDeployment []HandleFlagOverrideFunc + startWithProtocol *regexp.Regexp + urlValidator *regexp.Regexp } const ProtocolRegexp = "^https?://|^tcp://" @@ -69,7 +69,12 @@ func NewActor(v3Actor V7Actor, sharedActor SharedActor) *Actor { HandleAppPathOverride, HandleDropletPathOverride, } - + actor.TransformManifestSequenceForDeployment = []HandleFlagOverrideFunc{ + HandleInstancesOverrideForDeployment, + HandleMemoryOverrideForDeployment, + HandleDiskOverrideForDeployment, + HandleLogRateLimitOverrideForDeployment, + } actor.PreparePushPlanSequence = []UpdatePushPlanFunc{ SetDefaultBitsPathForPushPlan, SetupDropletPathForPushPlan, diff --git a/actor/v7pushaction/actor_test.go b/actor/v7pushaction/actor_test.go index 32f314564a4..f0331cde6ba 100644 --- a/actor/v7pushaction/actor_test.go +++ b/actor/v7pushaction/actor_test.go @@ -29,4 +29,15 @@ var _ = Describe("Actor", func() { )) }) }) + + Describe("TransformManifestSequenceForDeployment", func() { + It("is a list of functions for preparing the push plan", func() { + Expect(actor.TransformManifestSequenceForDeployment).To(matchers.MatchFuncsByName( + HandleInstancesOverrideForDeployment, + HandleMemoryOverrideForDeployment, + HandleDiskOverrideForDeployment, + HandleLogRateLimitOverrideForDeployment, + )) + }) + }) }) diff --git a/actor/v7pushaction/create_deployment_for_push_plan.go b/actor/v7pushaction/create_deployment_for_push_plan.go index b3f5ea2b01b..f6e28a94d3c 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan.go +++ b/actor/v7pushaction/create_deployment_for_push_plan.go @@ -25,6 +25,11 @@ func (actor Actor) CreateDeploymentForApplication(pushPlan PushPlan, eventStream } } + dep.Options.Instances = pushPlan.Instances + dep.Options.MemoryInMB = pushPlan.MemoryInMB + dep.Options.DiskInMB = pushPlan.DiskInMB + dep.Options.LogRateLimitInBPS = pushPlan.LogRateLimitInBPS + deploymentGUID, warnings, err := actor.V7Actor.CreateDeployment(dep) if err != nil { diff --git a/actor/v7pushaction/create_deployment_for_push_plan_test.go b/actor/v7pushaction/create_deployment_for_push_plan_test.go index 1be4bc16976..eaebf261f69 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan_test.go +++ b/actor/v7pushaction/create_deployment_for_push_plan_test.go @@ -8,6 +8,7 @@ import ( "code.cloudfoundry.org/cli/actor/v7pushaction/v7pushactionfakes" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -122,6 +123,10 @@ var _ = Describe("CreateDeploymentForApplication()", func() { ) paramPlan.Strategy = "rolling" paramPlan.MaxInFlight = 10 + paramPlan.Instances = types.NullInt{IsSet: true, Value: 3} + paramPlan.MemoryInMB = types.NullUint64{IsSet: true, Value: 10} + paramPlan.DiskInMB = types.NullUint64{IsSet: true, Value: 20} + paramPlan.LogRateLimitInBPS = types.NullInt{IsSet: true, Value: 30} }) It("waits for the app to start", func() { @@ -135,7 +140,12 @@ var _ = Describe("CreateDeploymentForApplication()", func() { dep := fakeV7Actor.CreateDeploymentArgsForCall(0) Expect(dep).To(Equal(resources.Deployment{ Strategy: "rolling", - Options: resources.DeploymentOpts{MaxInFlight: 10}, + Options: resources.DeploymentOpts{MaxInFlight: 10, + Instances: types.NullInt{IsSet: true, Value: 3}, + MemoryInMB: types.NullUint64{IsSet: true, Value: 10}, + DiskInMB: types.NullUint64{IsSet: true, Value: 20}, + LogRateLimitInBPS: types.NullInt{IsSet: true, Value: 30}, + }, Relationships: resources.Relationships{ constant.RelationshipTypeApplication: resources.Relationship{GUID: "some-app-guid"}, }, diff --git a/actor/v7pushaction/handle_deployment_scale_flag_overrides.go b/actor/v7pushaction/handle_deployment_scale_flag_overrides.go new file mode 100644 index 00000000000..cbf9e45c06d --- /dev/null +++ b/actor/v7pushaction/handle_deployment_scale_flag_overrides.go @@ -0,0 +1,22 @@ +package v7pushaction + +import ( + "code.cloudfoundry.org/cli/util/manifestparser" +) + +func (actor Actor) HandleDeploymentScaleFlagOverrides( + baseManifest manifestparser.Manifest, + flagOverrides FlagOverrides, +) (manifestparser.Manifest, error) { + newManifest := baseManifest + + for _, transformPlan := range actor.TransformManifestSequenceForDeployment { + var err error + newManifest, err = transformPlan(newManifest, flagOverrides) + if err != nil { + return manifestparser.Manifest{}, err + } + } + + return newManifest, nil +} diff --git a/actor/v7pushaction/handle_deployment_scale_flag_overrides_test.go b/actor/v7pushaction/handle_deployment_scale_flag_overrides_test.go new file mode 100644 index 00000000000..7928385fae6 --- /dev/null +++ b/actor/v7pushaction/handle_deployment_scale_flag_overrides_test.go @@ -0,0 +1,46 @@ +package v7pushaction_test + +import ( + . "code.cloudfoundry.org/cli/actor/v7pushaction" + "code.cloudfoundry.org/cli/util/manifestparser" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("HandleDeploymentScaleFlagOverrides", func() { + var ( + pushActor *Actor + baseManifest manifestparser.Manifest + flagOverrides FlagOverrides + transformedManifest manifestparser.Manifest + executeErr error + + testFuncCallCount int + ) + + testTransformManifestFunc := func(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + testFuncCallCount += 1 + return manifest, nil + } + + BeforeEach(func() { + baseManifest = manifestparser.Manifest{} + flagOverrides = FlagOverrides{} + testFuncCallCount = 0 + + pushActor, _, _ = getTestPushActor() + pushActor.TransformManifestSequenceForDeployment = []HandleFlagOverrideFunc{ + testTransformManifestFunc, + } + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = pushActor.HandleDeploymentScaleFlagOverrides(baseManifest, flagOverrides) + }) + + It("calls each transform-manifest-for-deployment function", func() { + Expect(testFuncCallCount).To(Equal(1)) + Expect(executeErr).NotTo(HaveOccurred()) + Expect(transformedManifest).To(Equal(baseManifest)) + }) +}) diff --git a/actor/v7pushaction/handle_disk_override.go b/actor/v7pushaction/handle_disk_override.go index 47adb500fd4..791511da919 100644 --- a/actor/v7pushaction/handle_disk_override.go +++ b/actor/v7pushaction/handle_disk_override.go @@ -1,11 +1,38 @@ package v7pushaction import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" ) func HandleDiskOverride(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != "" { + return manifest, nil + } + + if overrides.Disk != "" { + if manifest.ContainsMultipleApps() { + return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} + } + + webProcess := manifest.GetFirstAppWebProcess() + if webProcess != nil { + webProcess.DiskQuota = overrides.Disk + } else { + app := manifest.GetFirstApp() + app.DiskQuota = overrides.Disk + } + } + + return manifest, nil +} + +func HandleDiskOverrideForDeployment(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != constant.DeploymentStrategyRolling && overrides.Strategy != constant.DeploymentStrategyCanary { + return manifest, nil + } + if overrides.Disk != "" { if manifest.ContainsMultipleApps() { return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} diff --git a/actor/v7pushaction/handle_disk_override_test.go b/actor/v7pushaction/handle_disk_override_test.go index 3b139bd0728..89033b60978 100644 --- a/actor/v7pushaction/handle_disk_override_test.go +++ b/actor/v7pushaction/handle_disk_override_test.go @@ -1,6 +1,7 @@ package v7pushaction_test import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" @@ -51,6 +52,42 @@ var _ = Describe("HandleDiskOverride", func() { }) }) + When("disk is set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + + When("disk is set, and strategy is set to canary on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + When("disk is set on the flag overrides", func() { BeforeEach(func() { overrides.Disk = "5MB" @@ -144,3 +181,230 @@ var _ = Describe("HandleDiskOverride", func() { }) }) }) + +var _ = Describe("HandleDiskOverrideForDeployment", func() { + var ( + originalManifest manifestparser.Manifest + transformedManifest manifestparser.Manifest + overrides FlagOverrides + executeErr error + ) + + BeforeEach(func() { + originalManifest = manifestparser.Manifest{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = HandleDiskOverrideForDeployment(originalManifest, overrides) + }) + + When("manifest web process does not specify disk", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + When("disk is not set on the flag overrides", func() { + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + + When("disk is set, and strategy is not set on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + + When("disk is set, and strategy is set on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + It("changes the disk of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + { + Type: "web", + DiskQuota: "5MB", + }, + }, + }, + )) + }) + }) + }) + When("disk flag is set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the disk of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + DiskQuota: "5MB", + Processes: []manifestparser.Process{ + { + Type: "worker", + }, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker", DiskQuota: "2MB"}, + {Type: "web", DiskQuota: "3MB"}, + }, + DiskQuota: "1MB", + }, + } + }) + + It("changes the disk of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker", DiskQuota: "2MB"}, + {Type: "web", DiskQuota: "5MB"}, + }, + DiskQuota: "1MB", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + + When("disk flag is set, and strategy is set to canary on the flag overrides", func() { + BeforeEach(func() { + overrides.Disk = "5MB" + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the disk of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + DiskQuota: "5MB", + Processes: []manifestparser.Process{ + { + Type: "worker", + }, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker", DiskQuota: "2MB"}, + {Type: "web", DiskQuota: "3MB"}, + }, + DiskQuota: "1MB", + }, + } + }) + + It("changes the disk of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker", DiskQuota: "2MB"}, + {Type: "web", DiskQuota: "5MB"}, + }, + DiskQuota: "1MB", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + +}) diff --git a/actor/v7pushaction/handle_instances_override.go b/actor/v7pushaction/handle_instances_override.go index 018ab02e8a4..3b8717edef9 100644 --- a/actor/v7pushaction/handle_instances_override.go +++ b/actor/v7pushaction/handle_instances_override.go @@ -1,11 +1,38 @@ package v7pushaction import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" ) func HandleInstancesOverride(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != "" { + return manifest, nil + } + + if overrides.Instances.IsSet { + if manifest.ContainsMultipleApps() { + return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} + } + + webProcess := manifest.GetFirstAppWebProcess() + if webProcess != nil { + webProcess.Instances = &overrides.Instances.Value + } else { + app := manifest.GetFirstApp() + app.Instances = &overrides.Instances.Value + } + } + + return manifest, nil +} + +func HandleInstancesOverrideForDeployment(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != constant.DeploymentStrategyRolling && overrides.Strategy != constant.DeploymentStrategyCanary { + return manifest, nil + } + if overrides.Instances.IsSet { if manifest.ContainsMultipleApps() { return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} diff --git a/actor/v7pushaction/handle_instances_override_test.go b/actor/v7pushaction/handle_instances_override_test.go index 4b662ce7cfe..f01040df885 100644 --- a/actor/v7pushaction/handle_instances_override_test.go +++ b/actor/v7pushaction/handle_instances_override_test.go @@ -1,6 +1,7 @@ package v7pushaction_test import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/types" "code.cloudfoundry.org/cli/util/manifestparser" @@ -46,6 +47,30 @@ var _ = Describe("HandleInstancesOverride", func() { }) }) + When("instances are set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest).To(Equal(originalManifest)) + }) + }) + + When("instances are set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest).To(Equal(originalManifest)) + }) + }) + When("instances are set on the flag overrides", func() { BeforeEach(func() { overrides.Instances = types.NullInt{IsSet: true, Value: 4} @@ -136,3 +161,214 @@ var _ = Describe("HandleInstancesOverride", func() { }) }) }) + +var _ = Describe("HandleInstancesOverrideForDeployment", func() { + var ( + originalManifest manifestparser.Manifest + transformedManifest manifestparser.Manifest + overrides FlagOverrides + executeErr error + ) + + BeforeEach(func() { + originalManifest = manifestparser.Manifest{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = HandleInstancesOverrideForDeployment(originalManifest, overrides) + }) + + When("manifest web process does not specify instances", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + When("instances are not set on the flag overrides", func() { + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest).To(Equal(originalManifest)) + }) + }) + + When("instances are set, and strategy is not set on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest).To(Equal(originalManifest)) + }) + }) + + When("instances are set, and strategy is set on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + It("changes the instances of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web", Instances: &overrides.Instances.Value}, + }, + }, + )) + }) + }) + }) + When("instances flag is set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the instances of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Instances: &overrides.Instances.Value, + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + var instances = 5 + + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + Instances: &instances, + }, + } + }) + + It("changes the instances of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", Instances: &overrides.Instances.Value}, + }, + Instances: &instances, + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + When("instances flag is set, and strategy is set to canary on the flag overrides", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 4} + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the instances of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Instances: &overrides.Instances.Value, + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + var instances = 5 + + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + Instances: &instances, + }, + } + }) + + It("changes the instances of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", Instances: &overrides.Instances.Value}, + }, + Instances: &instances, + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + +}) diff --git a/actor/v7pushaction/handle_log_rate_limit_override.go b/actor/v7pushaction/handle_log_rate_limit_override.go index 9e80cbfc805..9f1639ebbe4 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override.go +++ b/actor/v7pushaction/handle_log_rate_limit_override.go @@ -1,11 +1,38 @@ package v7pushaction import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" ) func HandleLogRateLimitOverride(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != "" { + return manifest, nil + } + + if overrides.LogRateLimit != "" { + if manifest.ContainsMultipleApps() { + return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} + } + + webProcess := manifest.GetFirstAppWebProcess() + if webProcess != nil { + webProcess.LogRateLimit = overrides.LogRateLimit + } else { + app := manifest.GetFirstApp() + app.LogRateLimit = overrides.LogRateLimit + } + } + + return manifest, nil +} + +func HandleLogRateLimitOverrideForDeployment(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != constant.DeploymentStrategyRolling && overrides.Strategy != constant.DeploymentStrategyCanary { + return manifest, nil + } + if overrides.LogRateLimit != "" { if manifest.ContainsMultipleApps() { return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} diff --git a/actor/v7pushaction/handle_log_rate_limit_override_test.go b/actor/v7pushaction/handle_log_rate_limit_override_test.go index f261703e514..15013f14c60 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override_test.go +++ b/actor/v7pushaction/handle_log_rate_limit_override_test.go @@ -1,6 +1,7 @@ package v7pushaction_test import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" @@ -77,6 +78,32 @@ var _ = Describe("HandleLogRateLimitOverride", func() { }) }) + When("log rate limit is set, and strategy is set", func() { + BeforeEach(func() { + overrides.LogRateLimit = "64K" + overrides.Strategy = constant.DeploymentStrategyRolling + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + When("manifest app has only non-web processes", func() { BeforeEach(func() { overrides.LogRateLimit = "32B" @@ -147,3 +174,239 @@ var _ = Describe("HandleLogRateLimitOverride", func() { }) }) }) + +var _ = Describe("HandleLogRateLimitOverrideForDeployment", func() { + var ( + originalManifest manifestparser.Manifest + transformedManifest manifestparser.Manifest + overrides FlagOverrides + executeErr error + ) + + BeforeEach(func() { + originalManifest = manifestparser.Manifest{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = HandleLogRateLimitOverrideForDeployment(originalManifest, overrides) + }) + + When("log rate limit is not set on a flag override", func() { + BeforeEach(func() { + overrides.Strategy = constant.DeploymentStrategyRolling + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + {Type: "worker", LogRateLimit: "1B"}, + }, + }, + } + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + {Type: "worker", LogRateLimit: "1B"}, + }, + }, + )) + }) + }) + + When("manifest web process does not specify log rate limit", func() { + BeforeEach(func() { + overrides.LogRateLimit = "64K" + overrides.Strategy = constant.DeploymentStrategyCanary + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + It("changes the log rate limit of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web", LogRateLimit: "64K"}, + }, + }, + )) + }) + }) + + When("log rate limit is set, and strategy is not set", func() { + BeforeEach(func() { + overrides.LogRateLimit = "64K" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + When("log rate limit flag is set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.LogRateLimit = "32B" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + When("manifest app has only non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the log rate limit of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + LogRateLimit: "32B", + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + LogRateLimit: "1GB", + }, + } + }) + + It("changes the log rate limit of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", LogRateLimit: "32B"}, + }, + LogRateLimit: "1GB", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + + When("log rate limit flag is set, and strategy is set to canary on the flag overrides", func() { + BeforeEach(func() { + overrides.LogRateLimit = "32B" + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + When("manifest app has only non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the log rate limit of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + LogRateLimit: "32B", + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + LogRateLimit: "1GB", + }, + } + }) + + It("changes the log rate limit of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", LogRateLimit: "32B"}, + }, + LogRateLimit: "1GB", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) +}) diff --git a/actor/v7pushaction/handle_memory_override.go b/actor/v7pushaction/handle_memory_override.go index 67c12f758be..0663d3e1669 100644 --- a/actor/v7pushaction/handle_memory_override.go +++ b/actor/v7pushaction/handle_memory_override.go @@ -1,11 +1,38 @@ package v7pushaction import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" ) func HandleMemoryOverride(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != "" { + return manifest, nil + } + + if overrides.Memory != "" { + if manifest.ContainsMultipleApps() { + return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} + } + + webProcess := manifest.GetFirstAppWebProcess() + if webProcess != nil { + webProcess.Memory = overrides.Memory + } else { + app := manifest.GetFirstApp() + app.Memory = overrides.Memory + } + } + + return manifest, nil +} + +func HandleMemoryOverrideForDeployment(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.Strategy != constant.DeploymentStrategyRolling && overrides.Strategy != constant.DeploymentStrategyCanary { + return manifest, nil + } + if overrides.Memory != "" { if manifest.ContainsMultipleApps() { return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} diff --git a/actor/v7pushaction/handle_memory_override_test.go b/actor/v7pushaction/handle_memory_override_test.go index d301981543c..d2ed0dc25db 100644 --- a/actor/v7pushaction/handle_memory_override_test.go +++ b/actor/v7pushaction/handle_memory_override_test.go @@ -1,6 +1,7 @@ package v7pushaction_test import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" @@ -51,6 +52,24 @@ var _ = Describe("HandleMemoryOverride", func() { }) }) + When("memory are set,and strategy is set on the flag overrides", func() { + BeforeEach(func() { + overrides.Memory = "64M" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + When("memory are set on the flag overrides", func() { BeforeEach(func() { overrides.Memory = "64M" @@ -139,3 +158,231 @@ var _ = Describe("HandleMemoryOverride", func() { }) }) }) + +var _ = Describe("HandleMemoryOverrideForDeployment", func() { + var ( + originalManifest manifestparser.Manifest + transformedManifest manifestparser.Manifest + overrides FlagOverrides + executeErr error + ) + + BeforeEach(func() { + originalManifest = manifestparser.Manifest{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = HandleMemoryOverrideForDeployment(originalManifest, overrides) + }) + + When("manifest web process does not specify memory", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + When("memory are not set on the flag overrides", func() { + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + + When("memory are set,and strategy is not set on the flag overrides", func() { + BeforeEach(func() { + overrides.Memory = "64M" + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + )) + }) + }) + + When("memory are set, and strategy is set on the flag overrides", func() { + BeforeEach(func() { + overrides.Memory = "64M" + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + It("changes the memory of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web", Memory: "64M"}, + }, + }, + )) + }) + }) + }) + + When("memory flag is set, and strategy is set to rolling on the flag overrides", func() { + BeforeEach(func() { + overrides.Memory = "64M" + overrides.Strategy = constant.DeploymentStrategyRolling + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the memory of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Memory: "64M", + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + overrides.Memory = "64M" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + Memory: "8M", + }, + } + }) + + It("changes the memory of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", Memory: "64M"}, + }, + Memory: "8M", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + overrides.Memory = "64M" + + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) + + When("memory flag is set, and strategy is set to canary on the flag overrides", func() { + BeforeEach(func() { + overrides.Memory = "64M" + overrides.Strategy = constant.DeploymentStrategyCanary + }) + + When("manifest app has non-web processes", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the memory of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Memory: "64M", + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + overrides.Memory = "64M" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + Memory: "8M", + }, + } + }) + + It("changes the memory of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", Memory: "64M"}, + }, + Memory: "8M", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + overrides.Memory = "64M" + + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) + }) +}) diff --git a/actor/v7pushaction/push_plan.go b/actor/v7pushaction/push_plan.go index 089dad7fbee..48c6a681c71 100644 --- a/actor/v7pushaction/push_plan.go +++ b/actor/v7pushaction/push_plan.go @@ -21,6 +21,10 @@ type PushPlan struct { NoWait bool Strategy constant.DeploymentStrategy MaxInFlight int + Instances types.NullInt + MemoryInMB types.NullUint64 + DiskInMB types.NullUint64 + LogRateLimitInBPS types.NullInt TaskTypeApplication bool InstanceSteps []int64 diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan.go b/actor/v7pushaction/setup_deployment_information_for_push_plan.go index b610c7bae15..ce6079c90a4 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan.go @@ -1,6 +1,11 @@ package v7pushaction -import "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/command/flag" + "code.cloudfoundry.org/cli/types" +) func SetupDeploymentInformationForPushPlan(pushPlan PushPlan, overrides FlagOverrides) (PushPlan, error) { pushPlan.Strategy = overrides.Strategy @@ -13,5 +18,33 @@ func SetupDeploymentInformationForPushPlan(pushPlan PushPlan, overrides FlagOver pushPlan.InstanceSteps = overrides.InstanceSteps } + if overrides.Strategy != constant.DeploymentStrategyDefault && overrides.Instances.IsSet { + pushPlan.Instances = overrides.Instances + } + + if overrides.Strategy != constant.DeploymentStrategyDefault && overrides.Memory != "" { + size, err := flag.ConvertToMb(overrides.Memory) + if err != nil { + return PushPlan{}, errors.New(err.Error()) + } + pushPlan.MemoryInMB.Value = size + pushPlan.MemoryInMB.IsSet = true + } + if overrides.Strategy != constant.DeploymentStrategyDefault && overrides.Disk != "" { + size, err := flag.ConvertToMb(overrides.Disk) + if err != nil { + return PushPlan{}, errors.New(err.Error()) + } + pushPlan.DiskInMB.Value = size + pushPlan.DiskInMB.IsSet = true + } + + if overrides.Strategy != constant.DeploymentStrategyDefault && overrides.LogRateLimit != "" { + logRateLimit := flag.BytesWithUnlimited{} + if err := logRateLimit.IsValidValue(overrides.LogRateLimit); err != nil { + return PushPlan{}, errors.New(err.Error()) + } + pushPlan.LogRateLimitInBPS = types.NullInt(logRateLimit) + } return pushPlan, nil } diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go index 266edba6b32..797c38fce7b 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go @@ -2,6 +2,8 @@ package v7pushaction_test import ( "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/types" . "code.cloudfoundry.org/cli/actor/v7pushaction" @@ -18,54 +20,183 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { executeErr error ) - BeforeEach(func() { - pushPlan = PushPlan{} - overrides = FlagOverrides{} - }) + // A helper function to encapsulate the common flag override tests. + // This function will be called within the context of different strategy tests. + runCommonFlagOverrideTests := func() { + When("instance overrides is specified", func() { + BeforeEach(func() { + overrides.Instances = types.NullInt{IsSet: true, Value: 3} + }) + It("should set the instances", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.Instances).To(Equal(types.NullInt{IsSet: true, Value: 3})) + }) + }) - JustBeforeEach(func() { - expectedPushPlan, executeErr = SetupDeploymentInformationForPushPlan(pushPlan, overrides) - }) + When("memory overrides is specified with incorrect unit", func() { + var expectedErr error + BeforeEach(func() { + overrides.Memory = "10k" + expectedErr = errors.New("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB") + }) + It("should return error", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr).To(MatchError(expectedErr)) + }) + }) - When("flag overrides specifies strategy", func() { - BeforeEach(func() { - overrides.Strategy = "rolling" - maxInFlight := 5 - overrides.MaxInFlight = &maxInFlight - overrides.InstanceSteps = []int64{1, 2, 3, 4} + When("memory overrides is specified in GB", func() { + BeforeEach(func() { + overrides.Memory = "1GB" + }) + It("should set the memory in MB", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MemoryInMB).To(Equal(types.NullUint64{IsSet: true, Value: 1 * 1024})) + }) }) - It("sets the strategy on the push plan", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(expectedPushPlan.Strategy).To(Equal(constant.DeploymentStrategyRolling)) + When("memory overrides is specified in MB", func() { + BeforeEach(func() { + overrides.Memory = "1M" + }) + It("should set the memory in MB", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MemoryInMB).To(Equal(types.NullUint64{IsSet: true, Value: 1})) + }) }) - It("sets the max in flight on the push plan", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(expectedPushPlan.MaxInFlight).To(Equal(5)) + When("disk overrides is specified with incorrect unit", func() { + var expectedErr error + BeforeEach(func() { + overrides.Disk = "10k" + expectedErr = errors.New("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB") + }) + It("should return error", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr).To(MatchError(expectedErr)) + }) }) - When("strategy is rolling", func() { + When("disk overrides is specified in GB", func() { BeforeEach(func() { - overrides.Strategy = "rolling" + overrides.Disk = "2GB" }) + It("should set the disk in MB", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.DiskInMB).To(Equal(types.NullUint64{IsSet: true, Value: 2 * 1024})) + }) + }) - It("does not set the canary steps", func() { + When("disk overrides is specified in MB", func() { + BeforeEach(func() { + overrides.Disk = "1M" + }) + It("should set the disk in MB", func() { Expect(executeErr).ToNot(HaveOccurred()) - Expect(expectedPushPlan.InstanceSteps).To(BeEmpty()) + Expect(expectedPushPlan.DiskInMB).To(Equal(types.NullUint64{IsSet: true, Value: 1})) }) }) - When("strategy is canary", func() { + When("log rate limit is specified with incorrect unit", func() { + var expectedErr error BeforeEach(func() { - overrides.Strategy = "canary" + overrides.LogRateLimit = "10A" + expectedErr = errors.New("Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB") + }) + It("should return error", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr).To(MatchError(expectedErr)) }) + }) - It("does set the canary steps", func() { + When("unlimited log rate limit is specified", func() { + BeforeEach(func() { + overrides.LogRateLimit = "-1" + }) + It("should set the log rate limit", func() { Expect(executeErr).ToNot(HaveOccurred()) - Expect(expectedPushPlan.InstanceSteps).To(ContainElements(int64(1), int64(2), int64(3), int64(4))) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: true, Value: -1})) }) }) + + When("log rate limit overrides is specified in Bytes", func() { // Original comment was "disk overrides", corrected + BeforeEach(func() { + overrides.LogRateLimit = "10B" + }) + It("should set the log rate limit in Bytes", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: true, Value: 10})) + }) + }) + + When("log rate limit overrides is specified in KB", func() { + BeforeEach(func() { + overrides.LogRateLimit = "2K" + }) + It("should set the log rate limit in Bytes", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: true, Value: 2 * 1024})) + }) + }) + + When("log rate limit overrides is specified in MB", func() { // Original comment was "disk overrides", corrected + BeforeEach(func() { + overrides.LogRateLimit = "1MB" + }) + It("should set the log rate limit in Bytes", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: true, Value: 1 * 1024 * 1024})) + }) + }) + } + + BeforeEach(func() { + pushPlan = PushPlan{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + expectedPushPlan, executeErr = SetupDeploymentInformationForPushPlan(pushPlan, overrides) + }) + + When("flag overrides specifies strategy", func() { + BeforeEach(func() { + // These values are common for both rolling and canary when a strategy is specified + maxInFlight := 5 + overrides.MaxInFlight = &maxInFlight + overrides.InstanceSteps = []int64{1, 2, 3, 4} + }) + + DescribeTableSubtree("sets strategy and related options correctly", + func(strategy constant.DeploymentStrategy, expectedDeploymentStrategy constant.DeploymentStrategy, expectedInstanceSteps []int64) { + BeforeEach(func() { + overrides.Strategy = strategy + }) + + It("sets the strategy on the push plan", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.Strategy).To(Equal(expectedDeploymentStrategy)) + }) + + It("sets the max in flight on the push plan", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MaxInFlight).To(Equal(5)) + }) + + It("sets the instance steps correctly", func() { + Expect(executeErr).ToNot(HaveOccurred()) + if len(expectedInstanceSteps) > 0 { + Expect(expectedPushPlan.InstanceSteps).To(ContainElements(expectedInstanceSteps)) + } else { + Expect(expectedPushPlan.InstanceSteps).To(BeEmpty()) + } + }) + + runCommonFlagOverrideTests() + }, + Entry("when strategy is rolling", constant.DeploymentStrategyRolling, constant.DeploymentStrategyRolling, []int64{}), // No instance steps for rolling + Entry("when strategy is canary", constant.DeploymentStrategyCanary, constant.DeploymentStrategyCanary, []int64{1, 2, 3, 4}), + ) }) When("flag overrides does not specify strategy", func() { @@ -73,7 +204,12 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { maxInFlight := 10 overrides.MaxInFlight = &maxInFlight overrides.InstanceSteps = []int64{1, 2, 3, 4} + overrides.Instances = types.NullInt{IsSet: true, Value: 3} + overrides.Memory = "10k" + overrides.Disk = "20K" + overrides.LogRateLimit = "20K" }) + It("leaves the strategy as its default value on the push plan", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.Strategy).To(Equal(constant.DeploymentStrategyDefault)) @@ -88,17 +224,56 @@ var _ = Describe("SetupDeploymentInformationForPushPlan", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.InstanceSteps).To(BeEmpty()) }) + + It("does not set instances", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.Instances).To(Equal(types.NullInt{IsSet: false, Value: 0})) + }) + It("does not set Memory", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MemoryInMB).To(Equal(types.NullUint64{IsSet: false, Value: 0})) + }) + It("does not set Disk", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.DiskInMB).To(Equal(types.NullUint64{IsSet: false, Value: 0})) + }) + It("does not set log rate limit", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: false, Value: 0})) + }) }) - When("flag not provided", func() { + When("no flag overrides are provided", func() { + BeforeEach(func() { + overrides = FlagOverrides{} + }) It("does not set MaxInFlight", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.MaxInFlight).To(Equal(0)) }) - It("does not set the canary steps", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(expectedPushPlan.InstanceSteps).To(BeEmpty()) }) + It("does not set instances", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.Instances).To(Equal(types.NullInt{IsSet: false, Value: 0})) + }) + It("does not set Memory", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.MemoryInMB).To(Equal(types.NullUint64{IsSet: false, Value: 0})) + }) + It("does not set Disk", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.DiskInMB).To(Equal(types.NullUint64{IsSet: false, Value: 0})) + }) + It("does not set log rate limit", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.LogRateLimitInBPS).To(Equal(types.NullInt{IsSet: false, Value: 0})) + }) + It("leaves the strategy as its default value on the push plan", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(expectedPushPlan.Strategy).To(Equal(constant.DeploymentStrategyDefault)) + }) }) }) diff --git a/api/cloudcontroller/ccv3/deployment_test.go b/api/cloudcontroller/ccv3/deployment_test.go index 829b60f0bd5..76d817a5e73 100644 --- a/api/cloudcontroller/ccv3/deployment_test.go +++ b/api/cloudcontroller/ccv3/deployment_test.go @@ -1,6 +1,7 @@ package ccv3_test import ( + "code.cloudfoundry.org/cli/types" "fmt" "net/http" @@ -252,6 +253,10 @@ var _ = Describe("Deployment", func() { dep.RevisionGUID = revisionGUID dep.Relationships = resources.Relationships{constant.RelationshipTypeApplication: resources.Relationship{GUID: "some-app-guid"}} dep.Options.CanaryDeploymentOptions = &resources.CanaryDeploymentOptions{Steps: []resources.CanaryStep{{InstanceWeight: 1}, {InstanceWeight: 2}}} + dep.Options.Instances = types.NullInt{IsSet: true, Value: 2} + dep.Options.MemoryInMB = types.NullUint64{IsSet: true, Value: 1024} + dep.Options.DiskInMB = types.NullUint64{IsSet: true, Value: 2048} + dep.Options.LogRateLimitInBPS = types.NullInt{IsSet: true, Value: 10} deploymentGUID, warnings, executeErr = client.CreateApplicationDeployment(dep) }) @@ -278,7 +283,7 @@ var _ = Describe("Deployment", func() { server.AppendHandlers( CombineHandlers( VerifyRequest(http.MethodPost, "/v3/deployments"), - VerifyJSON(`{"revision":{ "guid":"some-revision-guid" }, "strategy": "canary", "relationships":{"app":{"data":{"guid":"some-app-guid"}}},"options":{"canary": {"steps": [{"instance_weight": 1}, {"instance_weight": 2}]}}}`), + VerifyJSON(`{"revision":{ "guid":"some-revision-guid" }, "strategy": "canary", "relationships":{"app":{"data":{"guid":"some-app-guid"}}},"options":{"canary": {"steps": [{"instance_weight": 1}, {"instance_weight": 2}]},"web_instances": 2,"memory_in_mb": 1024,"disk_in_mb": 2048,"log_rate_limit_in_bytes_per_second": 10}}`), RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"warning"}}), ), ) @@ -307,7 +312,7 @@ var _ = Describe("Deployment", func() { server.AppendHandlers( CombineHandlers( VerifyRequest(http.MethodPost, "/v3/deployments"), - VerifyJSON(`{"revision":{ "guid":"some-revision-guid" }, "strategy": "canary","options":{"canary": {"steps": [{"instance_weight": 1}, {"instance_weight": 2}]}}, "relationships":{"app":{"data":{"guid":"some-app-guid"}}}}`), + VerifyJSON(`{"revision":{ "guid":"some-revision-guid" }, "strategy": "canary","options":{"canary": {"steps": [{"instance_weight": 1}, {"instance_weight": 2}]},"web_instances": 2,"memory_in_mb": 1024,"disk_in_mb": 2048,"log_rate_limit_in_bytes_per_second": 10}, "relationships":{"app":{"data":{"guid":"some-app-guid"}}}}`), RespondWith(http.StatusTeapot, response, http.Header{}), ), ) diff --git a/command/v7/push_command.go b/command/v7/push_command.go index 790fcda238e..3952718bd5c 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -42,6 +42,7 @@ type ProgressBar interface { type PushActor interface { HandleFlagOverrides(baseManifest manifestparser.Manifest, flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error) + HandleDeploymentScaleFlagOverrides(manifest manifestparser.Manifest, flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error) CreatePushPlans(spaceGUID string, orgGUID string, manifest manifestparser.Manifest, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, v7action.Warnings, error) // Actualize applies any necessary changes. Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) <-chan *v7pushaction.PushEvent @@ -183,54 +184,53 @@ func (cmd PushCommand) Execute(args []string) error { return err } - transformedManifest, err := cmd.PushActor.HandleFlagOverrides(baseManifest, flagOverrides) + // Transform manifest from base using flag overrides + transformedInitialManifest, err := cmd.PushActor.HandleFlagOverrides(baseManifest, flagOverrides) if err != nil { return err } - flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, transformedManifest.ContainsPrivateDockerImages()) + flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, transformedInitialManifest.ContainsPrivateDockerImages()) if err != nil { return err } - transformedRawManifest, err := cmd.ManifestParser.MarshalManifest(transformedManifest) + transformedRawInitialManifest, err := cmd.ManifestParser.MarshalManifest(transformedInitialManifest) if err != nil { return err } - cmd.announcePushing(transformedManifest.AppNames(), user) + // Apply deployment scale options (this gives us the final manifest) + transformedFinalManifest, err := cmd.PushActor.HandleDeploymentScaleFlagOverrides(transformedInitialManifest, flagOverrides) + if err != nil { + return err + } + transformedRawFinalManifest, err := cmd.ManifestParser.MarshalManifest(transformedFinalManifest) + if err != nil { + return err + } + + cmd.announcePushing(transformedFinalManifest.AppNames(), user) - hasManifest := transformedManifest.PathToManifest != "" + hasManifest := transformedFinalManifest.PathToManifest != "" spaceGUID := cmd.Config.TargetedSpace().GUID if hasManifest { cmd.UI.DisplayText("Applying manifest file {{.Path}}...", map[string]interface{}{ - "Path": transformedManifest.PathToManifest, + "Path": transformedFinalManifest.PathToManifest, }) - diff, warnings, err := cmd.Actor.DiffSpaceManifest(spaceGUID, transformedRawManifest) - - cmd.UI.DisplayWarnings(warnings) + err := cmd.showManifestDiff(spaceGUID, transformedRawFinalManifest) if err != nil { - if _, isUnexpectedError := err.(ccerror.V3UnexpectedResponseError); isUnexpectedError { - cmd.UI.DisplayWarning("Unable to generate diff. Continuing to apply manifest...") - } else { - return err - } - } else { - cmd.UI.DisplayNewline() - cmd.UI.DisplayText("Updating with these attributes...") - - err = cmd.DiffDisplayer.DisplayDiff(transformedRawManifest, diff) - if err != nil { - return err - } + return err } + } + // Set space manifest using initial (non-scaled for rolling/canary deployment strategy) manifest v7ActionWarnings, err := cmd.VersionActor.SetSpaceManifest( cmd.Config.TargetedSpace().GUID, - transformedRawManifest, + transformedRawInitialManifest, ) cmd.UI.DisplayWarnings(v7ActionWarnings) @@ -244,7 +244,7 @@ func (cmd PushCommand) Execute(args []string) error { pushPlans, warnings, err := cmd.PushActor.CreatePushPlans( cmd.Config.TargetedSpace().GUID, cmd.Config.TargetedOrganization().GUID, - transformedManifest, + transformedFinalManifest, flagOverrides, ) @@ -776,3 +776,26 @@ func (cmd PushCommand) getLogs(logStream <-chan sharedaction.LogMessage, errStre } } } + +func (cmd PushCommand) showManifestDiff(spaceGUID string, transformedRawFinalManifest []byte) error { + diff, warnings, err := cmd.Actor.DiffSpaceManifest(spaceGUID, transformedRawFinalManifest) + + cmd.UI.DisplayWarnings(warnings) + if err != nil { + if _, isUnexpectedError := err.(ccerror.V3UnexpectedResponseError); isUnexpectedError { + cmd.UI.DisplayWarning("Unable to generate diff. Continuing to apply manifest...") + } else { + return err + } + } else { + cmd.UI.DisplayNewline() + cmd.UI.DisplayText("Updating with these attributes...") + + err = cmd.DiffDisplayer.DisplayDiff(transformedRawFinalManifest, diff) + if err != nil { + return err + } + } + + return nil +} diff --git a/command/v7/push_command_test.go b/command/v7/push_command_test.go index 8dc629c2321..0db4d3126c0 100644 --- a/command/v7/push_command_test.go +++ b/command/v7/push_command_test.go @@ -212,7 +212,7 @@ var _ = Describe("push Command", func() { When("the flags are all valid", func() { It("delegating to the GetBaseManifest", func() { - // This tells us GetBaseManifest is being called because we dont have a fake + // This tells us GetBaseManifest is being called because we don't have a fake Expect(fakeManifestLocator.PathCallCount()).To(Equal(1)) }) @@ -275,6 +275,14 @@ var _ = Describe("push Command", func() { }, nil, ) + fakeActor.HandleDeploymentScaleFlagOverridesReturns( + manifestparser.Manifest{ + Applications: []manifestparser.Application{ + {Name: "some-app-name"}, + }, + }, + nil, + ) }) When("the docker password is needed", func() { @@ -299,7 +307,7 @@ var _ = Describe("push Command", func() { }) It("delegates to the manifest parser", func() { - Expect(fakeManifestParser.MarshalManifestCallCount()).To(Equal(1)) + Expect(fakeManifestParser.MarshalManifestCallCount()).To(Equal(2)) Expect(fakeManifestParser.MarshalManifestArgsForCall(0)).To(Equal( manifestparser.Manifest{ Applications: []manifestparser.Application{ @@ -341,6 +349,12 @@ var _ = Describe("push Command", func() { }, nil, ) + fakeActor.HandleDeploymentScaleFlagOverridesReturns( + manifestparser.Manifest{ + PathToManifest: "path/to/manifest", + }, + nil, + ) expectedDiff = resources.ManifestDiff{ Diffs: []resources.Diff{ {Op: resources.AddOperation, Path: "/path/to/field", Value: "hello"}, diff --git a/command/v7/v7fakes/fake_push_actor.go b/command/v7/v7fakes/fake_push_actor.go index 95c137b77ba..f24de75af22 100644 --- a/command/v7/v7fakes/fake_push_actor.go +++ b/command/v7/v7fakes/fake_push_actor.go @@ -41,6 +41,20 @@ type FakePushActor struct { result2 v7action.Warnings result3 error } + HandleDeploymentScaleFlagOverridesStub func(manifestparser.Manifest, v7pushaction.FlagOverrides) (manifestparser.Manifest, error) + handleDeploymentScaleFlagOverridesMutex sync.RWMutex + handleDeploymentScaleFlagOverridesArgsForCall []struct { + arg1 manifestparser.Manifest + arg2 v7pushaction.FlagOverrides + } + handleDeploymentScaleFlagOverridesReturns struct { + result1 manifestparser.Manifest + result2 error + } + handleDeploymentScaleFlagOverridesReturnsOnCall map[int]struct { + result1 manifestparser.Manifest + result2 error + } HandleFlagOverridesStub func(manifestparser.Manifest, v7pushaction.FlagOverrides) (manifestparser.Manifest, error) handleFlagOverridesMutex sync.RWMutex handleFlagOverridesArgsForCall []struct { @@ -66,15 +80,16 @@ func (fake *FakePushActor) Actualize(arg1 v7pushaction.PushPlan, arg2 v7pushacti arg1 v7pushaction.PushPlan arg2 v7pushaction.ProgressBar }{arg1, arg2}) + stub := fake.ActualizeStub + fakeReturns := fake.actualizeReturns fake.recordInvocation("Actualize", []interface{}{arg1, arg2}) fake.actualizeMutex.Unlock() - if fake.ActualizeStub != nil { - return fake.ActualizeStub(arg1, arg2) + if stub != nil { + return stub(arg1, arg2) } if specificReturn { return ret.result1 } - fakeReturns := fake.actualizeReturns return fakeReturns.result1 } @@ -129,15 +144,16 @@ func (fake *FakePushActor) CreatePushPlans(arg1 string, arg2 string, arg3 manife arg3 manifestparser.Manifest arg4 v7pushaction.FlagOverrides }{arg1, arg2, arg3, arg4}) + stub := fake.CreatePushPlansStub + fakeReturns := fake.createPushPlansReturns fake.recordInvocation("CreatePushPlans", []interface{}{arg1, arg2, arg3, arg4}) fake.createPushPlansMutex.Unlock() - if fake.CreatePushPlansStub != nil { - return fake.CreatePushPlansStub(arg1, arg2, arg3, arg4) + if stub != nil { + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1, ret.result2, ret.result3 } - fakeReturns := fake.createPushPlansReturns return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 } @@ -189,6 +205,71 @@ func (fake *FakePushActor) CreatePushPlansReturnsOnCall(i int, result1 []v7pusha }{result1, result2, result3} } +func (fake *FakePushActor) HandleDeploymentScaleFlagOverrides(arg1 manifestparser.Manifest, arg2 v7pushaction.FlagOverrides) (manifestparser.Manifest, error) { + fake.handleDeploymentScaleFlagOverridesMutex.Lock() + ret, specificReturn := fake.handleDeploymentScaleFlagOverridesReturnsOnCall[len(fake.handleDeploymentScaleFlagOverridesArgsForCall)] + fake.handleDeploymentScaleFlagOverridesArgsForCall = append(fake.handleDeploymentScaleFlagOverridesArgsForCall, struct { + arg1 manifestparser.Manifest + arg2 v7pushaction.FlagOverrides + }{arg1, arg2}) + stub := fake.HandleDeploymentScaleFlagOverridesStub + fakeReturns := fake.handleDeploymentScaleFlagOverridesReturns + fake.recordInvocation("HandleDeploymentScaleFlagOverrides", []interface{}{arg1, arg2}) + fake.handleDeploymentScaleFlagOverridesMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakePushActor) HandleDeploymentScaleFlagOverridesCallCount() int { + fake.handleDeploymentScaleFlagOverridesMutex.RLock() + defer fake.handleDeploymentScaleFlagOverridesMutex.RUnlock() + return len(fake.handleDeploymentScaleFlagOverridesArgsForCall) +} + +func (fake *FakePushActor) HandleDeploymentScaleFlagOverridesCalls(stub func(manifestparser.Manifest, v7pushaction.FlagOverrides) (manifestparser.Manifest, error)) { + fake.handleDeploymentScaleFlagOverridesMutex.Lock() + defer fake.handleDeploymentScaleFlagOverridesMutex.Unlock() + fake.HandleDeploymentScaleFlagOverridesStub = stub +} + +func (fake *FakePushActor) HandleDeploymentScaleFlagOverridesArgsForCall(i int) (manifestparser.Manifest, v7pushaction.FlagOverrides) { + fake.handleDeploymentScaleFlagOverridesMutex.RLock() + defer fake.handleDeploymentScaleFlagOverridesMutex.RUnlock() + argsForCall := fake.handleDeploymentScaleFlagOverridesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakePushActor) HandleDeploymentScaleFlagOverridesReturns(result1 manifestparser.Manifest, result2 error) { + fake.handleDeploymentScaleFlagOverridesMutex.Lock() + defer fake.handleDeploymentScaleFlagOverridesMutex.Unlock() + fake.HandleDeploymentScaleFlagOverridesStub = nil + fake.handleDeploymentScaleFlagOverridesReturns = struct { + result1 manifestparser.Manifest + result2 error + }{result1, result2} +} + +func (fake *FakePushActor) HandleDeploymentScaleFlagOverridesReturnsOnCall(i int, result1 manifestparser.Manifest, result2 error) { + fake.handleDeploymentScaleFlagOverridesMutex.Lock() + defer fake.handleDeploymentScaleFlagOverridesMutex.Unlock() + fake.HandleDeploymentScaleFlagOverridesStub = nil + if fake.handleDeploymentScaleFlagOverridesReturnsOnCall == nil { + fake.handleDeploymentScaleFlagOverridesReturnsOnCall = make(map[int]struct { + result1 manifestparser.Manifest + result2 error + }) + } + fake.handleDeploymentScaleFlagOverridesReturnsOnCall[i] = struct { + result1 manifestparser.Manifest + result2 error + }{result1, result2} +} + func (fake *FakePushActor) HandleFlagOverrides(arg1 manifestparser.Manifest, arg2 v7pushaction.FlagOverrides) (manifestparser.Manifest, error) { fake.handleFlagOverridesMutex.Lock() ret, specificReturn := fake.handleFlagOverridesReturnsOnCall[len(fake.handleFlagOverridesArgsForCall)] @@ -196,15 +277,16 @@ func (fake *FakePushActor) HandleFlagOverrides(arg1 manifestparser.Manifest, arg arg1 manifestparser.Manifest arg2 v7pushaction.FlagOverrides }{arg1, arg2}) + stub := fake.HandleFlagOverridesStub + fakeReturns := fake.handleFlagOverridesReturns fake.recordInvocation("HandleFlagOverrides", []interface{}{arg1, arg2}) fake.handleFlagOverridesMutex.Unlock() - if fake.HandleFlagOverridesStub != nil { - return fake.HandleFlagOverridesStub(arg1, arg2) + if stub != nil { + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 } - fakeReturns := fake.handleFlagOverridesReturns return fakeReturns.result1, fakeReturns.result2 } @@ -260,6 +342,8 @@ func (fake *FakePushActor) Invocations() map[string][][]interface{} { defer fake.actualizeMutex.RUnlock() fake.createPushPlansMutex.RLock() defer fake.createPushPlansMutex.RUnlock() + fake.handleDeploymentScaleFlagOverridesMutex.RLock() + defer fake.handleDeploymentScaleFlagOverridesMutex.RUnlock() fake.handleFlagOverridesMutex.RLock() defer fake.handleFlagOverridesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/resources/deployment_resource.go b/resources/deployment_resource.go index 65f9755191b..ad5352ad29b 100644 --- a/resources/deployment_resource.go +++ b/resources/deployment_resource.go @@ -5,6 +5,7 @@ import ( "code.cloudfoundry.org/cli/api/cloudcontroller" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/types" ) type Deployment struct { @@ -27,10 +28,15 @@ type Deployment struct { type DeploymentOpts struct { MaxInFlight int `json:"max_in_flight,omitempty"` CanaryDeploymentOptions *CanaryDeploymentOptions `json:"canary,omitempty"` + Instances types.NullInt `json:"web_instances,omitempty"` + MemoryInMB types.NullUint64 `json:"memory_in_mb,omitempty"` + DiskInMB types.NullUint64 `json:"disk_in_mb,omitempty"` + LogRateLimitInBPS types.NullInt `json:"log_rate_limit_in_bytes_per_second,omitempty"` } func (d DeploymentOpts) IsEmpty() bool { - return d.MaxInFlight == 0 && (d.CanaryDeploymentOptions == nil || len(d.CanaryDeploymentOptions.Steps) == 0) + return d.MaxInFlight == 0 && (d.CanaryDeploymentOptions == nil || len(d.CanaryDeploymentOptions.Steps) == 0) && + !d.Instances.IsSet && !d.MemoryInMB.IsSet && !d.DiskInMB.IsSet && !d.LogRateLimitInBPS.IsSet } type CanaryDeploymentOptions struct { diff --git a/types/null_uint64.go b/types/null_uint64.go index 276c26906a5..91ee2734b00 100644 --- a/types/null_uint64.go +++ b/types/null_uint64.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "strconv" ) @@ -43,3 +44,10 @@ func (n *NullUint64) UnmarshalJSON(rawJSON []byte) error { return n.ParseStringValue(stringValue) } + +func (n *NullUint64) MarshalJSON() ([]byte, error) { + if n.IsSet { + return []byte(fmt.Sprint(n.Value)), nil + } + return []byte(JsonNull), nil +}