From f7ca159ee256268d61f0296769e0e97532bae40a Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Wed, 8 Oct 2025 17:31:12 -0700 Subject: [PATCH 1/2] Plumbing for --allow-no-pollers flag --- temporalcli/commands.gen.go | 4 ++++ temporalcli/commands.worker.deployment.go | 2 ++ temporalcli/commandsgen/commands.yml | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index a7a2d44ba..f053a2272 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -3037,6 +3037,7 @@ type TemporalWorkerDeploymentSetCurrentVersionCommand struct { Command cobra.Command DeploymentVersionOrUnversionedOptions IgnoreMissingTaskQueues bool + AllowNoPollers bool Yes bool } @@ -3053,6 +3054,7 @@ func NewTemporalWorkerDeploymentSetCurrentVersionCommand(cctx *CommandContext, p } s.Command.Args = cobra.NoArgs s.Command.Flags().BoolVar(&s.IgnoreMissingTaskQueues, "ignore-missing-task-queues", false, "Override protection to accidentally remove task queues.") + s.Command.Flags().BoolVar(&s.AllowNoPollers, "allow-no-pollers", false, "Override protection and set version as current even if it has no pollers.") s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm set Current Version.") s.DeploymentVersionOrUnversionedOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { @@ -3070,6 +3072,7 @@ type TemporalWorkerDeploymentSetRampingVersionCommand struct { Percentage float32 Delete bool IgnoreMissingTaskQueues bool + AllowNoPollers bool Yes bool } @@ -3088,6 +3091,7 @@ func NewTemporalWorkerDeploymentSetRampingVersionCommand(cctx *CommandContext, p s.Command.Flags().Float32Var(&s.Percentage, "percentage", 0, "Percentage of tasks redirected to the Ramping Version. Valid range [0,100].") s.Command.Flags().BoolVar(&s.Delete, "delete", false, "Delete the Ramping Version.") s.Command.Flags().BoolVar(&s.IgnoreMissingTaskQueues, "ignore-missing-task-queues", false, "Override protection to accidentally remove task queues.") + s.Command.Flags().BoolVar(&s.AllowNoPollers, "allow-no-pollers", false, "Override protection and set version as ramping even if it has no pollers.") s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm set Ramping Version.") s.DeploymentVersionOrUnversionedOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { diff --git a/temporalcli/commands.worker.deployment.go b/temporalcli/commands.worker.deployment.go index 3d184846f..abe7d796b 100644 --- a/temporalcli/commands.worker.deployment.go +++ b/temporalcli/commands.worker.deployment.go @@ -558,6 +558,7 @@ func (c *TemporalWorkerDeploymentSetCurrentVersionCommand) run(cctx *CommandCont BuildID: c.BuildId, Identity: c.Parent.Parent.Identity, IgnoreMissingTaskQueues: c.IgnoreMissingTaskQueues, + AllowNoPollers: c.AllowNoPollers, ConflictToken: token, }) if err != nil { @@ -596,6 +597,7 @@ func (c *TemporalWorkerDeploymentSetRampingVersionCommand) run(cctx *CommandCont ConflictToken: token, Identity: c.Parent.Parent.Identity, IgnoreMissingTaskQueues: c.IgnoreMissingTaskQueues, + AllowNoPollers: c.AllowNoPollers, }) if err != nil { return fmt.Errorf("error setting the ramping worker deployment version: %w", err) diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 9405fb761..a1d5ad493 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -1136,6 +1136,9 @@ commands: - name: ignore-missing-task-queues type: bool description: Override protection to accidentally remove task queues. + - name: allow-no-pollers + type: bool + description: Override protection and set version as current even if it has no pollers. - name: yes short: y type: bool @@ -1203,6 +1206,9 @@ commands: - name: ignore-missing-task-queues type: bool description: Override protection to accidentally remove task queues. + - name: allow-no-pollers + type: bool + description: Override protection and set version as ramping even if it has no pollers. - name: yes short: y type: bool From f0d6b6e82b6f8d1b59c1ea6c8d017fe91db8d40a Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Wed, 8 Oct 2025 17:44:19 -0700 Subject: [PATCH 2/2] add test and handle notfound err --- temporalcli/commands.worker.deployment.go | 6 +- .../commands.worker.deployment_test.go | 127 ++++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/temporalcli/commands.worker.deployment.go b/temporalcli/commands.worker.deployment.go index abe7d796b..a633748f4 100644 --- a/temporalcli/commands.worker.deployment.go +++ b/temporalcli/commands.worker.deployment.go @@ -1,12 +1,14 @@ package temporalcli import ( + "errors" "fmt" "time" "github.com/fatih/color" "github.com/temporalio/cli/temporalcli/internal/printer" "go.temporal.io/api/common/v1" + "go.temporal.io/api/serviceerror" "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" ) @@ -549,7 +551,7 @@ func (c *TemporalWorkerDeploymentSetCurrentVersionCommand) run(cctx *CommandCont safeModeMessage: "Current", deploymentName: c.DeploymentName, }) - if err != nil { + if err != nil && !(errors.As(err, new(*serviceerror.NotFound)) && c.AllowNoPollers) { return err } @@ -581,7 +583,7 @@ func (c *TemporalWorkerDeploymentSetRampingVersionCommand) run(cctx *CommandCont safeModeMessage: "Ramping", deploymentName: c.DeploymentName, }) - if err != nil { + if err != nil && !(errors.As(err, new(*serviceerror.NotFound)) && c.AllowNoPollers) { return err } diff --git a/temporalcli/commands.worker.deployment_test.go b/temporalcli/commands.worker.deployment_test.go index 0b5b7a5e4..04f07b70d 100644 --- a/temporalcli/commands.worker.deployment_test.go +++ b/temporalcli/commands.worker.deployment_test.go @@ -172,6 +172,133 @@ func (s *SharedServerSuite) TestDeployment_Set_Current_Version() { s.Nil(jsonVersionOut.Metadata) } +func (s *SharedServerSuite) TestDeployment_Set_Current_Version_AllowNoPollers() { + deploymentName := uuid.NewString() + buildId := uuid.NewString() + version := worker.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildID: buildId, + } + + // with --allow-no-pollers, no need to have a worker polling on this version + res := s.Execute( + "worker", "deployment", "set-current-version", + "--address", s.Address(), + "--deployment-name", version.DeploymentName, "--build-id", version.BuildID, + "--allow-no-pollers", + "--yes", + ) + s.NoError(res.Err) + + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "list", + "--address", s.Address(), + ) + assert.NoError(t, res.Err) + assert.Contains(t, res.Stdout.String(), deploymentName) + }, 30*time.Second, 100*time.Millisecond) + + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", version.DeploymentName, "--build-id", version.BuildID, + ) + assert.NoError(t, res.Err) + }, 30*time.Second, 100*time.Millisecond) + + res = s.Execute( + "worker", "deployment", "describe", + "--address", s.Address(), + "--name", deploymentName, + ) + s.NoError(res.Err) + + s.ContainsOnSameLine(res.Stdout.String(), "Name", deploymentName) + s.ContainsOnSameLine(res.Stdout.String(), "CurrentVersionDeploymentName", version.DeploymentName) + s.ContainsOnSameLine(res.Stdout.String(), "CurrentVersionBuildID", version.BuildID) + + // json + res = s.Execute( + "worker", "deployment", "describe", + "--address", s.Address(), + "--name", deploymentName, + "--output", "json", + ) + s.NoError(res.Err) + + var jsonOut jsonDeploymentInfoType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.Equal(deploymentName, jsonOut.Name) + s.Equal(version.DeploymentName, jsonOut.RoutingConfig.CurrentVersionDeploymentName) + s.Equal(version.BuildID, jsonOut.RoutingConfig.CurrentVersionBuildID) +} + +func (s *SharedServerSuite) TestDeployment_Set_Ramping_Version_AllowNoPollers() { + deploymentName := uuid.NewString() + buildId := uuid.NewString() + version := worker.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildID: buildId, + } + + // with --allow-no-pollers, no need to have a worker polling on this version + res := s.Execute( + "worker", "deployment", "set-ramping-version", + "--address", s.Address(), + "--deployment-name", version.DeploymentName, "--build-id", version.BuildID, + "--percentage", "5", + "--allow-no-pollers", + "--yes", + ) + s.NoError(res.Err) + + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "list", + "--address", s.Address(), + ) + assert.NoError(t, res.Err) + assert.Contains(t, res.Stdout.String(), deploymentName) + }, 30*time.Second, 100*time.Millisecond) + + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", version.DeploymentName, "--build-id", version.BuildID, + ) + assert.NoError(t, res.Err) + }, 30*time.Second, 100*time.Millisecond) + + res = s.Execute( + "worker", "deployment", "describe", + "--address", s.Address(), + "--name", deploymentName, + ) + s.NoError(res.Err) + + s.ContainsOnSameLine(res.Stdout.String(), "Name", deploymentName) + s.ContainsOnSameLine(res.Stdout.String(), "RampingVersionDeploymentName", version.DeploymentName) + s.ContainsOnSameLine(res.Stdout.String(), "RampingVersionBuildID", version.BuildID) + + // json + res = s.Execute( + "worker", "deployment", "describe", + "--address", s.Address(), + "--name", deploymentName, + "--output", "json", + ) + s.NoError(res.Err) + + var jsonOut jsonDeploymentInfoType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.Equal(deploymentName, jsonOut.Name) + s.Equal(version.DeploymentName, jsonOut.RoutingConfig.RampingVersionDeploymentName) + s.Equal(version.BuildID, jsonOut.RoutingConfig.RampingVersionBuildID) +} + func filterByNamePrefix(jsonOut []jsonDeploymentInfoType, prefix string) []jsonDeploymentInfoType { result := []jsonDeploymentInfoType{} for i := range jsonOut {