From 64afd76f0d32e799e514479477ebbe2ef4545ee0 Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Tue, 29 Jul 2025 22:25:36 +0200 Subject: [PATCH 1/7] activity: support batch operations in reset and update-options (#831) * The `temporal activity {pause,update-options}` support batch operations through `--query` * Consistently passing the `--identity` parameter to server requests Improved developer experience, can batch these requests through a visibility query instead of sending multiple requests 2. How was this tested: New unit tests 3. Any docs updates needed? Documentation updates are automatically generated with releases. --- go.mod | 9 +- go.sum | 26 ++- temporalcli/client.go | 25 ++- temporalcli/commands.activity.go | 223 ++++++++++++++-------- temporalcli/commands.activity_test.go | 86 ++++++++- temporalcli/commands.batch.go | 2 +- temporalcli/commands.gen.go | 80 ++++---- temporalcli/commands.worker.deployment.go | 8 +- temporalcli/commands.workflow.go | 14 +- temporalcli/commands.workflow_exec.go | 2 +- temporalcli/commands.workflow_reset.go | 2 +- temporalcli/commandsgen/commands.yml | 110 ++++++----- 12 files changed, 383 insertions(+), 204 deletions(-) diff --git a/go.mod b/go.mod index f7d1f9238..26af6c26c 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,11 @@ require ( github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/temporalio/ui-server/v2 v2.39.0 - go.temporal.io/api v1.50.0 + go.temporal.io/api v1.51.0 go.temporal.io/sdk v1.35.0 go.temporal.io/sdk/contrib/envconfig v0.1.0 - go.temporal.io/server v1.28.0 - google.golang.org/grpc v1.71.0 + go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9 + google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.34.1 @@ -121,6 +121,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/sony/gobreaker v1.0.0 // indirect github.com/spf13/cast v1.7.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7 // indirect github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb // indirect @@ -130,6 +131,7 @@ require ( github.com/uber-go/tally/v4 v4.1.17 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect @@ -144,7 +146,6 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go.temporal.io/version v0.3.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.18.1 // indirect go.uber.org/fx v1.23.0 // indirect diff --git a/go.sum b/go.sum index a18e8152e..63dc1f148 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -254,6 +256,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -320,6 +326,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -360,8 +368,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/pdata v1.34.0 h1:2vwYftckXe7pWxI9mfSo+tw3wqdGNrYpMbDx/5q6rw8= +go.opentelemetry.io/collector/pdata v1.34.0/go.mod h1:StPHMFkhLBellRWrULq0DNjv4znCDJZP6La4UuC+JHI= go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= @@ -390,16 +402,14 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.50.0 h1:7s8Cn+fKfNx9G0v2Ge9We6X2WiCA3JvJ9JryeNbx1Bc= -go.temporal.io/api v1.50.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.51.0 h1:9+e14GrIa7nWoWoudqj/PSwm33yYjV+u8TAR9If7s/g= +go.temporal.io/api v1.51.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= go.temporal.io/sdk/contrib/envconfig v0.1.0 h1:s+G/Ujph+Xl2jzLiiIm2T1vuijDkUL4Kse49dgDVGBE= go.temporal.io/sdk/contrib/envconfig v0.1.0/go.mod h1:FQEO3C56h9C7M6sDgSanB8HnBTmopw9qgVx4F1S6pJk= -go.temporal.io/server v1.28.0 h1:1rLPrT21ZwpsRjElJqSgThj1NZSAtAPyi8nKX+EAkgo= -go.temporal.io/server v1.28.0/go.mod h1:yri8PdZoAtwI9p65hzvABf11WqXelHl/HabbrnJSu+g= -go.temporal.io/version v0.3.0 h1:dMrei9l9NyHt8nG6EB8vAwDLLTwx2SvRyucCSumAiig= -go.temporal.io/version v0.3.0/go.mod h1:UA9S8/1LaKYae6TyD9NaPMJTZb911JcbqghI2CBSP78= +go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9 h1:jJV/LmX6msjAQj+TrPIM+qVZZMu8EPnLENM4nNiJq9k= +go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9/go.mod h1:qRq3Ei+nk7eXw+Dw60GaHdCDo7dbbqGa7LnSJqPaIlk= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -569,8 +579,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/temporalcli/client.go b/temporalcli/client.go index d03c2277e..cc8c73018 100644 --- a/temporalcli/client.go +++ b/temporalcli/client.go @@ -27,6 +27,18 @@ func (c *ClientOptions) dialClient(cctx *CommandContext) (client.Client, error) return nil, fmt.Errorf("root command unexpectedly missing when dialing client") } + if c.Identity == "" { + hostname, err := os.Hostname() + if err != nil { + hostname = "unknown-host" + } + username := "unknown-user" + if u, err := user.Current(); err == nil { + username = u.Username + } + c.Identity = "temporal-cli:" + username + "@" + hostname + } + // Load a client config profile var clientProfile envconfig.ClientConfigProfile if !cctx.RootCommand.DisableConfigFile || !cctx.RootCommand.DisableConfigEnv { @@ -178,7 +190,6 @@ func (c *ClientOptions) dialClient(cctx *CommandContext) (client.Client, error) } clientOptions.Logger = log.NewStructuredLogger(cctx.Logger) - clientOptions.Identity = clientIdentity() // We do not put codec on data converter here, it is applied via // interceptor. Same for failure conversion. // XXX: If this is altered to be more dynamic, have to also update @@ -270,18 +281,6 @@ func payloadCodecInterceptor( ) } -func clientIdentity() string { - hostname, err := os.Hostname() - if err != nil { - hostname = "unknown-host" - } - username := "unknown-user" - if u, err := user.Current(); err == nil { - username = u.Username - } - return "temporal-cli:" + username + "@" + hostname -} - var DataConverterWithRawValue = converter.NewCompositeDataConverter( rawValuePayloadConverter{}, converter.NewNilPayloadConverter(), diff --git a/temporalcli/commands.activity.go b/temporalcli/commands.activity.go index f2c26b9ca..6940727ae 100644 --- a/temporalcli/commands.activity.go +++ b/temporalcli/commands.activity.go @@ -50,7 +50,7 @@ func (c *TemporalActivityCompleteCommand) run(cctx *CommandContext, args []strin RunId: c.RunId, ActivityId: c.ActivityId, Result: resultPayloads, - Identity: c.Identity, + Identity: c.Parent.Identity, }) if err != nil { return fmt.Errorf("unable to complete Activity: %w", err) @@ -86,7 +86,7 @@ func (c *TemporalActivityFailCommand) run(cctx *CommandContext, args []string) e Details: detailPayloads, }}, }, - Identity: c.Identity, + Identity: c.Parent.Identity, }) if err != nil { return fmt.Errorf("unable to fail Activity: %w", err) @@ -156,39 +156,79 @@ func (c *TemporalActivityUpdateOptionsCommand) run(cctx *CommandContext, args [] updatePath = append(updatePath, "retry_policy.maximum_attempts") } - result, err := cl.WorkflowService().UpdateActivityOptions(cctx, &workflowservice.UpdateActivityOptionsRequest{ - Namespace: c.Parent.Namespace, - Execution: &common.WorkflowExecution{ - WorkflowId: c.WorkflowId, - RunId: c.RunId, - }, - Activity: &workflowservice.UpdateActivityOptionsRequest_Id{Id: c.ActivityId}, - ActivityOptions: activityOptions, - UpdateMask: &fieldmaskpb.FieldMask{ - Paths: updatePath, - }, - Identity: c.Identity, - }) + opts := SingleWorkflowOrBatchOptions{ + WorkflowId: c.WorkflowId, + RunId: c.RunId, + Query: c.Query, + Reason: c.Reason, + Yes: c.Yes, + Rps: c.Rps, + } + + exec, batchReq, err := opts.workflowExecOrBatch(cctx, c.Parent.Namespace, cl, singleOrBatchOverrides{}) if err != nil { - return fmt.Errorf("unable to update Activity options: %w", err) + return err } - updatedOptions := updateOptionsDescribe{ - TaskQueue: result.GetActivityOptions().TaskQueue.GetName(), + if exec != nil { + result, err := cl.WorkflowService().UpdateActivityOptions(cctx, &workflowservice.UpdateActivityOptionsRequest{ + Namespace: c.Parent.Namespace, + Execution: &common.WorkflowExecution{ + WorkflowId: c.WorkflowId, + RunId: c.RunId, + }, + Activity: &workflowservice.UpdateActivityOptionsRequest_Id{Id: c.ActivityId}, + ActivityOptions: activityOptions, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: updatePath, + }, + Identity: c.Parent.Identity, + }) + if err != nil { + return fmt.Errorf("unable to update Activity options: %w", err) + } - ScheduleToCloseTimeout: result.GetActivityOptions().ScheduleToCloseTimeout.AsDuration(), - ScheduleToStartTimeout: result.GetActivityOptions().ScheduleToStartTimeout.AsDuration(), - StartToCloseTimeout: result.GetActivityOptions().StartToCloseTimeout.AsDuration(), - HeartbeatTimeout: result.GetActivityOptions().HeartbeatTimeout.AsDuration(), + updatedOptions := updateOptionsDescribe{ + TaskQueue: result.GetActivityOptions().TaskQueue.GetName(), - InitialInterval: result.GetActivityOptions().RetryPolicy.InitialInterval.AsDuration(), - BackoffCoefficient: result.GetActivityOptions().RetryPolicy.BackoffCoefficient, - MaximumInterval: result.GetActivityOptions().RetryPolicy.MaximumInterval.AsDuration(), - MaximumAttempts: result.GetActivityOptions().RetryPolicy.MaximumAttempts, - } + ScheduleToCloseTimeout: result.GetActivityOptions().ScheduleToCloseTimeout.AsDuration(), + ScheduleToStartTimeout: result.GetActivityOptions().ScheduleToStartTimeout.AsDuration(), + StartToCloseTimeout: result.GetActivityOptions().StartToCloseTimeout.AsDuration(), + HeartbeatTimeout: result.GetActivityOptions().HeartbeatTimeout.AsDuration(), - _ = cctx.Printer.PrintStructured(updatedOptions, printer.StructuredOptions{}) + InitialInterval: result.GetActivityOptions().RetryPolicy.InitialInterval.AsDuration(), + BackoffCoefficient: result.GetActivityOptions().RetryPolicy.BackoffCoefficient, + MaximumInterval: result.GetActivityOptions().RetryPolicy.MaximumInterval.AsDuration(), + MaximumAttempts: result.GetActivityOptions().RetryPolicy.MaximumAttempts, + } + _ = cctx.Printer.PrintStructured(updatedOptions, printer.StructuredOptions{}) + } else { + updateActivitiesOperation := &batch.BatchOperationUpdateActivityOptions{ + Identity: c.Parent.Identity, + Activity: &batch.BatchOperationUpdateActivityOptions_Type{Type: c.ActivityType}, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: updatePath, + }, + RestoreOriginal: c.RestoreOriginalOptions, + } + + if c.ActivityType != "" { + updateActivitiesOperation.Activity = &batch.BatchOperationUpdateActivityOptions_Type{Type: c.ActivityType} + } else if c.MatchAll { + updateActivitiesOperation.Activity = &batch.BatchOperationUpdateActivityOptions_MatchAll{MatchAll: true} + } else { + return fmt.Errorf("either Activity Type must be provided or MatchAll must be set to true") + } + + batchReq.Operation = &workflowservice.StartBatchOperationRequest_UpdateActivityOptionsOperation{ + UpdateActivityOptionsOperation: updateActivitiesOperation, + } + + if err := startBatchJob(cctx, cl, batchReq); err != nil { + return err + } + } return nil } @@ -205,19 +245,15 @@ func (c *TemporalActivityPauseCommand) run(cctx *CommandContext, args []string) WorkflowId: c.WorkflowId, RunId: c.RunId, }, - Identity: c.Identity, + Identity: c.Parent.Identity, } if c.ActivityId != "" && c.ActivityType != "" { return fmt.Errorf("either Activity Type or Activity Id, but not both") - } - - if c.ActivityType != "" { + } else if c.ActivityType != "" { request.Activity = &workflowservice.PauseActivityRequest_Type{Type: c.ActivityType} } else if c.ActivityId != "" { request.Activity = &workflowservice.PauseActivityRequest_Id{Id: c.ActivityId} - } else { - return fmt.Errorf("either Activity Type or Activity Id must be provided") } _, err = cl.WorkflowService().PauseActivity(cctx, request) @@ -244,10 +280,7 @@ func (c *TemporalActivityUnpauseCommand) run(cctx *CommandContext, args []string Rps: c.Rps, } - exec, batchReq, err := opts.workflowExecOrBatch(cctx, c.Parent.Namespace, cl, singleOrBatchOverrides{ - // You're allowed to specify a reason when terminating a workflow - AllowReasonWithWorkflowID: true, - }) + exec, batchReq, err := opts.workflowExecOrBatch(cctx, c.Parent.Namespace, cl, singleOrBatchOverrides{}) if err != nil { return err } @@ -262,35 +295,31 @@ func (c *TemporalActivityUnpauseCommand) run(cctx *CommandContext, args []string ResetAttempts: c.ResetAttempts, ResetHeartbeat: c.ResetHeartbeats, Jitter: durationpb.New(c.Jitter.Duration()), - Identity: c.Identity, + Identity: c.Parent.Identity, } if c.ActivityId != "" && c.ActivityType != "" { return fmt.Errorf("either Activity Type or Activity Id, but not both") - } - - if c.ActivityType != "" { + } else if c.ActivityType != "" { request.Activity = &workflowservice.UnpauseActivityRequest_Type{Type: c.ActivityType} } else if c.ActivityId != "" { request.Activity = &workflowservice.UnpauseActivityRequest_Id{Id: c.ActivityId} - } else { - return fmt.Errorf("either Activity Type or Activity Id must be provided") } _, err = cl.WorkflowService().UnpauseActivity(cctx, request) if err != nil { - return fmt.Errorf("unable to uppause an Activity: %w", err) + return fmt.Errorf("unable to unpause an Activity: %w", err) } } else { // batch operation unpauseActivitiesOperation := &batch.BatchOperationUnpauseActivities{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, ResetAttempts: c.ResetAttempts, ResetHeartbeat: c.ResetHeartbeats, Jitter: durationpb.New(c.Jitter.Duration()), } if c.ActivityType != "" { unpauseActivitiesOperation.Activity = &batch.BatchOperationUnpauseActivities_Type{Type: c.ActivityType} - } else if c.MatchAll == true { + } else if c.MatchAll { unpauseActivitiesOperation.Activity = &batch.BatchOperationUnpauseActivities_MatchAll{MatchAll: true} } else { return fmt.Errorf("either Activity Type must be provided or MatchAll must be set to true") @@ -315,45 +344,81 @@ func (c *TemporalActivityResetCommand) run(cctx *CommandContext, args []string) } defer cl.Close() - request := &workflowservice.ResetActivityRequest{ - Namespace: c.Parent.Namespace, - Execution: &common.WorkflowExecution{ - WorkflowId: c.WorkflowId, - RunId: c.RunId, - }, - Identity: c.Identity, - KeepPaused: c.KeepPaused, - ResetHeartbeat: c.ResetHeartbeats, + opts := SingleWorkflowOrBatchOptions{ + WorkflowId: c.WorkflowId, + RunId: c.RunId, + Query: c.Query, + Reason: c.Reason, + Yes: c.Yes, + Rps: c.Rps, } - if c.ActivityId != "" && c.ActivityType != "" { - return fmt.Errorf("either Activity Type or Activity Id, but not both") + exec, batchReq, err := opts.workflowExecOrBatch(cctx, c.Parent.Namespace, cl, singleOrBatchOverrides{}) + if err != nil { + return err } - if c.ActivityType != "" { - request.Activity = &workflowservice.ResetActivityRequest_Type{Type: c.ActivityType} - } else if c.ActivityId != "" { - request.Activity = &workflowservice.ResetActivityRequest_Id{Id: c.ActivityId} - } else { - return fmt.Errorf("either Activity Type or Activity Id must be provided") - } + if exec != nil { // single workflow operation + request := &workflowservice.ResetActivityRequest{ + Namespace: c.Parent.Namespace, + Execution: &common.WorkflowExecution{ + WorkflowId: c.WorkflowId, + RunId: c.RunId, + }, + Identity: c.Parent.Identity, + KeepPaused: c.KeepPaused, + ResetHeartbeat: c.ResetHeartbeats, + } - resp, err := cl.WorkflowService().ResetActivity(cctx, request) - if err != nil { - return fmt.Errorf("unable to reset an Activity: %w", err) - } + if c.ActivityId != "" && c.ActivityType != "" { + return fmt.Errorf("either Activity Type or Activity Id, but not both") + } else if c.ActivityType != "" { + request.Activity = &workflowservice.ResetActivityRequest_Type{Type: c.ActivityType} + } else if c.ActivityId != "" { + request.Activity = &workflowservice.ResetActivityRequest_Id{Id: c.ActivityId} + } - resetResponse := struct { - KeepPaused bool `json:"keepPaused"` - ResetHeartbeats bool `json:"resetHeartbeats"` - ServerResponse bool `json:"-"` - }{ - ServerResponse: resp != nil, - KeepPaused: c.KeepPaused, - ResetHeartbeats: c.ResetHeartbeats, - } + resp, err := cl.WorkflowService().ResetActivity(cctx, request) + if err != nil { + return fmt.Errorf("unable to reset an Activity: %w", err) + } - _ = cctx.Printer.PrintStructured(resetResponse, printer.StructuredOptions{}) + resetResponse := struct { + KeepPaused bool `json:"keepPaused"` + ResetHeartbeats bool `json:"resetHeartbeats"` + ServerResponse bool `json:"-"` + }{ + ServerResponse: resp != nil, + KeepPaused: c.KeepPaused, + ResetHeartbeats: c.ResetHeartbeats, + } + + _ = cctx.Printer.PrintStructured(resetResponse, printer.StructuredOptions{}) + } else { // batch operation + resetActivitiesOperation := &batch.BatchOperationResetActivities{ + Identity: c.Parent.Identity, + ResetAttempts: c.ResetAttempts, + ResetHeartbeat: c.ResetHeartbeats, + KeepPaused: c.KeepPaused, + Jitter: durationpb.New(c.Jitter.Duration()), + RestoreOriginalOptions: c.RestoreOriginalOptions, + } + if c.ActivityType != "" { + resetActivitiesOperation.Activity = &batch.BatchOperationResetActivities_Type{Type: c.ActivityType} + } else if c.MatchAll { + resetActivitiesOperation.Activity = &batch.BatchOperationResetActivities_MatchAll{MatchAll: true} + } else { + return fmt.Errorf("either Activity Type must be provided or MatchAll must be set to true") + } + + batchReq.Operation = &workflowservice.StartBatchOperationRequest_ResetActivitiesOperation{ + ResetActivitiesOperation: resetActivitiesOperation, + } + + if err := startBatchJob(cctx, cl, batchReq); err != nil { + return err + } + } return nil } diff --git a/temporalcli/commands.activity_test.go b/temporalcli/commands.activity_test.go index b56dc3130..6ce214472 100644 --- a/temporalcli/commands.activity_test.go +++ b/temporalcli/commands.activity_test.go @@ -356,7 +356,6 @@ func (s *SharedServerSuite) TestUnpauseActivity_BatchSuccess() { var failActivity atomic.Bool failActivity.Store(true) s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { - time.Sleep(100 * time.Millisecond) if failActivity.Load() { return nil, fmt.Errorf("update workflow received non-float input") } @@ -436,3 +435,88 @@ func (s *SharedServerSuite) TestUnpauseActivity_BatchSuccess() { // unblock the activities to let them finish failActivity.Store(false) } + + +func (s *SharedServerSuite) TestResetActivity_BatchSuccess() { + var failActivity atomic.Bool + failActivity.Store(true) + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + if failActivity.Load() { + return nil, fmt.Errorf("update workflow received non-float input") + } + return nil, nil + }) + + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + // override the activity options to allow activity to constantly fail + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + ActivityID: activityId, + StartToCloseTimeout: 1 * time.Minute, + RetryPolicy: &temporal.RetryPolicy{ + MaximumAttempts: 0, + }, + }) + var res any + err := workflow.ExecuteActivity(ctx, DevActivity).Get(ctx, &res) + return res, err + }) + + run1 := waitWorkflowStarted(s) + run2 := waitWorkflowStarted(s) + + // Wait for all to appear in list + query := fmt.Sprintf("WorkflowId = '%s' OR WorkflowId = '%s'", run1.GetID(), run2.GetID()) + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: query, + }) + s.NoError(err) + return len(resp.Executions) == 2 + }, 3*time.Second, 100*time.Millisecond) + + // Pause the activities + res := sendActivityCommand("pause", run1, s, "--activity-id", activityId) + s.NoError(res.Err) + res = sendActivityCommand("pause", run2, s, "--activity-id", activityId) + s.NoError(res.Err) + + // wait for activities to be paused + checkActivitiesPaused(s, run1) + checkActivitiesPaused(s, run2) + + var lastRequestLock sync.Mutex + var startBatchRequest *workflowservice.StartBatchOperationRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + lastRequestLock.Lock() + if r, ok := req.(*workflowservice.StartBatchOperationRequest); ok { + startBatchRequest = r + } + lastRequestLock.Unlock() + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + // Send reset activity unpause + cmdRes := s.Execute("activity", "reset", + "--rps", "1", + "--address", s.Address(), + "--query", query, + "--reason", "unpause-test", + "--yes", "--match-all", + ) + s.NoError(cmdRes.Err) + s.NotEmpty(startBatchRequest.JobId) + + // check activities are running + checkActivitiesRunning(s, run1) + checkActivitiesRunning(s, run2) + + // unblock the activities to let them finish + failActivity.Store(false) +} diff --git a/temporalcli/commands.batch.go b/temporalcli/commands.batch.go index 1f0a1e2c0..93686732c 100644 --- a/temporalcli/commands.batch.go +++ b/temporalcli/commands.batch.go @@ -143,7 +143,7 @@ func (c TemporalBatchTerminateCommand) run(cctx *CommandContext, args []string) Namespace: c.Parent.Namespace, JobId: c.JobId, Reason: c.Reason, - Identity: clientIdentity(), + Identity: c.Parent.Identity, }) var notFound *serviceerror.NotFound diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 4b5d3328b..d88b740e5 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -34,6 +34,7 @@ type ClientOptions struct { CodecEndpoint string CodecAuth string CodecHeader []string + Identity string } func (v *ClientOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { @@ -54,6 +55,7 @@ func (v *ClientOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { f.StringVar(&v.CodecEndpoint, "codec-endpoint", "", "Remote Codec Server endpoint.") f.StringVar(&v.CodecAuth, "codec-auth", "", "Authorization header for Codec Server requests.") f.StringArrayVar(&v.CodecHeader, "codec-header", nil, "HTTP headers for requests to codec server. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers.") + f.StringVar(&v.Identity, "identity", "", "The identity of the user or client submitting this request. Defaults to \"temporal-cli:$USER@$HOST\".") } type OverlapPolicyOptions struct { @@ -427,7 +429,6 @@ type TemporalActivityCompleteCommand struct { WorkflowReferenceOptions ActivityId string Result string - Identity string } func NewTemporalActivityCompleteCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityCompleteCommand { @@ -446,7 +447,6 @@ func NewTemporalActivityCompleteCommand(cctx *CommandContext, parent *TemporalAc _ = cobra.MarkFlagRequired(s.Command.Flags(), "activity-id") s.Command.Flags().StringVar(&s.Result, "result", "", "Result `JSON` to return. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "result") - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.WorkflowReferenceOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -462,7 +462,6 @@ type TemporalActivityFailCommand struct { WorkflowReferenceOptions ActivityId string Detail string - Identity string Reason string } @@ -481,7 +480,6 @@ func NewTemporalActivityFailCommand(cctx *CommandContext, parent *TemporalActivi s.Command.Flags().StringVar(&s.ActivityId, "activity-id", "", "Activity ID to fail. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "activity-id") s.Command.Flags().StringVar(&s.Detail, "detail", "", "Reason for failing the Activity (JSON).") - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for failing the Activity.") s.WorkflowReferenceOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { @@ -514,7 +512,7 @@ func NewTemporalActivityPauseCommand(cctx *CommandContext, parent *TemporalActiv } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to pause. Either `activity-id` or `activity-type` must be provided, but not both.") - s.Command.Flags().StringVarP(&s.ActivityType, "activity-type", "g", "", "All activities of the Activity Type will be paused. Either `activity-id` or `activity-type` must be provided, but not both. Note: Pausing Activity by Type is an experimental feature and may change in the future.") + s.Command.Flags().StringVar(&s.ActivityType, "activity-type", "", "All activities of the Activity Type will be paused. Either `activity-id` or `activity-type` must be provided, but not both. Note: Pausing Activity by Type is an experimental feature and may change in the future.") s.Command.Flags().StringVar(&s.Identity, "identity", "", "The identity of the user or client submitting this request.") s.WorkflowReferenceOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { @@ -528,12 +526,15 @@ func NewTemporalActivityPauseCommand(cctx *CommandContext, parent *TemporalActiv type TemporalActivityResetCommand struct { Parent *TemporalActivityCommand Command cobra.Command - WorkflowReferenceOptions - ActivityId string - ActivityType string - Identity string - KeepPaused bool - ResetHeartbeats bool + SingleWorkflowOrBatchOptions + ActivityId string + ActivityType string + KeepPaused bool + ResetAttempts bool + ResetHeartbeats bool + MatchAll bool + Jitter Duration + RestoreOriginalOptions bool } func NewTemporalActivityResetCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityResetCommand { @@ -543,17 +544,21 @@ func NewTemporalActivityResetCommand(cctx *CommandContext, parent *TemporalActiv s.Command.Use = "reset [flags]" s.Command.Short = "Reset an Activity" if hasHighlighting { - s.Command.Long = "Reset an activity. This restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify \x1b[1mkeep_paused\x1b[0m to prevent this.\n\nIf the activity is paused and the \x1b[1mkeep_paused\x1b[0m flag is not provided,\nit will be unpaused. If the activity is paused and \x1b[1mkeep_paused\x1b[0m flag\nis provided - it will stay paused.\n\nActivities can be specified by their Activity ID or Activity Type.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the \x1b[1mreset_heartbeats\x1b[0m flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity Type of ID and Workflow IDs:\n\n\x1b[1mtemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\x1b[0m" + s.Command.Long = "Reset an activity. This restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify \x1b[1mkeep_paused\x1b[0m to prevent this.\n\nIf the activity is paused and the \x1b[1mkeep_paused\x1b[0m flag is not provided,\nit will be unpaused. If the activity is paused and \x1b[1mkeep_paused\x1b[0m flag\nis provided - it will stay paused.\n\nActivities can be specified by their Activity ID or Activity Type.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the \x1b[1mreset_heartbeats\x1b[0m flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity Type of ID and Workflow IDs:\n\n\x1b[1mtemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\x1b[0m\n\nEither \x1b[1mactivity-id\x1b[0m, \x1b[1mactivity-type\x1b[0m, or \x1b[1m--match-all\x1b[0m must be specified.\n\nActivities can be reset in bulk with a visibility query list filter. \nFor example, if you want to reset activities of type Foo:\n\n\x1b[1mtemporal activity reset \\\n --query 'TemporalResetInfo=\"property:activityType=Foo\"'\x1b[0m" } else { - s.Command.Long = "Reset an activity. This restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify `keep_paused` to prevent this.\n\nIf the activity is paused and the `keep_paused` flag is not provided,\nit will be unpaused. If the activity is paused and `keep_paused` flag\nis provided - it will stay paused.\n\nActivities can be specified by their Activity ID or Activity Type.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the `reset_heartbeats` flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity Type of ID and Workflow IDs:\n\n```\ntemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\n```" + s.Command.Long = "Reset an activity. This restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify `keep_paused` to prevent this.\n\nIf the activity is paused and the `keep_paused` flag is not provided,\nit will be unpaused. If the activity is paused and `keep_paused` flag\nis provided - it will stay paused.\n\nActivities can be specified by their Activity ID or Activity Type.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the `reset_heartbeats` flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity Type of ID and Workflow IDs:\n\n```\ntemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\n```\n\nEither `activity-id`, `activity-type`, or `--match-all` must be specified.\n\nActivities can be reset in bulk with a visibility query list filter. \nFor example, if you want to reset activities of type Foo:\n\n```\ntemporal activity reset \\\n --query 'TemporalResetInfo=\"property:activityType=Foo\"'\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to reset. Either `activity-id` or `activity-type` must be provided, but not both.") - s.Command.Flags().StringVarP(&s.ActivityType, "activity-type", "g", "", "The Activity Type to reset. Either `activity-id` or `activity-type` must be provided, but not both.") - s.Command.Flags().StringVar(&s.Identity, "identity", "", "The identity of the user or client submitting this request.") + s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to reset. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified.") + s.Command.Flags().StringVar(&s.ActivityType, "activity-type", "", "Activities of this Type will be reset. Mutually exclusive with `--match-all` and `activity-id`.") s.Command.Flags().BoolVar(&s.KeepPaused, "keep-paused", false, "If the activity was paused, it will stay paused.") - s.Command.Flags().BoolVar(&s.ResetHeartbeats, "reset-heartbeats", false, "Clear the Activity's heartbeat details.") - s.WorkflowReferenceOptions.buildFlags(cctx, s.Command.Flags()) + s.Command.Flags().BoolVar(&s.ResetAttempts, "reset-attempts", false, "Reset the activity attempts.") + s.Command.Flags().BoolVar(&s.ResetHeartbeats, "reset-heartbeats", false, "Reset the Activity's heartbeats. Only works with --reset-attempts.") + s.Command.Flags().BoolVar(&s.MatchAll, "match-all", false, "Every activity should be reset. Every activity should be updated. Mutually exclusive with `--activity-id` and `--activity-type`.") + s.Jitter = 0 + s.Command.Flags().Var(&s.Jitter, "jitter", "The activity will reset at random a time within the specified duration. Can only be used with --query.") + s.Command.Flags().BoolVar(&s.RestoreOriginalOptions, "restore-original-options", false, "Restore the original options of the activity.") + s.SingleWorkflowOrBatchOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -568,7 +573,6 @@ type TemporalActivityUnpauseCommand struct { SingleWorkflowOrBatchOptions ActivityId string ActivityType string - Identity string ResetAttempts bool ResetHeartbeats bool MatchAll bool @@ -587,14 +591,13 @@ func NewTemporalActivityUnpauseCommand(cctx *CommandContext, parent *TemporalAct s.Command.Long = "Re-schedule a previously-paused Activity for execution.\n\nIf the Activity is not running and is past its retry timeout, it will be\nscheduled immediately. Otherwise, it will be scheduled after its retry\ntimeout expires.\n\nUse `--reset-attempts` to reset the number of previous run attempts to\nzero. For example, if an Activity is near the maximum number of attempts\nN specified in its retry policy, `--reset-attempts` will allow the\nActivity to be retried another N times after unpausing.\n\nUse `--reset-heartbeat` to reset the Activity's heartbeats.\n\nActivities can be specified by their Activity ID or Activity Type.\nOne of those parameters must be provided.\n\nSpecify the Activity ID or Type and Workflow IDs:\n\n```\ntemporal activity unpause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --reset-attempts\n --reset-heartbeats\n```\n\nActivities can be unpaused in bulk via a visibility Query list filter.\nFor example, if you want to unpause activities of type Foo that you\npreviously paused, do:\n\n```\ntemporal activity unpause \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to unpause. Can only be used without --query or --match-all. Either `activity-id` or `activity-type` must be provided, but not both.") - s.Command.Flags().StringVarP(&s.ActivityType, "activity-type", "g", "", "Activities of this Type will unpause. Can only be used without --match-all. Either `activity-id` or `activity-type` must be provided, but not both.") - s.Command.Flags().StringVar(&s.Identity, "identity", "", "The identity of the user or client submitting this request.") - s.Command.Flags().BoolVar(&s.ResetAttempts, "reset-attempts", false, "Also reset the activity attempts.") + s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to unpause. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified.") + s.Command.Flags().StringVar(&s.ActivityType, "activity-type", "", "Activities of this Type will unpause. Can only be used without --match-all. Either `activity-id` or `activity-type` must be provided, but not both.") + s.Command.Flags().BoolVar(&s.ResetAttempts, "reset-attempts", false, "Reset the activity attempts.") s.Command.Flags().BoolVar(&s.ResetHeartbeats, "reset-heartbeats", false, "Reset the Activity's heartbeats. Only works with --reset-attempts.") - s.Command.Flags().BoolVar(&s.MatchAll, "match-all", false, "Every paused activity should be unpaused. This flag is ignored if activity-type is provided. Can only be used with --query.") + s.Command.Flags().BoolVar(&s.MatchAll, "match-all", false, "Every paused activity should be unpaused. This flag is ignored if activity-type is provided.") s.Jitter = 0 - s.Command.Flags().VarP(&s.Jitter, "jitter", "j", "The activity will start at random a time within the specified duration. Can only be used with --query.") + s.Command.Flags().Var(&s.Jitter, "jitter", "The activity will start at random a time within the specified duration. Can only be used with --query.") s.SingleWorkflowOrBatchOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -607,8 +610,10 @@ func NewTemporalActivityUnpauseCommand(cctx *CommandContext, parent *TemporalAct type TemporalActivityUpdateOptionsCommand struct { Parent *TemporalActivityCommand Command cobra.Command - WorkflowReferenceOptions + SingleWorkflowOrBatchOptions ActivityId string + ActivityType string + MatchAll bool TaskQueue string ScheduleToCloseTimeout Duration ScheduleToStartTimeout Duration @@ -618,7 +623,7 @@ type TemporalActivityUpdateOptionsCommand struct { RetryMaximumInterval Duration RetryBackoffCoefficient float32 RetryMaximumAttempts int - Identity string + RestoreOriginalOptions bool } func NewTemporalActivityUpdateOptionsCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityUpdateOptionsCommand { @@ -628,13 +633,14 @@ func NewTemporalActivityUpdateOptionsCommand(cctx *CommandContext, parent *Tempo s.Command.Use = "update-options [flags]" s.Command.Short = "Update Activity options" if hasHighlighting { - s.Command.Long = "Update the options of a running Activity that were passed into it from\n a Workflow. Updates are incremental, only changing the specified\n options.\n\nFor example:\n\n\x1b[1mtemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\x1b[0m\n\nYou may follow this command with \x1b[1mtemporal activity reset\x1b[0m, and the new values will apply after the reset." + s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified \noptions.\n\nFor example:\n\n\x1b[1mtemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\x1b[0m\n\nYou may follow this command with \x1b[1mtemporal activity reset\x1b[0m, and the new values will apply after the reset.\n\nEither \x1b[1mactivity-id\x1b[0m, \x1b[1mactivity-type\x1b[0m, or \x1b[1m--match-all\x1b[0m must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n\x1b[1mtemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\x1b[0m" } else { - s.Command.Long = "Update the options of a running Activity that were passed into it from\n a Workflow. Updates are incremental, only changing the specified\n options.\n\nFor example:\n\n```\ntemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\n```\n\nYou may follow this command with `temporal activity reset`, and the new values will apply after the reset." + s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified \noptions.\n\nFor example:\n\n```\ntemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\n```\n\nYou may follow this command with `temporal activity reset`, and the new values will apply after the reset.\n\nEither `activity-id`, `activity-type`, or `--match-all` must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n```\ntemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.ActivityId, "activity-id", "", "Activity ID. Required.") - _ = cobra.MarkFlagRequired(s.Command.Flags(), "activity-id") + s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to update options. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified.") + s.Command.Flags().StringVar(&s.ActivityType, "activity-type", "", "Activities of this Type will be updated. Mutually exclusive with `--match-all` and `activity-id`.") + s.Command.Flags().BoolVar(&s.MatchAll, "match-all", false, "Every activity should be updated. Mutually exclusive with `--activity-id` and `--activity-type`.") s.Command.Flags().StringVar(&s.TaskQueue, "task-queue", "", "Name of the task queue for the Activity.") s.ScheduleToCloseTimeout = 0 s.Command.Flags().Var(&s.ScheduleToCloseTimeout, "schedule-to-close-timeout", "Indicates how long the caller is willing to wait for an activity completion. Limits how long retries will be attempted.") @@ -650,8 +656,8 @@ func NewTemporalActivityUpdateOptionsCommand(cctx *CommandContext, parent *Tempo s.Command.Flags().Var(&s.RetryMaximumInterval, "retry-maximum-interval", "Maximum interval between retries. Exponential backoff leads to interval increase. This value is the cap of the increase.") s.Command.Flags().Float32Var(&s.RetryBackoffCoefficient, "retry-backoff-coefficient", 0, "Coefficient used to calculate the next retry interval. The next retry interval is previous interval multiplied by the backoff coefficient. Must be 1 or larger.") s.Command.Flags().IntVar(&s.RetryMaximumAttempts, "retry-maximum-attempts", 0, "Maximum number of attempts. When exceeded the retries stop even if not expired yet. Setting this value to 1 disables retries. Setting this value to 0 means unlimited attempts(up to the timeouts).") - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") - s.WorkflowReferenceOptions.buildFlags(cctx, s.Command.Flags()) + s.Command.Flags().BoolVar(&s.RestoreOriginalOptions, "restore-original-options", false, "Restore the original options of the activity.") + s.SingleWorkflowOrBatchOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -2791,7 +2797,6 @@ type TemporalWorkerDeploymentDeleteCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command DeploymentNameOptions - Identity string } func NewTemporalWorkerDeploymentDeleteCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentDeleteCommand { @@ -2806,7 +2811,6 @@ func NewTemporalWorkerDeploymentDeleteCommand(cctx *CommandContext, parent *Temp s.Command.Long = "```\n+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n```\n\nRemove a Worker Deployment given its Deployment Name.\nA Deployment can only be deleted if it has no Version in it.\n\n```\ntemporal worker deployment delete [options]\n```\n\nFor example, setting the user identity that removed the deployment:\n\n```\ntemporal worker deployment delete \\\n --name YourDeploymentName \\\n --identity YourIdentity\n\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.DeploymentNameOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -2820,7 +2824,6 @@ type TemporalWorkerDeploymentDeleteVersionCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command DeploymentVersionOptions - Identity string SkipDrainage bool } @@ -2836,7 +2839,6 @@ func NewTemporalWorkerDeploymentDeleteVersionCommand(cctx *CommandContext, paren s.Command.Long = "```\n+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n```\n\nRemove a Worker Deployment Version given its fully-qualified identifier.\nThis is rarely needed during normal operation\nsince unused Versions are eventually garbage collected.\nThe client can delete a Version only when all of the following conditions\nare met:\n - It is not the Current or Ramping Version for this Deployment.\n - It has no active pollers, i.e., none of the task queues in the\n Version have pollers.\n - It is not draining. This requirement can be ignored with the option\n`--skip-drainage`.\n```\ntemporal worker deployment delete-version [options]\n```\n\nFor example, skipping the drainage restriction:\n\n```\ntemporal worker deployment delete-version \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --skip-drainage\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.Command.Flags().BoolVar(&s.SkipDrainage, "skip-drainage", false, "Ignore the deletion requirement of not draining.") s.DeploymentVersionOptions.buildFlags(cctx, s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { @@ -2930,7 +2932,6 @@ type TemporalWorkerDeploymentSetCurrentVersionCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command DeploymentVersionOrUnversionedOptions - Identity string IgnoreMissingTaskQueues bool Yes bool } @@ -2947,7 +2948,6 @@ func NewTemporalWorkerDeploymentSetCurrentVersionCommand(cctx *CommandContext, p s.Command.Long = "```\n+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n```\n\nSet the Current Version for a Deployment.\nWhen a Version is current, Workers of that Deployment Version will receive\ntasks from new Workflows, and from existing AutoUpgrade Workflows that\nare running on this Deployment.\n\nIf not all the expected Task Queues are being polled by Workers in the\nnew Version the request will fail. To override this protection use\n`--ignore-missing-task-queues`. Note that this would ignore task queues\nin a deployment that are not yet discovered, leading to inconsistent task\nqueue configuration.\n\n```\ntemporal worker deployment set-current-version [options]\n```\n\nFor example, to set the Current Version of a deployment\n`YourDeploymentName`, with a version with Build ID `YourBuildID`, and\nin the default namespace:\n\n```\ntemporal worker deployment set-current-version \\\n --deployment-name YourDeploymentName --build-id YourBuildID\n```\n\nThe target of set-current-version can also be unversioned workers:\n\n```\ntemporal worker deployment set-current-version \\\n --deployment-name YourDeploymentName --unversioned\n```" } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.Command.Flags().BoolVar(&s.IgnoreMissingTaskQueues, "ignore-missing-task-queues", false, "Override protection to accidentally remove task queues.") s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm set Current Version.") s.DeploymentVersionOrUnversionedOptions.buildFlags(cctx, s.Command.Flags()) @@ -2965,7 +2965,6 @@ type TemporalWorkerDeploymentSetRampingVersionCommand struct { DeploymentVersionOrUnversionedOptions Percentage float32 Delete bool - Identity string IgnoreMissingTaskQueues bool Yes bool } @@ -2984,7 +2983,6 @@ func NewTemporalWorkerDeploymentSetRampingVersionCommand(cctx *CommandContext, p s.Command.Args = cobra.NoArgs 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().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") s.Command.Flags().BoolVar(&s.IgnoreMissingTaskQueues, "ignore-missing-task-queues", false, "Override protection to accidentally remove task queues.") s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm set Ramping Version.") s.DeploymentVersionOrUnversionedOptions.buildFlags(cctx, s.Command.Flags()) diff --git a/temporalcli/commands.worker.deployment.go b/temporalcli/commands.worker.deployment.go index 4f9a11d89..072fd838f 100644 --- a/temporalcli/commands.worker.deployment.go +++ b/temporalcli/commands.worker.deployment.go @@ -415,7 +415,7 @@ func (c *TemporalWorkerDeploymentDeleteCommand) run(cctx *CommandContext, args [ _, err = cl.WorkerDeploymentClient().Delete(cctx, client.WorkerDeploymentDeleteOptions{ Name: c.Name, - Identity: c.Identity, + Identity: c.Parent.Parent.Identity, }) if err != nil { return fmt.Errorf("error deleting worker deployment: %w", err) @@ -503,7 +503,7 @@ func (c *TemporalWorkerDeploymentDeleteVersionCommand) run(cctx *CommandContext, _, err = dHandle.DeleteVersion(cctx, client.WorkerDeploymentDeleteVersionOptions{ BuildID: c.BuildId, SkipDrainage: c.SkipDrainage, - Identity: c.Identity, + Identity: c.Parent.Parent.Identity, }) if err != nil { return fmt.Errorf("error deleting worker deployment version: %w", err) @@ -556,7 +556,7 @@ func (c *TemporalWorkerDeploymentSetCurrentVersionCommand) run(cctx *CommandCont dHandle := cl.WorkerDeploymentClient().GetHandle(c.DeploymentName) _, err = dHandle.SetCurrentVersion(cctx, client.WorkerDeploymentSetCurrentVersionOptions{ BuildID: c.BuildId, - Identity: c.Identity, + Identity: c.Parent.Parent.Identity, IgnoreMissingTaskQueues: c.IgnoreMissingTaskQueues, ConflictToken: token, }) @@ -594,7 +594,7 @@ func (c *TemporalWorkerDeploymentSetRampingVersionCommand) run(cctx *CommandCont BuildID: c.BuildId, Percentage: percentage, ConflictToken: token, - Identity: c.Identity, + Identity: c.Parent.Parent.Identity, IgnoreMissingTaskQueues: c.IgnoreMissingTaskQueues, }) if err != nil { diff --git a/temporalcli/commands.workflow.go b/temporalcli/commands.workflow.go index 4be89054c..6b9b8714b 100644 --- a/temporalcli/commands.workflow.go +++ b/temporalcli/commands.workflow.go @@ -50,7 +50,7 @@ func (c *TemporalWorkflowCancelCommand) run(cctx *CommandContext, args []string) } else { // batchReq != nil batchReq.Operation = &workflowservice.StartBatchOperationRequest_CancellationOperation{ CancellationOperation: &batch.BatchOperationCancellation{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, }, } if err := startBatchJob(cctx, cl, batchReq); err != nil { @@ -85,7 +85,7 @@ func (c *TemporalWorkflowDeleteCommand) run(cctx *CommandContext, args []string) } else { // batchReq != nil batchReq.Operation = &workflowservice.StartBatchOperationRequest_DeletionOperation{ DeletionOperation: &batch.BatchOperationDeletion{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, }, } if err := startBatchJob(cctx, cl, batchReq); err != nil { @@ -174,7 +174,7 @@ func (c *TemporalWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args [] batchReq.Operation = &workflowservice.StartBatchOperationRequest_UpdateWorkflowOptionsOperation{ UpdateWorkflowOptionsOperation: &batch.BatchOperationUpdateWorkflowExecutionOptions{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{ VersioningOverride: protoVerOverride, }, @@ -223,7 +223,7 @@ func (c *TemporalWorkflowSignalCommand) run(cctx *CommandContext, args []string) WorkflowExecution: &common.WorkflowExecution{WorkflowId: c.WorkflowId, RunId: c.RunId}, SignalName: c.Name, Input: input, - Identity: clientIdentity(), + Identity: c.Parent.Identity, }) if err != nil { return fmt.Errorf("failed signalling workflow: %w", err) @@ -234,7 +234,7 @@ func (c *TemporalWorkflowSignalCommand) run(cctx *CommandContext, args []string) SignalOperation: &batch.BatchOperationSignal{ Signal: c.Name, Input: input, - Identity: clientIdentity(), + Identity: c.Parent.Identity, }, } if err := startBatchJob(cctx, cl, batchReq); err != nil { @@ -287,7 +287,7 @@ func (c *TemporalWorkflowTerminateCommand) run(cctx *CommandContext, _ []string) } else { // batchReq != nil batchReq.Operation = &workflowservice.StartBatchOperationRequest_TerminationOperation{ TerminationOperation: &batch.BatchOperationTermination{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, }, } if err := startBatchJob(cctx, cl, batchReq); err != nil { @@ -386,7 +386,7 @@ func (c *TemporalWorkflowUpdateDescribeCommand) run(cctx *CommandContext, args [ }, UpdateId: c.UpdateId, }, - Identity: clientIdentity(), + Identity: c.Parent.Parent.Identity, // WaitPolicy omitted intentionally for nonblocking } resp, err := cl.WorkflowService().PollWorkflowExecutionUpdate(cctx, pollReq) diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index 4c5be6d20..7580d3c2f 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -173,7 +173,7 @@ func (c *TemporalWorkflowSignalWithStartCommand) run(cctx *CommandContext, _ []s WorkflowTaskTimeout: durationpb.New(wfStartOpts.WorkflowTaskTimeout), SignalName: c.SignalName, SignalInput: signalInput, - Identity: clientIdentity(), + Identity: c.Parent.Identity, RetryPolicy: retryPolicy, CronSchedule: wfStartOpts.CronSchedule, Memo: memo, diff --git a/temporalcli/commands.workflow_reset.go b/temporalcli/commands.workflow_reset.go index 47112a2c0..9a4b6f135 100644 --- a/temporalcli/commands.workflow_reset.go +++ b/temporalcli/commands.workflow_reset.go @@ -130,7 +130,7 @@ func (c *TemporalWorkflowResetCommand) runBatchResetWithPostOps(cctx *CommandCon } request.Operation = &workflowservice.StartBatchOperationRequest_ResetOperation{ ResetOperation: &batch.BatchOperationReset{ - Identity: clientIdentity(), + Identity: c.Parent.Identity, Options: batchResetOptions, PostResetOperations: postOps, }, diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index bcfed3303..3a0d718dc 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -304,9 +304,6 @@ commands: type: string description: Result `JSON` to return. required: true - - name: identity - type: string - description: Identity of the user submitting this request. option-sets: - workflow-reference @@ -329,9 +326,6 @@ commands: - name: detail type: string description: Reason for failing the Activity (JSON). - - name: identity - type: string - description: Identity of the user submitting this request. - name: reason type: string description: Reason for failing the Activity. @@ -342,8 +336,8 @@ commands: summary: Update Activity options description: | Update the options of a running Activity that were passed into it from - a Workflow. Updates are incremental, only changing the specified - options. + a Workflow. Updates are incremental, only changing the specified + options. For example: @@ -363,11 +357,32 @@ commands: ``` You may follow this command with `temporal activity reset`, and the new values will apply after the reset. + + Either `activity-id`, `activity-type`, or `--match-all` must be specified. + + Activity options can be updated in bulk with a visibility query list filter. + For example, if you want to reset for activities of type Foo, do: + + ``` + temporal activity update-options \ + --query 'TemporalPauseInfo="property:activityType=Foo"' + ... + ``` options: + - name: activity-id + short: a type: string - description: Activity ID. - required: true + description: | + The Activity ID to update options. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified. + - name: activity-type + type: string + description: | + Activities of this Type will be updated. Mutually exclusive with `--match-all` and `activity-id`. + - name: match-all + type: bool + description: | + Every activity should be updated. Mutually exclusive with `--activity-id` and `--activity-type`. - name: task-queue type: string description: Name of the task queue for the Activity. @@ -420,11 +435,11 @@ commands: expired yet. Setting this value to 1 disables retries. Setting this value to 0 means unlimited attempts(up to the timeouts). - - name: identity - type: string - description: Identity of the user submitting this request. + - name: restore-original-options + type: bool + description: Restore the original options of the activity. option-sets: - - workflow-reference + - single-workflow-or-batch - name: temporal activity pause summary: Pause an Activity @@ -458,7 +473,6 @@ commands: type: string description: The Activity ID to pause. Either `activity-id` or `activity-type` must be provided, but not both. - name: activity-type - short: g type: string description: | All activities of the Activity Type will be paused. Either `activity-id` or `activity-type` must be provided, but not both. @@ -511,18 +525,14 @@ commands: short: a type: string description: | - The Activity ID to unpause. Can only be used without --query or --match-all. Either `activity-id` or `activity-type` must be provided, but not both. + The Activity ID to unpause. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified. - name: activity-type - short: g type: string description: | Activities of this Type will unpause. Can only be used without --match-all. Either `activity-id` or `activity-type` must be provided, but not both. - - name: identity - type: string - description: The identity of the user or client submitting this request. - name: reset-attempts type: bool - description: Also reset the activity attempts. + description: Reset the activity attempts. - name: reset-heartbeats type: bool description: | @@ -531,10 +541,9 @@ commands: type: bool description: | Every paused activity should be unpaused. This flag is ignored if - activity-type is provided. Can only be used with --query. + activity-type is provided. - name: jitter type: duration - short: j description: | The activity will start at random a time within the specified duration. Can only be used with --query. @@ -582,26 +591,49 @@ commands: --keep-paused --reset-heartbeats ``` + + Either `activity-id`, `activity-type`, or `--match-all` must be specified. + + Activities can be reset in bulk with a visibility query list filter. + For example, if you want to reset activities of type Foo: + + ``` + temporal activity reset \ + --query 'TemporalResetInfo="property:activityType=Foo"' + ``` options: - name: activity-id short: a type: string - description: The Activity ID to reset. Either `activity-id` or `activity-type` must be provided, but not both. + description: The Activity ID to reset. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified. - name: activity-type - short: g - type: string - description: The Activity Type to reset. Either `activity-id` or `activity-type` must be provided, but not both. - - name: identity type: string - description: The identity of the user or client submitting this request. + description: Activities of this Type will be reset. Mutually exclusive with `--match-all` and `activity-id`. - name: keep-paused type: bool description: If the activity was paused, it will stay paused. + - name: reset-attempts + type: bool + description: Reset the activity attempts. - name: reset-heartbeats type: bool - description: Clear the Activity's heartbeat details. + description: | + Reset the Activity's heartbeats. Only works with --reset-attempts. + - name: match-all + type: bool + description: | + Every activity should be reset. Every activity should be updated. Mutually exclusive with `--activity-id` and `--activity-type`. + - name: jitter + type: duration + description: | + The activity will reset at random a time within the specified duration. + Can only be used with --query. + - name: restore-original-options + type: bool + description: | + Restore the original options of the activity. option-sets: - - workflow-reference + - single-workflow-or-batch - name: temporal batch summary: Manage running batch jobs @@ -968,10 +1000,6 @@ commands: ``` option-sets: - deployment-name - options: - - name: identity - type: string - description: Identity of the user submitting this request. - name: temporal worker deployment list summary: Enumerate Worker Deployments in the client's namespace @@ -1060,9 +1088,6 @@ commands: option-sets: - deployment-version options: - - name: identity - type: string - description: Identity of the user submitting this request. - name: skip-drainage type: bool description: Ignore the deletion requirement of not draining. @@ -1110,9 +1135,6 @@ commands: option-sets: - deployment-version-or-unversioned options: - - name: identity - type: string - description: Identity of the user submitting this request. - name: ignore-missing-task-queues type: bool description: Override protection to accidentally remove task queues. @@ -1180,9 +1202,6 @@ commands: - name: delete type: bool description: Delete the Ramping Version. - - name: identity - type: string - description: Identity of the user submitting this request. - name: ignore-missing-task-queues type: bool description: Override protection to accidentally remove task queues. @@ -4204,6 +4223,9 @@ option-sets: HTTP headers for requests to codec server. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers. + - name: identity + type: string + description: The identity of the user or client submitting this request. Defaults to "temporal-cli:$USER@$HOST". - name: overlap-policy options: From ecd709e56ebcb3a528851fab08c6a2d5adabc592 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 1 Aug 2025 14:22:58 -0600 Subject: [PATCH 2/7] Fix default value for address (#839) Not changing the actual default, just what is printed in the help output. --- temporalcli/commands.gen.go | 2 +- temporalcli/commandsgen/commands.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index d88b740e5..017dccd7b 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -38,7 +38,7 @@ type ClientOptions struct { } func (v *ClientOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { - f.StringVar(&v.Address, "address", "127.0.0.1:7233", "Temporal Service gRPC endpoint.") + f.StringVar(&v.Address, "address", "localhost:7233", "Temporal Service gRPC endpoint.") f.StringVar(&v.ClientAuthority, "client-authority", "", "Temporal gRPC client :authority pseudoheader.") f.StringVarP(&v.Namespace, "namespace", "n", "default", "Temporal Service Namespace.") f.StringVar(&v.ApiKey, "api-key", "", "API key for request.") diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 3a0d718dc..8634f9233 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -4135,7 +4135,7 @@ option-sets: - name: address type: string description: Temporal Service gRPC endpoint. - default: 127.0.0.1:7233 + default: localhost:7233 implied-env: TEMPORAL_ADDRESS - name: client-authority type: string From daf3c284d11367ee76deb48126691fa33e32728d Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Mon, 4 Aug 2025 15:40:50 -0600 Subject: [PATCH 3/7] Support multiple --input-meta flags for the same key (#836) Closes #833 --------- Co-authored-by: Spencer Judge --- temporalcli/commands.activity.go | 4 +-- temporalcli/commands.gen.go | 2 +- temporalcli/commands.workflow_exec.go | 14 ++++++++-- temporalcli/commands.workflow_test.go | 40 +++++++++++++++++++++++++++ temporalcli/commandsgen/commands.yml | 1 + temporalcli/payload.go | 23 +++++++++++---- 6 files changed, 74 insertions(+), 10 deletions(-) diff --git a/temporalcli/commands.activity.go b/temporalcli/commands.activity.go index 6940727ae..d9550b319 100644 --- a/temporalcli/commands.activity.go +++ b/temporalcli/commands.activity.go @@ -38,7 +38,7 @@ func (c *TemporalActivityCompleteCommand) run(cctx *CommandContext, args []strin } defer cl.Close() - metadata := map[string][]byte{"encoding": []byte("json/plain")} + metadata := map[string][][]byte{"encoding": {[]byte("json/plain")}} resultPayloads, err := CreatePayloads([][]byte{[]byte(c.Result)}, metadata, false) if err != nil { return err @@ -67,7 +67,7 @@ func (c *TemporalActivityFailCommand) run(cctx *CommandContext, args []string) e var detailPayloads *common.Payloads if len(c.Detail) > 0 { - metadata := map[string][]byte{"encoding": []byte("json/plain")} + metadata := map[string][][]byte{"encoding": {[]byte("json/plain")}} detailPayloads, err = CreatePayloads([][]byte{[]byte(c.Detail)}, metadata, false) if err != nil { return err diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 017dccd7b..094f85d89 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -248,7 +248,7 @@ type PayloadInputOptions struct { func (v *PayloadInputOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { f.StringArrayVarP(&v.Input, "input", "i", nil, "Input value. Use JSON content or set --input-meta to override. Can't be combined with --input-file. Can be passed multiple times to pass multiple arguments.") f.StringArrayVar(&v.InputFile, "input-file", nil, "A path or paths for input file(s). Use JSON content or set --input-meta to override. Can't be combined with --input. Can be passed multiple times to pass multiple arguments.") - f.StringArrayVar(&v.InputMeta, "input-meta", nil, "Input payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times.") + f.StringArrayVar(&v.InputMeta, "input-meta", nil, "Input payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times. Repeated metadata keys are applied to the corresponding inputs in the provided order.") f.BoolVar(&v.InputBase64, "input-base64", false, "Assume inputs are base64-encoded and attempt to decode them.") } diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index 7580d3c2f..ff9f73443 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -623,13 +623,23 @@ func (p *PayloadInputOptions) buildRawInputPayloads() (*common.Payloads, error) } // Build metadata - metadata := map[string][]byte{"encoding": []byte("json/plain")} + metadata := map[string][][]byte{} for _, meta := range p.InputMeta { metaPieces := strings.SplitN(meta, "=", 2) if len(metaPieces) != 2 { return nil, fmt.Errorf("metadata %v expected to have '='", meta) } - metadata[metaPieces[0]] = []byte(metaPieces[1]) + if vals, ok := metadata[metaPieces[0]]; ok { + if len(vals) == len(inData) { + return nil, fmt.Errorf("received more --input-meta flags for key %q than number of inputs", metaPieces[0]) + } + metadata[metaPieces[0]] = append(vals, []byte(metaPieces[1])) + } else { + metadata[metaPieces[0]] = [][]byte{[]byte(metaPieces[1])} + } + } + if _, ok := metadata["encoding"]; !ok { + metadata["encoding"] = [][]byte{[]byte("json/plain")} } return CreatePayloads(inData, metadata, p.InputBase64) } diff --git a/temporalcli/commands.workflow_test.go b/temporalcli/commands.workflow_test.go index c1a5b7bfd..775b7000e 100644 --- a/temporalcli/commands.workflow_test.go +++ b/temporalcli/commands.workflow_test.go @@ -56,6 +56,46 @@ func (s *SharedServerSuite) TestWorkflow_Signal_SingleWorkflowSuccess() { s.Equal(map[string]any{"foo": "bar"}, actual) } +func (s *SharedServerSuite) TestWorkflow_Signal_MultipleInputsWithComplexMetadata() { + // Start a random workflow + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{TaskQueue: s.Worker().Options.TaskQueue}, + DevWorkflow, + "ignored", + ) + s.NoError(err) + + // Send signal + res := s.Execute( + "workflow", "signal", + "--address", s.Address(), + "-w", run.GetID(), + "--name", "my-signal", + "-i", `{"foo": "bar"}`, + "-i", `{"bar": "baz"}`, + "--input-meta", "encoding=json/proto", + "--input-meta", "messageType=foo", + "--input-meta", "messageType=bar", + ) + s.NoError(res.Err) + s.NoError(s.Client.TerminateWorkflow(s.Context, run.GetID(), "", "")) + iter := s.Client.GetWorkflowHistory(s.Context, run.GetID(), "", false, enums.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT) + for iter.HasNext() { + ev, err := iter.Next() + s.NoError(err) + if attr := ev.GetWorkflowExecutionSignaledEventAttributes(); attr != nil { + payloads := attr.GetInput().GetPayloads() + s.Equal("json/proto", string(payloads[0].Metadata["encoding"])) + s.Equal("json/proto", string(payloads[1].Metadata["encoding"])) + s.Equal("foo", string(payloads[0].Metadata["messageType"])) + s.Equal("bar", string(payloads[1].Metadata["messageType"])) + } + return + } + s.Fail("No signal event found in workflow history") +} + func (s *SharedServerSuite) TestWorkflow_Signal_BatchWorkflowSuccess() { res := s.testSignalBatchWorkflow(false) s.Contains(res.Stdout.String(), "approximately 5 workflow(s)") diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 8634f9233..582cc0bf1 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -4531,6 +4531,7 @@ option-sets: Input payload metadata as a `KEY=VALUE` pair. When the KEY is "encoding", this overrides the default ("json/plain"). Can be passed multiple times. + Repeated metadata keys are applied to the corresponding inputs in the provided order. - name: input-base64 type: bool description: | diff --git a/temporalcli/payload.go b/temporalcli/payload.go index cb5eb1eda..a2f3bc9d8 100644 --- a/temporalcli/payload.go +++ b/temporalcli/payload.go @@ -9,12 +9,25 @@ import ( "go.temporal.io/api/common/v1" ) -func CreatePayloads(data [][]byte, metadata map[string][]byte, isBase64 bool) (*common.Payloads, error) { +// CreatePayloads creates API Payload objects from given data and metadata slices. +// If metadata has an entry at a data index, it is used, otherwise it uses the metadata entry at index 0. +func CreatePayloads(data [][]byte, metadata map[string][][]byte, isBase64 bool) (*common.Payloads, error) { ret := &common.Payloads{Payloads: make([]*common.Payload, len(data))} for i, in := range data { - // If it's JSON, validate it - if strings.HasPrefix(string(metadata["encoding"]), "json/") && !json.Valid(in) { - return nil, fmt.Errorf("input #%v is not valid JSON", i+1) + var metadataForIndex = make(map[string][]byte, len(metadata)) + for k, vals := range metadata { + if len(vals) == 0 { + continue + } + v := vals[0] + if len(vals) > i { + v = vals[i] + } + // If it's JSON, validate it + if k == "encoding" && strings.HasPrefix(string(v), "json/") && !json.Valid(in) { + return nil, fmt.Errorf("input #%v is not valid JSON", i+1) + } + metadataForIndex[k] = v } // Decode base64 if base64'd (std encoding only for now) if isBase64 { @@ -23,7 +36,7 @@ func CreatePayloads(data [][]byte, metadata map[string][]byte, isBase64 bool) (* return nil, fmt.Errorf("input #%v is not valid base64", i+1) } } - ret.Payloads[i] = &common.Payload{Data: in, Metadata: metadata} + ret.Payloads[i] = &common.Payload{Data: in, Metadata: metadataForIndex} } return ret, nil } From 21fad54aaf01319b2bb366319458454063711449 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 27 Aug 2025 10:21:48 -0700 Subject: [PATCH 4/7] Add priority options to workflow starting (#843) --- go.mod | 12 ++-- go.sum | 70 ++++------------------ temporalcli/commands.gen.go | 6 ++ temporalcli/commands.workflow_exec.go | 6 ++ temporalcli/commands.workflow_exec_test.go | 48 +++++++++++++++ temporalcli/commandsgen/commands.yml | 29 +++++++-- 6 files changed, 100 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 26af6c26c..962d5464a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/temporalio/cli -go 1.24.4 +go 1.24.5 require ( github.com/BurntSushi/toml v1.4.0 @@ -16,10 +16,10 @@ require ( github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/temporalio/ui-server/v2 v2.39.0 - go.temporal.io/api v1.51.0 - go.temporal.io/sdk v1.35.0 + go.temporal.io/api v1.52.0 + go.temporal.io/sdk v1.36.0 go.temporal.io/sdk/contrib/envconfig v0.1.0 - go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9 + go.temporal.io/server v1.29.0-139.3 google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 @@ -27,7 +27,7 @@ require ( ) require ( - cel.dev/expr v0.22.1 // indirect + cel.dev/expr v0.23.1 // indirect cloud.google.com/go v0.120.0 // indirect cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -76,7 +76,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect diff --git a/go.sum b/go.sum index 63dc1f148..c6e37dc53 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ -cel.dev/expr v0.22.1 h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI= -cel.dev/expr v0.22.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= +cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= @@ -54,7 +53,6 @@ github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07 github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/benbjohnson/clock v0.0.0-20160125162948-a620c1cc9866/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -75,11 +73,8 @@ github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsj github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= @@ -98,16 +93,12 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= @@ -126,8 +117,6 @@ github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -136,7 +125,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus= github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -144,14 +132,9 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -159,7 +142,6 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -183,8 +165,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -288,7 +270,6 @@ github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a h1:AA9vgIBDjM github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a/go.mod h1:lzZQ3Noex5pfAy7mkAeCjcBDteYU85uWWnJ/y6gKU8k= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= @@ -402,14 +383,14 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.51.0 h1:9+e14GrIa7nWoWoudqj/PSwm33yYjV+u8TAR9If7s/g= -go.temporal.io/api v1.51.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= -go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= +go.temporal.io/api v1.52.0 h1:Tn69z2nhQeXtofa1/j/MbwPHnFRM9+13xqYmFl/KFjM= +go.temporal.io/api v1.52.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.36.0 h1:WO9zetpybBNK7xsQth4Z+3Zzw1zSaM9MOUGrnnUjZMo= +go.temporal.io/sdk v1.36.0/go.mod h1:8BxGRF0LcQlfQrLLGkgVajbsKUp/PY7280XTdcKc18Y= go.temporal.io/sdk/contrib/envconfig v0.1.0 h1:s+G/Ujph+Xl2jzLiiIm2T1vuijDkUL4Kse49dgDVGBE= go.temporal.io/sdk/contrib/envconfig v0.1.0/go.mod h1:FQEO3C56h9C7M6sDgSanB8HnBTmopw9qgVx4F1S6pJk= -go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9 h1:jJV/LmX6msjAQj+TrPIM+qVZZMu8EPnLENM4nNiJq9k= -go.temporal.io/server v1.29.0-135.0.0.20250725000618-7e01f6c035c9/go.mod h1:qRq3Ei+nk7eXw+Dw60GaHdCDo7dbbqGa7LnSJqPaIlk= +go.temporal.io/server v1.29.0-139.3 h1:j4u7esLvLqzE67Q6s/ExvaFKOensFhskCWXe1VHyBdU= +go.temporal.io/server v1.29.0-139.3/go.mod h1:OKFCSB0xxCX1Bmi2rYutWJEKppIrwuqx+c3e5GeY54w= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -418,19 +399,16 @@ go.uber.org/dig v1.18.1 h1:rLww6NuajVjeQn+49u5NcezUJEGwd5uXmyoCKW2g5Es= go.uber.org/dig v1.18.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -443,7 +421,6 @@ golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -452,9 +429,6 @@ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+ golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -466,9 +440,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -482,11 +453,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -495,7 +463,6 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -507,7 +474,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -535,16 +501,12 @@ golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -563,22 +525,12 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw= google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0= google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= @@ -601,8 +553,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 094f85d89..d186826d3 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -198,6 +198,9 @@ type SharedWorkflowStartOptions struct { Memo []string StaticSummary string StaticDetails string + PriorityKey int + FairnessKey string + FairnessWeight float32 } func (v *SharedWorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { @@ -216,6 +219,9 @@ func (v *SharedWorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.F f.StringArrayVar(&v.Memo, "memo", nil, "Memo using 'KEY=\"VALUE\"' pairs. Use JSON values.") f.StringVar(&v.StaticSummary, "static-summary", "", "Static Workflow summary for human consumption in UIs. Uses Temporal Markdown formatting, should be a single line. EXPERIMENTAL.") f.StringVar(&v.StaticDetails, "static-details", "", "Static Workflow details for human consumption in UIs. Uses Temporal Markdown formatting, may be multiple lines. EXPERIMENTAL.") + f.IntVar(&v.PriorityKey, "priority-key", 0, "Priority key (1-5, lower numbers = higher priority). Tasks in a queue should be processed in close-to-priority-order. Default is 3 when not specified.") + f.StringVar(&v.FairnessKey, "fairness-key", "", "Fairness key (max 64 bytes) for proportional task dispatch. Tasks with same key share capacity based on their weight.") + f.Float32Var(&v.FairnessWeight, "fairness-weight", 0, "Weight [0.001-1000] for this fairness key. Keys are dispatched proportionally to their weights.") } type WorkflowStartOptions struct { diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index ff9f73443..fce190b25 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -24,6 +24,7 @@ import ( "go.temporal.io/api/temporalproto" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" + "go.temporal.io/sdk/temporal" "google.golang.org/protobuf/types/known/durationpb" ) @@ -560,6 +561,11 @@ func buildStartOptions(sw *SharedWorkflowStartOptions, w *WorkflowStartOptions) StartDelay: w.StartDelay.Duration(), StaticSummary: sw.StaticSummary, StaticDetails: sw.StaticDetails, + Priority: temporal.Priority{ + PriorityKey: sw.PriorityKey, + FairnessKey: sw.FairnessKey, + FairnessWeight: sw.FairnessWeight, + }, } if w.IdReusePolicy.Value != "" { var err error diff --git a/temporalcli/commands.workflow_exec_test.go b/temporalcli/commands.workflow_exec_test.go index 108d8cb5d..690b4823f 100644 --- a/temporalcli/commands.workflow_exec_test.go +++ b/temporalcli/commands.workflow_exec_test.go @@ -17,6 +17,7 @@ import ( "github.com/google/uuid" "go.temporal.io/api/common/v1" "go.temporal.io/api/enums/v1" + "go.temporal.io/api/history/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" @@ -953,3 +954,50 @@ func (s *SharedServerSuite) testStartUpdateWithStartHelper(opts updateWithStartT s.Equal(opts.expectedWfOutput["workflow"], wfReturn["workflow"]) s.Equal(opts.expectedWfOutput["update"], wfReturn["update"]) } + +func (s *SharedServerSuite) TestWorkflow_Start_WithPriorityOptions() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, input any) (any, error) { + return "success", nil + }) + + workflowId := "priority-test-" + uuid.New().String() + res := s.Execute( + "workflow", "start", + "--address", s.Address(), + "--task-queue", s.Worker().Options.TaskQueue, + "--type", "DevWorkflow", + "--workflow-id", workflowId, + "--priority-key", "2", + "--fairness-key", "high-priority-tenant", + "--fairness-weight", "5.5", + ) + s.NoError(res.Err) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + iter := s.Client.GetWorkflowHistory(ctx, + workflowId, "", false, enums.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT) + + var startedEvent *history.HistoryEvent + for iter.HasNext() { + event, err := iter.Next() + s.NoError(err) + if event.EventType == enums.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED { + startedEvent = event + break + } + } + + s.NotNil(startedEvent, "WorkflowExecutionStarted event not found") + + startedAttrs := startedEvent.GetWorkflowExecutionStartedEventAttributes() + s.NotNil(startedAttrs, "WorkflowExecutionStarted attributes not found") + + priority := startedAttrs.GetPriority() + s.NotNil(priority, "Priority not found in WorkflowExecutionStarted event") + + s.Equal(int32(2), priority.GetPriorityKey()) + s.Equal("high-priority-tenant", priority.GetFairnessKey()) + s.Equal(float32(5.5), priority.GetFairnessWeight()) +} diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 582cc0bf1..9cbf02389 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -3500,11 +3500,13 @@ commands: - auto_upgrade - name: versioning-override-deployment-name type: string - description: When overriding to a `pinned` behavior, specifies the Deployment Name of the + description: + When overriding to a `pinned` behavior, specifies the Deployment Name of the version to target. - name: versioning-override-build-id type: string - description: When overriding to a `pinned` behavior, specifies the Build ID of the + description: + When overriding to a `pinned` behavior, specifies the Build ID of the version to target. - name: temporal workflow query summary: Retrieve Workflow Execution state @@ -4473,6 +4475,22 @@ option-sets: description: | Static Workflow details for human consumption in UIs. Uses Temporal Markdown formatting, may be multiple lines. + - name: priority-key + type: int + description: | + Priority key (1-5, lower numbers = higher priority). + Tasks in a queue should be processed in close-to-priority-order. + Default is 3 when not specified. + - name: fairness-key + type: string + description: | + Fairness key (max 64 bytes) for proportional task dispatch. + Tasks with same key share capacity based on their weight. + - name: fairness-weight + type: float + description: | + Weight [0.001-1000] for this fairness key. + Keys are dispatched proportionally to their weights. - name: workflow-start options: @@ -4643,10 +4661,11 @@ option-sets: - auto_upgrade - name: versioning-override-deployment-name type: string - description: When overriding to a `pinned` behavior, specifies the Deployment Name of the + description: + When overriding to a `pinned` behavior, specifies the Deployment Name of the version to target. - name: versioning-override-build-id type: string - description: When overriding to a `pinned` behavior, specifies the Build ID of the + description: + When overriding to a `pinned` behavior, specifies the Build ID of the version to target. - From 932e5862e6ec5f7985a0d2483fbe9fb10d4b727c Mon Sep 17 00:00:00 2001 From: Siva Girish Ramesh <35769591+sivagirish81@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:13:00 -0700 Subject: [PATCH 5/7] Update task queue config (#840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What was changed + Added a new `report-config` field in the describe command to retrieve updated task queue configs if exists. + Implement display logic for config table along with truncation of large update reasons or update identities. **Note : These changes are only a part of the legacy mode of the describeTaskQueueApi.** ``` ./temporal task-queue describe \ --task-queue=test-display-queue \ --task-queue-type-legacy=activity \ -n=default \ --report-config \ --legacy-mode ``` Sample Response ``` ./temporal task-queue describe \ --task-queue=hello-world \ --task-queue-type-legacy=activity \ -n=default \ --report-config \ --legacy-mode Pollers: Identity LastAccessTime RatePerSecond Task Queue Configuration: Note: Long content may be truncated. Use --output json for full details. Setting Value Reason UpdatedBy UpdatedTime Fairness Key Rate Limit Default 100 requests/second Fairness key test api-tester 2025-08-12 04:31:46 ``` --- + Added separate config Subcommand for getting/setting configs. ``` ./temporal task-queue config --help Manage Task Queue configuration: temporal task-queue config [command] [options] Available commands: - get: Retrieve the current configuration for a task queue - set: Update the configuration for a task queue Usage: temporal task-queue config [command] Available Commands: get Get Task Queue configuration set Set Task Queue configuration ``` --- + Get Subcommand ``` ./temporal task-queue config get --help Retrieve the current configuration for a Task Queue: temporal task-queue config get \ --task-queue YourTaskQueue \ --task-queue-type activity This command returns the current configuration including: - Queue rate limit: The overall rate limit of the task queue. This setting overrides the worker rate limit if set. Unless modified, this is the system-defined rate limit. - Fairness key rate limit defaults: Default rate limits for fairness keys. If set, each individual fairness key will be limited to this rate, scaled by the weight of the fairness key. Usage: temporal task-queue config get [flags] ``` Sample Response ``` ./temporal task-queue config get \ --task-queue=hello-world \ --task-queue-type=activity \ --namespace=default Note: Long content may be truncated. Use --output json for full details. Setting Value Reason UpdatedBy UpdatedTime Fairness Key Rate Limit Default 100 requests/second Fairness key test api-tester 2025-08-12 04:31:46 ``` --- + Set Subcommand ``` ./temporal task-queue config set --help Update a Task Queue's overall rate limit and the default rate limit for all fairness keys: temporal task-queue config set \ --task-queue YourTaskQueue \ --task-queue-type activity \ --namespace YourNamespace \ --queue-rps-limit \ --queue-rps-limit-reason \ --fairness-key-rps-limit-default \ --fairness-key-rps-limit-reason This command supports updating: - Queue rate limits: Controls the overall rate limit of the task queue. This setting overrides the worker rate limit if set. Unless modified, this is the system-defined rate limit. - Fairness key rate limit defaults: Sets default rate limits for fairness keys. If set, each individual fairness key will be limited to this rate, scaled by the weight of the fairness key. To unset a rate limit, pass in 'default', for example: --queue-rps-limit default Usage: temporal task-queue config set [flags] Flags: --fairness-key-rps-limit-default float|default Fairness key rate limit default in requests per second. Accepts a float; or 'default' to unset. --fairness-key-rps-limit-reason string Reason for fairness key rate limit update. -h, --help help for set --queue-rps-limit float|default Queue rate limit in requests per second. Accepts a float; or 'default' to unset. --queue-rps-limit-reason string Reason for queue rate limit update. -t, --task-queue string Task Queue name. Required. --task-queue-type string Task Queue type. Accepted values: workflow, activity, nexus. Accepted values: workflow, activity, nexus. Required. ``` Sample response : ``` ./temporal task-queue config set \ --task-queue=hello-world \ --task-queue-type=activity \ --fairness-key-rps-limit-default=100 \ --fairness-key-rps-limit-reason="Fairness key test" \ --identity="api-tester" \ --namespace=default Successfully updated task queue configuration Config {"fairnessKeysRateLimitDefault":{"rateLimit":{"requestsPerSecond":100},"metadata":{"reason":"Fairness key test","updateIdentity":"api-tester","updateTime":"2025-08-12T04:31:46.640Z"}}} ``` ## Why? + Cli support for `UpdateTaskQueueConfig` api - new api that allows updates of rate limits against task queues. ## Checklist 1. How was this tested: + Added tests. 2. Any docs updates needed? Pending. --------- Co-authored-by: Stephan Behnke Co-authored-by: Stephan Behnke --- temporalcli/commands.gen.go | 98 +++++++ temporalcli/commands.go | 13 + temporalcli/commands.taskqueue.go | 38 ++- temporalcli/commands.taskqueue.helper.go | 100 +++++++ temporalcli/commands.taskqueue_config.go | 163 +++++++++++ temporalcli/commands.taskqueue_config_test.go | 256 ++++++++++++++++++ temporalcli/commandsgen/code.go | 9 +- temporalcli/commandsgen/commands.yml | 120 ++++++++ temporalcli/commandsgen/parse.go | 4 + 9 files changed, 794 insertions(+), 7 deletions(-) create mode 100644 temporalcli/commands.taskqueue.helper.go create mode 100644 temporalcli/commands.taskqueue_config.go create mode 100644 temporalcli/commands.taskqueue_config_test.go diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index d186826d3..35acc6370 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -2164,6 +2164,7 @@ func NewTemporalTaskQueueCommand(cctx *CommandContext, parent *TemporalCommand) s.Command.Long = "Inspect and update Task Queues, the queues that Workers poll for Workflow and\nActivity tasks:\n\n```\ntemporal task-queue [command] [command options] \\\n --task-queue YourTaskQueue\n```\n\nFor example:\n\n```\ntemporal task-queue describe \\\n --task-queue YourTaskQueue\n```" } s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewTemporalTaskQueueConfigCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueGetBuildIdReachabilityCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueGetBuildIdsCommand(cctx, &s).Command) @@ -2174,6 +2175,101 @@ func NewTemporalTaskQueueCommand(cctx *CommandContext, parent *TemporalCommand) return &s } +type TemporalTaskQueueConfigCommand struct { + Parent *TemporalTaskQueueCommand + Command cobra.Command +} + +func NewTemporalTaskQueueConfigCommand(cctx *CommandContext, parent *TemporalTaskQueueCommand) *TemporalTaskQueueConfigCommand { + var s TemporalTaskQueueConfigCommand + s.Parent = parent + s.Command.Use = "config" + s.Command.Short = "Get and set Task Queue configuration" + if hasHighlighting { + s.Command.Long = "Manage Task Queue configuration:\n\n\x1b[1mtemporal task-queue config [command] [options]\x1b[0m\n\nAvailable commands:\n- \x1b[1mget\x1b[0m: Retrieve the current configuration for a task queue\n- \x1b[1mset\x1b[0m: Update the configuration for a task queue" + } else { + s.Command.Long = "Manage Task Queue configuration:\n\n```\ntemporal task-queue config [command] [options]\n```\n\nAvailable commands:\n- `get`: Retrieve the current configuration for a task queue\n- `set`: Update the configuration for a task queue" + } + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewTemporalTaskQueueConfigGetCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalTaskQueueConfigSetCommand(cctx, &s).Command) + return &s +} + +type TemporalTaskQueueConfigGetCommand struct { + Parent *TemporalTaskQueueConfigCommand + Command cobra.Command + TaskQueue string + TaskQueueType StringEnum +} + +func NewTemporalTaskQueueConfigGetCommand(cctx *CommandContext, parent *TemporalTaskQueueConfigCommand) *TemporalTaskQueueConfigGetCommand { + var s TemporalTaskQueueConfigGetCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "get [flags]" + s.Command.Short = "Get Task Queue configuration" + if hasHighlighting { + s.Command.Long = "Retrieve the current configuration for a Task Queue:\n\n\x1b[1mtemporal task-queue config get \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity\x1b[0m\n\nThis command returns the current configuration including:\n- Queue rate limit: The overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key." + } else { + s.Command.Long = "Retrieve the current configuration for a Task Queue:\n\n```\ntemporal task-queue config get \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity\n```\n\nThis command returns the current configuration including:\n- Queue rate limit: The overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "task-queue") + s.TaskQueueType = NewStringEnum([]string{"workflow", "activity", "nexus"}, "") + s.Command.Flags().Var(&s.TaskQueueType, "task-queue-type", "Task Queue type. Accepted values: workflow, activity, nexus. Accepted values: workflow, activity, nexus. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "task-queue-type") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalTaskQueueConfigSetCommand struct { + Parent *TemporalTaskQueueConfigCommand + Command cobra.Command + TaskQueue string + TaskQueueType StringEnum + QueueRpsLimit string + QueueRpsLimitReason string + FairnessKeyRpsLimitDefault string + FairnessKeyRpsLimitReason string +} + +func NewTemporalTaskQueueConfigSetCommand(cctx *CommandContext, parent *TemporalTaskQueueConfigCommand) *TemporalTaskQueueConfigSetCommand { + var s TemporalTaskQueueConfigSetCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "set [flags]" + s.Command.Short = "Set Task Queue configuration" + if hasHighlighting { + s.Command.Long = "Update configuration settings for a Task Queue.\n\n\x1b[1mtemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \x1b[0m\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default" + } else { + s.Command.Long = "Update configuration settings for a Task Queue.\n\n```\ntemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \n```\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "task-queue") + s.TaskQueueType = NewStringEnum([]string{"workflow", "activity", "nexus"}, "") + s.Command.Flags().Var(&s.TaskQueueType, "task-queue-type", "Task Queue type. Accepted values: workflow, activity, nexus. Accepted values: workflow, activity, nexus. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "task-queue-type") + s.Command.Flags().StringVar(&s.QueueRpsLimit, "queue-rps-limit", "", "Queue rate limit in requests per second. Accepts a float; or 'default' to unset.") + overrideFlagDisplayType(s.Command.Flags().Lookup("queue-rps-limit"), "float|default") + s.Command.Flags().StringVar(&s.QueueRpsLimitReason, "queue-rps-limit-reason", "", "Reason for queue rate limit update.") + s.Command.Flags().StringVar(&s.FairnessKeyRpsLimitDefault, "fairness-key-rps-limit-default", "", "Fairness key rate limit default in requests per second. Accepts a float; or 'default' to unset.") + overrideFlagDisplayType(s.Command.Flags().Lookup("fairness-key-rps-limit-default"), "float|default") + s.Command.Flags().StringVar(&s.FairnessKeyRpsLimitReason, "fairness-key-rps-limit-reason", "", "Reason for fairness key rate limit update.") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalTaskQueueDescribeCommand struct { Parent *TemporalTaskQueueCommand Command cobra.Command @@ -2187,6 +2283,7 @@ type TemporalTaskQueueDescribeCommand struct { TaskQueueTypeLegacy StringEnum PartitionsLegacy int DisableStats bool + ReportConfig bool } func NewTemporalTaskQueueDescribeCommand(cctx *CommandContext, parent *TemporalTaskQueueCommand) *TemporalTaskQueueDescribeCommand { @@ -2214,6 +2311,7 @@ func NewTemporalTaskQueueDescribeCommand(cctx *CommandContext, parent *TemporalT s.Command.Flags().Var(&s.TaskQueueTypeLegacy, "task-queue-type-legacy", "Task Queue type (legacy mode only). Accepted values: workflow, activity.") s.Command.Flags().IntVar(&s.PartitionsLegacy, "partitions-legacy", 1, "Query partitions 1 through `N`. Experimental/Temporary feature. Legacy mode only.") s.Command.Flags().BoolVar(&s.DisableStats, "disable-stats", false, "Disable task queue statistics.") + s.Command.Flags().BoolVar(&s.ReportConfig, "report-config", false, "Include task queue configuration in the response. When enabled, the command will return the current rate limit configuration for the task queue.") s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) diff --git a/temporalcli/commands.go b/temporalcli/commands.go index 2c280e05f..a549c80b5 100644 --- a/temporalcli/commands.go +++ b/temporalcli/commands.go @@ -580,3 +580,16 @@ func encodeMapToPayloads(in map[string]any) (map[string]*commonpb.Payload, error } return out, nil } + +type overrideDisplayTypeFlagValue struct { + pflag.Value + displayType string +} + +func (o *overrideDisplayTypeFlagValue) Type() string { + return o.displayType +} + +func overrideFlagDisplayType(flag *pflag.Flag, displayType string) { + flag.Value = &overrideDisplayTypeFlagValue{Value: flag.Value, displayType: displayType} +} diff --git a/temporalcli/commands.taskqueue.go b/temporalcli/commands.taskqueue.go index fdfaad9cf..a769190a4 100644 --- a/temporalcli/commands.taskqueue.go +++ b/temporalcli/commands.taskqueue.go @@ -334,6 +334,7 @@ func (c *TemporalTaskQueueDescribeCommand) runLegacy(cctx *CommandContext, args var statuses []*statusWithPartition var pollers []*pollerWithPartition + var config *taskqueue.TaskQueueConfig // TODO: remove this when the server does partition fan-out for p := 0; p < partitions; p++ { @@ -345,6 +346,7 @@ func (c *TemporalTaskQueueDescribeCommand) runLegacy(cctx *CommandContext, args }, TaskQueueType: taskQueueType, IncludeTaskQueueStatus: true, + ReportConfig: c.ReportConfig, }) if err != nil { return fmt.Errorf("unable to describe task queue: %w", err) @@ -360,14 +362,25 @@ func (c *TemporalTaskQueueDescribeCommand) runLegacy(cctx *CommandContext, args Versioning: pi.WorkerVersionCapabilities, }) } + // Capture config from the first partition (they should all be the same) + if p == 0 && resp.Config != nil { + config = resp.Config + } } // For JSON, we'll just dump the proto if cctx.JSONOutput { - return cctx.Printer.PrintStructured(map[string]any{ + output := map[string]any{ "taskQueues": statuses, "pollers": pollers, - }, printer.StructuredOptions{}) + } + + // Include config if requested + if c.ReportConfig && config != nil { + output["config"] = config + } + + return cctx.Printer.PrintStructured(output, printer.StructuredOptions{}) } // For text, we will use a table for pollers @@ -382,7 +395,18 @@ func (c *TemporalTaskQueueDescribeCommand) runLegacy(cctx *CommandContext, args items[i].LastAccessTime = poller.LastAccessTime.AsTime() items[i].RatePerSecond = poller.RatePerSecond } - return cctx.Printer.PrintStructured(items, printer.StructuredOptions{Table: &printer.TableOptions{}}) + err = cctx.Printer.PrintStructured(items, printer.StructuredOptions{Table: &printer.TableOptions{}}) + if err != nil { + return err + } + + // Display config if requested + if c.ReportConfig && config != nil { + cctx.Printer.Println(color.MagentaString("\nTask Queue Configuration:")) + return printTaskQueueConfig(cctx, config) + } + + return nil } func (c *TemporalTaskQueueListPartitionCommand) run(cctx *CommandContext, args []string) error { @@ -425,3 +449,11 @@ func (c *TemporalTaskQueueListPartitionCommand) run(cctx *CommandContext, args [ return nil } + +// Helper function to truncate strings +func truncateString(s string, maxLength int) string { + if len(s) <= maxLength { + return s + } + return s[:maxLength-3] + "..." +} diff --git a/temporalcli/commands.taskqueue.helper.go b/temporalcli/commands.taskqueue.helper.go new file mode 100644 index 000000000..591531c3d --- /dev/null +++ b/temporalcli/commands.taskqueue.helper.go @@ -0,0 +1,100 @@ +package temporalcli + +import ( + "fmt" + "time" + + "github.com/fatih/color" + "github.com/temporalio/cli/temporalcli/internal/printer" + "go.temporal.io/api/enums/v1" + "go.temporal.io/api/taskqueue/v1" +) + +// Create a structured table for config display. +type configRow struct { + Setting string + Value string + Reason string + UpdatedBy string + UpdatedTime string +} + +func parseTaskQueueType(input string) (enums.TaskQueueType, error) { + switch input { + case "", "workflow": + return enums.TASK_QUEUE_TYPE_WORKFLOW, nil + case "activity": + return enums.TASK_QUEUE_TYPE_ACTIVITY, nil + case "nexus": + return enums.TASK_QUEUE_TYPE_NEXUS, nil + default: + return enums.TASK_QUEUE_TYPE_WORKFLOW, fmt.Errorf( + "invalid task queue type: %s. Must be one of: workflow, activity, nexus", input) + } +} + +func buildRateLimitConfigRow(setting string, rl *taskqueue.RateLimitConfig, format string) configRow { + value := "Not Set" + reason := "" + updatedBy := "" + updatedTime := "" + + if rl.RateLimit != nil && rl.RateLimit.RequestsPerSecond > 0 { + value = fmt.Sprintf(format, rl.RateLimit.RequestsPerSecond) + } + + if rl.Metadata != nil { + if rl.Metadata.Reason != "" { + reason = truncateString(rl.Metadata.Reason, 50) + } + if rl.Metadata.UpdateIdentity != "" { + updatedBy = truncateString(rl.Metadata.UpdateIdentity, 50) + } + if rl.Metadata.UpdateTime != nil { + updateTime := rl.Metadata.UpdateTime.AsTime() + updatedTime = updateTime.Format(time.RFC3339) + } + } + + return configRow{ + Setting: setting, + Value: value, + Reason: reason, + UpdatedBy: updatedBy, + UpdatedTime: updatedTime, + } +} + +// printTaskQueueConfig is a shared function to print task queue configuration +// This can be used by both the config get command and the describe command +func printTaskQueueConfig(cctx *CommandContext, config *taskqueue.TaskQueueConfig) error { + // For JSON, we'll just dump the proto + if cctx.JSONOutput { + return cctx.Printer.PrintStructured(config, printer.StructuredOptions{}) + } + + // For text, we will use a table + var configRows []configRow + + // Queue Rate Limit + if config.QueueRateLimit != nil { + configRows = append(configRows, buildRateLimitConfigRow("Queue Rate Limit", config.QueueRateLimit, "%.2f rps")) + } + + // Fairness Key Rate Limit Default + if config.FairnessKeysRateLimitDefault != nil { + configRows = append(configRows, buildRateLimitConfigRow("Fairness Key Rate Limit Default", config.FairnessKeysRateLimitDefault, "%.2f rps")) + } + + // Print the config table + if len(configRows) > 0 { + // Always show truncation note, regardless of actual truncation + cctx.Printer.Println(color.YellowString("Note: Long content may be truncated. Use --output json for full details.")) + + return cctx.Printer.PrintStructured(configRows, printer.StructuredOptions{ + Table: &printer.TableOptions{}, + }) + } + + return nil +} diff --git a/temporalcli/commands.taskqueue_config.go b/temporalcli/commands.taskqueue_config.go new file mode 100644 index 000000000..a9de915ef --- /dev/null +++ b/temporalcli/commands.taskqueue_config.go @@ -0,0 +1,163 @@ +package temporalcli + +import ( + "fmt" + "strconv" + "strings" + + "github.com/temporalio/cli/temporalcli/internal/printer" + enums "go.temporal.io/api/enums/v1" + "go.temporal.io/api/taskqueue/v1" + "go.temporal.io/api/workflowservice/v1" +) + +// TaskQueueConfigGetCommand handles getting task queue configuration +func (c *TemporalTaskQueueConfigGetCommand) run(cctx *CommandContext, args []string) error { + // Validate inputs before dialing client + taskQueue := c.TaskQueue + if taskQueue == "" { + return fmt.Errorf("taskQueue name is required") + } + + taskQueueType, err := parseTaskQueueType(c.TaskQueueType.Value) + if err != nil { + return err + } + + namespace := c.Parent.Parent.Namespace + if namespace == "" { + return fmt.Errorf("namespace is required") + } + + cl, err := c.Parent.Parent.ClientOptions.dialClient(cctx) + if err != nil { + return err + } + defer cl.Close() + + // Get the task queue configuration + resp, err := cl.WorkflowService().DescribeTaskQueue(cctx, &workflowservice.DescribeTaskQueueRequest{ + Namespace: namespace, + TaskQueue: &taskqueue.TaskQueue{ + Name: taskQueue, + Kind: enums.TASK_QUEUE_KIND_NORMAL, + }, + TaskQueueType: taskQueueType, + ReportConfig: true, + }) + if err != nil { + return fmt.Errorf("error getting task queue config: %w", err) + } + if resp.Config == nil { + cctx.Printer.Println("No configuration found for task queue") + return nil + } + // Print the configuration using the shared function + return printTaskQueueConfig(cctx, resp.Config) +} + +// TaskQueueConfigSetCommand handles setting task queue configuration +func (c *TemporalTaskQueueConfigSetCommand) run(cctx *CommandContext, args []string) error { + // Validate inputs before dialing client + taskQueue := c.TaskQueue + if taskQueue == "" { + return fmt.Errorf("taskQueue name is required") + } + + taskQueueType, err := parseTaskQueueType(c.TaskQueueType.Value) + if err != nil { + return err + } + + namespace := c.Parent.Parent.Namespace + if namespace == "" { + return fmt.Errorf("namespace is required") + } + + // Check workflow task queue restrictions + if taskQueueType == enums.TASK_QUEUE_TYPE_WORKFLOW { + if c.Command.Flags().Changed("queue-rps-limit") || + c.Command.Flags().Changed("queue-rps-limit-reason") { + return fmt.Errorf("setting rate limit on workflow task queues is not allowed") + } + } + + // Helper to parse RPS values for a given flag name. + // Accepts "default" or a non-negative float string. + parseRPS := func(flagName string) (*taskqueue.RateLimit, error) { + raw := strings.TrimSpace(c.Command.Flags().Lookup(flagName).Value.String()) + if raw == "" { + return nil, fmt.Errorf("invalid value for --%s: must be a non-negative number or 'default'", flagName) + } + if strings.EqualFold(raw, "default") { + // Unset: returning nil RateLimit removes the existing rate limit. + return nil, nil + } + v, err := strconv.ParseFloat(raw, 32) + if err != nil { + return nil, fmt.Errorf("invalid value for --%s: must be a non-negative number or 'default'", flagName) + } + if v < 0 { + return nil, fmt.Errorf("invalid value for --%s: must be >= 0 or 'default'", flagName) + } + return &taskqueue.RateLimit{RequestsPerSecond: float32(v)}, nil + } + + var queueRpsLimitParsed *taskqueue.RateLimit + if c.Command.Flags().Changed("queue-rps-limit") { + var err error + if queueRpsLimitParsed, err = parseRPS("queue-rps-limit"); err != nil { + return err + } + } else if c.Command.Flags().Changed("queue-rps-limit-reason") { + return fmt.Errorf("queue-rps-limit-reason can only be set if queue-rps-limit is updated") + } + + var fairnessKeyRpsLimitDefaultParsed *taskqueue.RateLimit + if c.Command.Flags().Changed("fairness-key-rps-limit-default") { + var err error + if fairnessKeyRpsLimitDefaultParsed, err = parseRPS("fairness-key-rps-limit-default"); err != nil { + return err + } + } else if c.Command.Flags().Changed("fairness-key-rps-limit-default-reason") { + return fmt.Errorf("fairness-key-rps-limit-default-reason can only be set if fairness-key-rps-limit-default is updated") + } + + cl, err := c.Parent.Parent.ClientOptions.dialClient(cctx) + if err != nil { + return err + } + defer cl.Close() + + request := &workflowservice.UpdateTaskQueueConfigRequest{ + Namespace: namespace, + Identity: c.Parent.Parent.Identity, + TaskQueue: taskQueue, + TaskQueueType: taskQueueType, + } + + // Add queue rate limit if specified (including unset) + if c.Command.Flags().Changed("queue-rps-limit") { + request.UpdateQueueRateLimit = &workflowservice.UpdateTaskQueueConfigRequest_RateLimitUpdate{ + RateLimit: queueRpsLimitParsed, + Reason: c.QueueRpsLimitReason, + } + } + + // Add fairness key rate limit default if specified (including unset) + if c.Command.Flags().Changed("fairness-key-rps-limit-default") { + request.UpdateFairnessKeyRateLimitDefault = &workflowservice.UpdateTaskQueueConfigRequest_RateLimitUpdate{ + RateLimit: fairnessKeyRpsLimitDefaultParsed, + Reason: c.FairnessKeyRpsLimitReason, + } + } + + // Call the API + resp, err := cl.WorkflowService().UpdateTaskQueueConfig(cctx, request) + if err != nil { + return fmt.Errorf("error updating task queue config: %w", err) + } + + cctx.Printer.Println("Successfully updated task queue configuration") + return cctx.Printer.PrintStructured(resp, printer.StructuredOptions{}) +} diff --git a/temporalcli/commands.taskqueue_config_test.go b/temporalcli/commands.taskqueue_config_test.go new file mode 100644 index 000000000..d6fac4b71 --- /dev/null +++ b/temporalcli/commands.taskqueue_config_test.go @@ -0,0 +1,256 @@ +package temporalcli_test + +import ( + "encoding/json" +) + +type taskQueueConfigType struct { + QueueRateLimit *rateLimitConfigType `json:"queueRateLimit,omitempty"` + FairnessKeysRateLimitDefault *rateLimitConfigType `json:"fairnessKeysRateLimitDefault,omitempty"` +} + +type rateLimitConfigType struct { + RateLimit *rateLimitType `json:"rateLimit,omitempty"` + Metadata *metadataType `json:"metadata,omitempty"` +} + +type rateLimitType struct { + RequestsPerSecond float32 `json:"requestsPerSecond"` +} + +type metadataType struct { + Reason string `json:"reason,omitempty"` + UpdateIdentity string `json:"updateIdentity,omitempty"` + UpdateTime string `json:"updateTime,omitempty"` +} + +func (s *SharedServerSuite) TestTaskQueue_Config_Get_Empty() { + // Test getting config for a task queue with no configuration + res := s.Execute( + "task-queue", "config", "get", + "--address", s.Address(), + "--task-queue", s.Worker().Options.TaskQueue, + "--task-queue-type", "activity", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "No configuration found for task queue") +} + +func (s *SharedServerSuite) TestTaskQueue_Config_Set_And_Get_Both_Limits() { + taskQueue := "test-config-queue-" + s.T().Name() + testIdentity := "test-identity-" + s.T().Name() + + // Set both queue rate limit and fairness key rate limit with explicit identity + res := s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "--identity", testIdentity, + "--queue-rps-limit", "20.0", + "--queue-rps-limit-reason", "queue limit reason", + "--fairness-key-rps-limit-default", "10.0", + "--fairness-key-rps-limit-reason", "fairness limit reason", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully updated task queue configuration") + + // Get the configuration and verify both were set using JSON output + res = s.Execute( + "task-queue", "config", "get", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "-o", "json", + ) + s.NoError(res.Err) + + var config taskQueueConfigType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &config)) + + // Verify queue rate limit + s.NotNil(config.QueueRateLimit) + s.NotNil(config.QueueRateLimit.RateLimit) + s.Equal(float32(20.0), config.QueueRateLimit.RateLimit.RequestsPerSecond) + s.NotNil(config.QueueRateLimit.Metadata) + s.Equal("queue limit reason", config.QueueRateLimit.Metadata.Reason) + s.Equal(testIdentity, config.QueueRateLimit.Metadata.UpdateIdentity) + s.NotEmpty(config.QueueRateLimit.Metadata.UpdateTime) + + // Verify fairness key rate limit + s.NotNil(config.FairnessKeysRateLimitDefault) + s.NotNil(config.FairnessKeysRateLimitDefault.RateLimit) + s.Equal(float32(10.0), config.FairnessKeysRateLimitDefault.RateLimit.RequestsPerSecond) + s.NotNil(config.FairnessKeysRateLimitDefault.Metadata) + s.Equal("fairness limit reason", config.FairnessKeysRateLimitDefault.Metadata.Reason) + s.Equal(testIdentity, config.FairnessKeysRateLimitDefault.Metadata.UpdateIdentity) + s.NotEmpty(config.FairnessKeysRateLimitDefault.Metadata.UpdateTime) +} + +func (s *SharedServerSuite) TestTaskQueue_Config_Unset_Rate_Limits() { + taskQueue := "test-config-queue-" + s.T().Name() + testIdentity := "test-identity-" + s.T().Name() + var config taskQueueConfigType + // Set initial configuration + res := s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "--identity", testIdentity, + "--queue-rps-limit", "10.0", + "--fairness-key-rps-limit-default", "5.0", + ) + s.NoError(res.Err) + + res = s.Execute( + "task-queue", "config", "get", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "-o", "json", + ) + s.NoError(res.Err) + + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &config)) + s.NotNil(config.QueueRateLimit) + s.NotNil(config.QueueRateLimit.RateLimit) + s.Equal(float32(10.0), config.QueueRateLimit.RateLimit.RequestsPerSecond) + s.NotNil(config.FairnessKeysRateLimitDefault) + s.NotNil(config.FairnessKeysRateLimitDefault.RateLimit) + s.Equal(float32(5.0), config.FairnessKeysRateLimitDefault.RateLimit.RequestsPerSecond) + + // Unset queue rate limit (set to default) + res = s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "--identity", testIdentity, + "--queue-rps-limit", "default", + ) + s.NoError(res.Err) + + // Get configuration and verify queue rate limit is unset using JSON output + res = s.Execute( + "task-queue", "config", "get", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "-o", "json", + ) + s.NoError(res.Err) + + var unsetQrlConfig taskQueueConfigType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &unsetQrlConfig)) + s.NotNil(unsetQrlConfig.QueueRateLimit) + s.Nil(unsetQrlConfig.QueueRateLimit.RateLimit) + s.NotNil(unsetQrlConfig.FairnessKeysRateLimitDefault) + s.Equal(float32(5.0), unsetQrlConfig.FairnessKeysRateLimitDefault.RateLimit.RequestsPerSecond) + + // Unset fairness key rate limit + res = s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "--identity", testIdentity, + "--fairness-key-rps-limit-default", "default", + ) + s.NoError(res.Err) + + // Get configuration and verify both are unset using JSON output + res = s.Execute( + "task-queue", "config", "get", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "-o", "json", + ) + s.NoError(res.Err) + + var unsetFkrlConfig taskQueueConfigType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &unsetFkrlConfig)) + s.NotNil(unsetFkrlConfig.FairnessKeysRateLimitDefault) + s.Nil(unsetFkrlConfig.FairnessKeysRateLimitDefault.RateLimit) +} + +func (s *SharedServerSuite) TestTaskQueue_Config_Workflow_Task_Queue_Restrictions() { + taskQueue := "test-config-queue-" + s.T().Name() + + // Try to set queue rate limit on workflow task queue (should fail) + res := s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "workflow", + "--queue-rps-limit", "10.0", + ) + s.Error(res.Err) + + // TODO : add test to check if setting fairness key rate limit on workflow task queue is allowed + // Will be done after the server PR (pending) to allow setting fairness key rate limit on workflow task queues is merged. +} + +func (s *SharedServerSuite) TestTaskQueue_Config_Describe_With_Report_Config() { + taskQueue := "test-config-queue-" + s.T().Name() + testIdentity := "test-identity-" + s.T().Name() + + // Set configuration + res := s.Execute( + "task-queue", "config", "set", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type", "activity", + "--identity", testIdentity, + "--queue-rps-limit", "12.5", + "--queue-rps-limit-reason", "describe test", + ) + s.NoError(res.Err) + + // Test JSON output with describe + res = s.Execute( + "task-queue", "describe", + "--address", s.Address(), + "--task-queue", taskQueue, + "--task-queue-type-legacy", "activity", + "--report-config", + "--legacy-mode", + "-o", "json", + ) + s.NoError(res.Err) + + // The JSON output should contain the config section + var result map[string]any + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + + cfg, ok := result["config"].(map[string]any) + s.True(ok, "config should be an object") + s.NotEmpty(cfg) + + qrl, ok := cfg["queue_rate_limit"].(map[string]any) + s.True(ok, "config.queueRateLimit should be an object") + s.NotEmpty(qrl) + + rl, ok := qrl["rate_limit"].(map[string]any) + s.True(ok, "config.queueRateLimit.RateLimit should be an object") + + rps, ok := rl["requests_per_second"].(float64) + s.True(ok, "requests_per_second should be a number") + s.Equal(12.5, rps) + + md, ok := qrl["metadata"].(map[string]any) + s.True(ok, "metadata should be an object") + s.NotEmpty(md) + + reason, ok := md["reason"].(string) + s.True(ok) + s.Equal("describe test", reason) + + updID, ok := md["update_identity"].(string) + s.True(ok) + s.Equal(testIdentity, updID) + + updTime, _ := md["update_time"].(map[string]any) + s.NotEmpty(updTime) +} diff --git a/temporalcli/commandsgen/code.go b/temporalcli/commandsgen/code.go index e2784f90f..6c67082ba 100644 --- a/temporalcli/commandsgen/code.go +++ b/temporalcli/commandsgen/code.go @@ -421,11 +421,12 @@ func (o *Option) writeFlagBuilding(selfVar, flagVar string, w *codeWriter) error w.writeLinef("%v.%v = %v", selfVar, o.fieldName(), setDefault) } if o.Short != "" { - w.writeLinef("%v.%vP(&%v.%v, %q, %q%v, %q)", - flagVar, flagMeth, selfVar, o.fieldName(), o.Name, o.Short, defaultLit, desc) + w.writeLinef("%v.%vP(&%v.%v, %q, %q%v, %q)", flagVar, flagMeth, selfVar, o.fieldName(), o.Name, o.Short, defaultLit, desc) } else { - w.writeLinef("%v.%v(&%v.%v, %q%v, %q)", - flagVar, flagMeth, selfVar, o.fieldName(), o.Name, defaultLit, desc) + w.writeLinef("%v.%v(&%v.%v, %q%v, %q)", flagVar, flagMeth, selfVar, o.fieldName(), o.Name, defaultLit, desc) + } + if o.DisplayType != "" { + w.writeLinef("overrideFlagDisplayType(%v.Lookup(%q), %q)", flagVar, o.Name, o.DisplayType) } if o.Required { w.writeLinef("_ = %v.MarkFlagRequired(%v, %q)", w.importCobra(), flagVar, o.Name) diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 9cbf02389..7f7bcc03a 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -99,6 +99,7 @@ # options: A list of options. (Option[]) # - name: The option name. (string) # type: The option type. (string) +# display-type: Optional custom description for displaying the type in the help (string) # description: The option description. (string) # required: Whether the option is required. (bool) # short: The single letter short version of name (i.e. a for address). (string) @@ -2592,6 +2593,12 @@ commands: - name: disable-stats type: bool description: Disable task queue statistics. + - name: report-config + type: bool + description: | + Include task queue configuration in the response. + When enabled, the command will return the current rate limit + configuration for the task queue. - name: temporal task-queue get-build-id-reachability summary: Show Build ID availability (Deprecated) @@ -3183,6 +3190,119 @@ commands: type: bool description: Don't prompt to confirm. + - name: temporal task-queue config + summary: Get and set Task Queue configuration + description: | + Manage Task Queue configuration: + + ``` + temporal task-queue config [command] [options] + ``` + + Available commands: + - `get`: Retrieve the current configuration for a task queue + - `set`: Update the configuration for a task queue + + - name: temporal task-queue config get + summary: Get Task Queue configuration + description: | + Retrieve the current configuration for a Task Queue: + + ``` + temporal task-queue config get \ + --task-queue YourTaskQueue \ + --task-queue-type activity + ``` + + This command returns the current configuration including: + - Queue rate limit: The overall rate limit of the task queue. + This setting overrides the worker rate limit if set. + Unless modified, this is the system-defined rate limit. + - Fairness key rate limit defaults: Default rate limits for fairness keys. + If set, each individual fairness key will be limited to this rate, + scaled by the weight of the fairness key. + + options: + - name: task-queue + type: string + description: | + Task Queue name. + required: true + short: t + - name: task-queue-type + type: string-enum + description: | + Task Queue type. + Accepted values: workflow, activity, nexus. + required: true + enum-values: + - workflow + - activity + - nexus + + - name: temporal task-queue config set + summary: Set Task Queue configuration + description: | + Update configuration settings for a Task Queue. + + ``` + temporal task-queue config set \ + --task-queue YourTaskQueue \ + --task-queue-type activity \ + --namespace YourNamespace \ + --queue-rps-limit \ + --queue-rps-limit-reason \ + --fairness-key-rps-limit-default \ + --fairness-key-rps-limit-reason + ``` + + This command supports updating: + - Queue rate limits: Controls the overall rate limit of the task queue. + This setting overrides the worker rate limit if set. + Unless modified, this is the system-defined rate limit. + - Fairness key rate limit defaults: Sets default rate limits for fairness keys. + If set, each individual fairness key will be limited to this rate, + scaled by the weight of the fairness key. + + To unset a rate limit, pass in 'default', for example: --queue-rps-limit default + options: + - name: task-queue + type: string + description: | + Task Queue name. + required: true + short: t + - name: task-queue-type + type: string-enum + description: | + Task Queue type. + Accepted values: workflow, activity, nexus. + required: true + enum-values: + - workflow + - activity + - nexus + - name: queue-rps-limit + type: string + display-type: float|default + description: | + Queue rate limit in requests per second. + Accepts a float; or 'default' to unset. + - name: queue-rps-limit-reason + type: string + description: Reason for queue rate limit update. + hidden: true + - name: fairness-key-rps-limit-default + type: string + display-type: float|default + description: | + Fairness key rate limit default in requests per second. + Accepts a float; or 'default' to unset. + - name: fairness-key-rps-limit-reason + type: string + description: Reason for fairness key rate limit update. + hidden: true + - name: temporal workflow summary: Start, list, and operate on Workflows description: | diff --git a/temporalcli/commandsgen/parse.go b/temporalcli/commandsgen/parse.go index 568e38fd7..7945e1c7e 100644 --- a/temporalcli/commandsgen/parse.go +++ b/temporalcli/commandsgen/parse.go @@ -22,6 +22,7 @@ type ( Option struct { Name string `yaml:"name"` Type string `yaml:"type"` + DisplayType string `yaml:"display-type"` Description string `yaml:"description"` Deprecated string `yaml:"deprecated"` Short string `yaml:"short,omitempty"` @@ -215,6 +216,9 @@ func (o *Option) processSection() error { if o.Type == "" { return fmt.Errorf("missing option type") } + if o.Type != "string" && o.DisplayType != "" { + return fmt.Errorf("display-type is only allowed for string options") + } if o.Description == "" { return fmt.Errorf("missing description for option: %s", o.Name) From 2f68de746f4fb6ceff15fd2b443eb35be0032bea Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 3 Oct 2025 11:11:44 -0700 Subject: [PATCH 6/7] Formatting and merge conflicts --- temporalcli/commands.activity.go | 2 ++ temporalcli/commandsgen/commands.yml | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/temporalcli/commands.activity.go b/temporalcli/commands.activity.go index d9550b319..0d297a4a2 100644 --- a/temporalcli/commands.activity.go +++ b/temporalcli/commands.activity.go @@ -376,6 +376,8 @@ func (c *TemporalActivityResetCommand) run(cctx *CommandContext, args []string) request.Activity = &workflowservice.ResetActivityRequest_Type{Type: c.ActivityType} } else if c.ActivityId != "" { request.Activity = &workflowservice.ResetActivityRequest_Id{Id: c.ActivityId} + } else { + return fmt.Errorf("either Activity Type or Activity Id must be provided") } resp, err := cl.WorkflowService().ResetActivity(cctx, request) diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 7f7bcc03a..9405fb761 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -337,8 +337,7 @@ commands: summary: Update Activity options description: | Update the options of a running Activity that were passed into it from - a Workflow. Updates are incremental, only changing the specified - options. + a Workflow. Updates are incremental, only changing the specified options. For example: @@ -370,7 +369,6 @@ commands: ... ``` options: - - name: activity-id short: a type: string @@ -541,8 +539,7 @@ commands: - name: match-all type: bool description: | - Every paused activity should be unpaused. This flag is ignored if - activity-type is provided. + Every paused activity should be unpaused. This flag is ignored if activity-type is provided. - name: jitter type: duration description: | From dfd8509799c7be29dd81f0ab5840c658b1805da5 Mon Sep 17 00:00:00 2001 From: Roey Berman Date: Fri, 3 Oct 2025 12:50:38 -0600 Subject: [PATCH 7/7] Bump server to 1.29.0 (#857) --- go.mod | 6 +++--- go.sum | 8 ++++---- temporalcli/commands.gen.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 962d5464a..ffa9f813f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/temporalio/cli -go 1.24.5 +go 1.25.0 require ( github.com/BurntSushi/toml v1.4.0 @@ -16,10 +16,10 @@ require ( github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/temporalio/ui-server/v2 v2.39.0 - go.temporal.io/api v1.52.0 + go.temporal.io/api v1.53.0 go.temporal.io/sdk v1.36.0 go.temporal.io/sdk/contrib/envconfig v0.1.0 - go.temporal.io/server v1.29.0-139.3 + go.temporal.io/server v1.29.0 google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index c6e37dc53..8e4d4c508 100644 --- a/go.sum +++ b/go.sum @@ -383,14 +383,14 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.52.0 h1:Tn69z2nhQeXtofa1/j/MbwPHnFRM9+13xqYmFl/KFjM= -go.temporal.io/api v1.52.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.53.0 h1:6vAFpXaC584AIELa6pONV56MTpkm4Ha7gPWL2acNAjo= +go.temporal.io/api v1.53.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.36.0 h1:WO9zetpybBNK7xsQth4Z+3Zzw1zSaM9MOUGrnnUjZMo= go.temporal.io/sdk v1.36.0/go.mod h1:8BxGRF0LcQlfQrLLGkgVajbsKUp/PY7280XTdcKc18Y= go.temporal.io/sdk/contrib/envconfig v0.1.0 h1:s+G/Ujph+Xl2jzLiiIm2T1vuijDkUL4Kse49dgDVGBE= go.temporal.io/sdk/contrib/envconfig v0.1.0/go.mod h1:FQEO3C56h9C7M6sDgSanB8HnBTmopw9qgVx4F1S6pJk= -go.temporal.io/server v1.29.0-139.3 h1:j4u7esLvLqzE67Q6s/ExvaFKOensFhskCWXe1VHyBdU= -go.temporal.io/server v1.29.0-139.3/go.mod h1:OKFCSB0xxCX1Bmi2rYutWJEKppIrwuqx+c3e5GeY54w= +go.temporal.io/server v1.29.0 h1:BGBCvI7vcPokCjuDsfitLx2eS+8ow+yQ4frLJZcn2nQ= +go.temporal.io/server v1.29.0/go.mod h1:pc0n6DRcN06V4WNhaxdxE3KaZIS3KSDNKdca6uu6RuU= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 35acc6370..a7a2d44ba 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -639,9 +639,9 @@ func NewTemporalActivityUpdateOptionsCommand(cctx *CommandContext, parent *Tempo s.Command.Use = "update-options [flags]" s.Command.Short = "Update Activity options" if hasHighlighting { - s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified \noptions.\n\nFor example:\n\n\x1b[1mtemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\x1b[0m\n\nYou may follow this command with \x1b[1mtemporal activity reset\x1b[0m, and the new values will apply after the reset.\n\nEither \x1b[1mactivity-id\x1b[0m, \x1b[1mactivity-type\x1b[0m, or \x1b[1m--match-all\x1b[0m must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n\x1b[1mtemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\x1b[0m" + s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified options.\n\nFor example:\n\n\x1b[1mtemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\x1b[0m\n\nYou may follow this command with \x1b[1mtemporal activity reset\x1b[0m, and the new values will apply after the reset.\n\nEither \x1b[1mactivity-id\x1b[0m, \x1b[1mactivity-type\x1b[0m, or \x1b[1m--match-all\x1b[0m must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n\x1b[1mtemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\x1b[0m" } else { - s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified \noptions.\n\nFor example:\n\n```\ntemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\n```\n\nYou may follow this command with `temporal activity reset`, and the new values will apply after the reset.\n\nEither `activity-id`, `activity-type`, or `--match-all` must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n```\ntemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\n```" + s.Command.Long = "Update the options of a running Activity that were passed into it from\na Workflow. Updates are incremental, only changing the specified options.\n\nFor example:\n\n```\ntemporal activity update-options \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId \\\n --task-queue NewTaskQueueName \\\n --schedule-to-close-timeout DURATION \\\n --schedule-to-start-timeout DURATION \\\n --start-to-close-timeout DURATION \\\n --heartbeat-timeout DURATION \\\n --retry-initial-interval DURATION \\\n --retry-maximum-interval DURATION \\\n --retry-backoff-coefficient NewBackoffCoefficient \\\n --retry-maximum-attempts NewMaximumAttempts\n```\n\nYou may follow this command with `temporal activity reset`, and the new values will apply after the reset.\n\nEither `activity-id`, `activity-type`, or `--match-all` must be specified.\n\nActivity options can be updated in bulk with a visibility query list filter. \nFor example, if you want to reset for activities of type Foo, do:\n\n```\ntemporal activity update-options \\\n --query 'TemporalPauseInfo=\"property:activityType=Foo\"'\n ...\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to update options. Mutually exclusive with `--query`, `--match-all`, and `--activity-type`. Requires `--workflow-id` to be specified.")