From 728274ac4b1cd7e7031c87e068eed0e78d608dac Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 27 Feb 2025 10:08:07 -0800 Subject: [PATCH 01/15] User Metadata set on start / desc (#742) --- .gitignore | 2 +- temporalcli/commands.gen.go | 4 ++ temporalcli/commands.schedule.go | 2 + temporalcli/commands.schedule_test.go | 18 +++++++++ temporalcli/commands.workflow_exec.go | 2 + temporalcli/commands.workflow_view.go | 14 +++++++ temporalcli/commands.workflow_view_test.go | 43 ++++++++++++++++++++++ temporalcli/commandsgen/commands.yml | 12 ++++++ 8 files changed, 96 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a8a5794dc..14713bfa8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ # Used by IDE /.idea /.vscode +/.zed *~ - diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 04c626589..ccab78306 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -190,6 +190,8 @@ type SharedWorkflowStartOptions struct { TaskTimeout Duration SearchAttribute []string Memo []string + StaticSummary string + StaticDetails string } func (v *SharedWorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { @@ -206,6 +208,8 @@ func (v *SharedWorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.F f.Var(&v.TaskTimeout, "task-timeout", "Fail a Workflow Task if it lasts longer than `DURATION`. This is the Start-to-close timeout for a Workflow Task.") f.StringArrayVar(&v.SearchAttribute, "search-attribute", nil, "Search Attribute in `KEY=VALUE` format. Keys must be identifiers, and values must be JSON values. For example: 'YourKey={\"your\": \"value\"}'. Can be passed multiple times.") 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.") + f.StringVar(&v.StaticDetails, "static-details", "", "Static Workflow details for human consumption in UIs. Uses Temporal Markdown formatting, may be multiple lines.") } type WorkflowStartOptions struct { diff --git a/temporalcli/commands.schedule.go b/temporalcli/commands.schedule.go index 3d645cb2e..7cf300e88 100644 --- a/temporalcli/commands.schedule.go +++ b/temporalcli/commands.schedule.go @@ -270,6 +270,8 @@ func toScheduleAction(sw *SharedWorkflowStartOptions, i *PayloadInputOptions) (c // RetryPolicy not supported yet UntypedSearchAttributes: untypedSearchAttributes, Memo: opts.Memo, + StaticSummary: opts.StaticSummary, + StaticDetails: opts.StaticDetails, } if action.Args, err = i.buildRawInput(); err != nil { return nil, err diff --git a/temporalcli/commands.schedule_test.go b/temporalcli/commands.schedule_test.go index ec2167438..d24dc02c2 100644 --- a/temporalcli/commands.schedule_test.go +++ b/temporalcli/commands.schedule_test.go @@ -176,6 +176,24 @@ func (s *SharedServerSuite) TestSchedule_CreateDescribe_SearchAttributes_Memo() s.ContainsOnSameLine(out, "Action", "wfMemo", b64(`"other data"`)) } +func (s *SharedServerSuite) TestSchedule_CreateDescribe_UserMetadata() { + schedId, _, res := s.createSchedule("--interval", "10d", + "--static-summary", "summ", + "--static-details", "details", + ) + s.NoError(res.Err) + + res = s.Execute( + "schedule", "describe", + "--address", s.Address(), + "-s", schedId, + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.ContainsOnSameLine(out, "Action", "Summary", "summ") + s.ContainsOnSameLine(out, "Action", "Details", "details") +} + func (s *SharedServerSuite) TestSchedule_List() { res := s.Execute( "operator", "search-attribute", "create", diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index 08393a7d9..cb5cf77f6 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -386,6 +386,8 @@ func buildStartOptions(sw *SharedWorkflowStartOptions, w *WorkflowStartOptions) CronSchedule: w.Cron, WorkflowExecutionErrorWhenAlreadyStarted: w.FailExisting, StartDelay: w.StartDelay.Duration(), + StaticSummary: sw.StaticSummary, + StaticDetails: sw.StaticDetails, } if w.IdReusePolicy.Value != "" { var err error diff --git a/temporalcli/commands.workflow_view.go b/temporalcli/commands.workflow_view.go index a1b969322..70c25ddaa 100644 --- a/temporalcli/commands.workflow_view.go +++ b/temporalcli/commands.workflow_view.go @@ -134,6 +134,20 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin HistorySize: info.HistorySizeBytes, }, printer.StructuredOptions{}) + staticSummary := resp.GetExecutionConfig().GetUserMetadata().GetSummary() + staticDetails := resp.GetExecutionConfig().GetUserMetadata().GetDetails() + if len(staticSummary.GetData()) > 0 || len(staticDetails.GetData()) > 0 { + cctx.Printer.Println() + cctx.Printer.Println(color.MagentaString("Metadata:")) + _ = cctx.Printer.PrintStructured(struct { + StaticSummary *common.Payload + StaticDetails *common.Payload + }{ + StaticSummary: staticSummary, + StaticDetails: staticDetails, + }, printer.StructuredOptions{}) + } + if info.VersioningInfo != nil { cctx.Printer.Println() cctx.Printer.Println(color.MagentaString("Versioning Info:")) diff --git a/temporalcli/commands.workflow_view_test.go b/temporalcli/commands.workflow_view_test.go index fe169bbd4..31bfc96af 100644 --- a/temporalcli/commands.workflow_view_test.go +++ b/temporalcli/commands.workflow_view_test.go @@ -950,3 +950,46 @@ func (s *SharedServerSuite) Test_WorkflowResult() { s.Contains(output, `"message": "failed on purpose"`) s.Contains(output, "workflowExecutionFailedEventAttributes") } + +func (s *SharedServerSuite) TestWorkflow_Describe_WorkflowMetadata() { + workflowId := uuid.NewString() + + s.Worker().OnDevWorkflow(func(ctx workflow.Context, input any) (any, error) { + return map[string]string{"foo": "bar"}, nil + }) + + res := s.Execute( + "workflow", "start", + "--address", s.Address(), + "--task-queue", s.Worker().Options.TaskQueue, + "--type", "DevWorkflow", + "--workflow-id", workflowId, + "--static-summary", "summie", + "--static-details", "deets", + ) + s.NoError(res.Err) + + // Text + res = s.Execute( + "workflow", "describe", + "--address", s.Address(), + "-w", workflowId, + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.ContainsOnSameLine(out, "StaticSummary", "summie") + s.ContainsOnSameLine(out, "StaticDetails", "deets") + + // JSON + res = s.Execute( + "workflow", "describe", + "-o", "json", + "--address", s.Address(), + "-w", workflowId, + ) + s.NoError(res.Err) + var jsonOut workflowservice.DescribeWorkflowExecutionResponse + s.NoError(temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonOut, true)) + s.NotNil(jsonOut.ExecutionConfig.UserMetadata.Summary) + s.NotNil(jsonOut.ExecutionConfig.UserMetadata.Details) +} diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 85ec87d2e..ea676a117 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -3991,6 +3991,18 @@ option-sets: description: | Memo using 'KEY="VALUE"' pairs. Use JSON values. + - name: static-summary + type: string + experimental: true + description: | + Static Workflow summary for human consumption in UIs. + Uses Temporal Markdown formatting, should be a single line. + - name: static-details + type: string + experimental: true + description: | + Static Workflow details for human consumption in UIs. + Uses Temporal Markdown formatting, may be multiple lines. - name: workflow-start options: From cbf2b52eb78a011d843c1c0005b5d24257938bcc Mon Sep 17 00:00:00 2001 From: Antonio Lain <135073478+antlai-temporal@users.noreply.github.com> Date: Thu, 27 Feb 2025 12:31:19 -0800 Subject: [PATCH 02/15] Fix batch deployment test nil pointer exception (#769) --- temporalcli/commands.workflow_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/temporalcli/commands.workflow_test.go b/temporalcli/commands.workflow_test.go index 8e479c82f..02fddb10f 100644 --- a/temporalcli/commands.workflow_test.go +++ b/temporalcli/commands.workflow_test.go @@ -528,7 +528,9 @@ func (s *SharedServerSuite) TestWorkflow_Batch_Update_Options_Versioning_Overrid var jsonResp workflowservice.DescribeWorkflowExecutionResponse assert.NoError(t, temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonResp, true)) - versioningInfo := jsonResp.WorkflowExecutionInfo.VersioningInfo + + versioningInfo := jsonResp.GetWorkflowExecutionInfo().GetVersioningInfo() + assert.NotNil(t, versioningInfo) assert.NotNil(t, versioningInfo.VersioningOverride) assert.Equal(t, version2, versioningInfo.VersioningOverride.PinnedVersion) } From 97cc6c27149b812d92f43abef2c2250ca79c53e0 Mon Sep 17 00:00:00 2001 From: Yuri Date: Thu, 27 Feb 2025 12:49:39 -0800 Subject: [PATCH 03/15] Add fields from extended info to DescribeWorkflow output (#771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What was changed Add extended info to the DescribeWorkflow output. ## Why? Customer request. --- temporalcli/commands.workflow_view.go | 67 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/temporalcli/commands.workflow_view.go b/temporalcli/commands.workflow_view.go index 70c25ddaa..75f1db34e 100644 --- a/temporalcli/commands.workflow_view.go +++ b/temporalcli/commands.workflow_view.go @@ -102,36 +102,47 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin cctx.Printer.Println(color.MagentaString("Execution Info:")) info := resp.WorkflowExecutionInfo + extendedInfo := resp.WorkflowExtendedInfo _ = cctx.Printer.PrintStructured(struct { - WorkflowId string - RunId string - Type string - Namespace string - TaskQueue string - AssignedBuildId string - StartTime time.Time - CloseTime time.Time `cli:",cardOmitEmpty"` - ExecutionTime time.Time `cli:",cardOmitEmpty"` - Memo map[string]*common.Payload `cli:",cardOmitEmpty"` - SearchAttributes map[string]*common.Payload `cli:",cardOmitEmpty"` - StateTransitionCount int64 - HistoryLength int64 - HistorySize int64 + WorkflowId string + RunId string + Type string + Namespace string + TaskQueue string + AssignedBuildId string + StartTime time.Time + CloseTime time.Time `cli:",cardOmitEmpty"` + ExecutionTime time.Time `cli:",cardOmitEmpty"` + Memo map[string]*common.Payload `cli:",cardOmitEmpty"` + SearchAttributes map[string]*common.Payload `cli:",cardOmitEmpty"` + StateTransitionCount int64 + HistoryLength int64 + HistorySize int64 + ExecutionExpirationTime time.Time `cli:",cardOmitEmpty"` + RunExpirationTime time.Time `cli:",cardOmitEmpty"` + CancelRequested bool + LastResetTime time.Time `cli:",cardOmitEmpty"` + OriginalStartTime time.Time `cli:",cardOmitEmpty"` }{ - WorkflowId: info.Execution.WorkflowId, - RunId: info.Execution.RunId, - Type: info.Type.GetName(), - Namespace: c.Parent.Namespace, - TaskQueue: info.TaskQueue, - AssignedBuildId: info.GetAssignedBuildId(), - StartTime: timestampToTime(info.StartTime), - CloseTime: timestampToTime(info.CloseTime), - ExecutionTime: timestampToTime(info.ExecutionTime), - Memo: info.Memo.GetFields(), - SearchAttributes: info.SearchAttributes.GetIndexedFields(), - StateTransitionCount: info.StateTransitionCount, - HistoryLength: info.HistoryLength, - HistorySize: info.HistorySizeBytes, + WorkflowId: info.Execution.WorkflowId, + RunId: info.Execution.RunId, + Type: info.Type.GetName(), + Namespace: c.Parent.Namespace, + TaskQueue: info.TaskQueue, + AssignedBuildId: info.GetAssignedBuildId(), + StartTime: timestampToTime(info.StartTime), + CloseTime: timestampToTime(info.CloseTime), + ExecutionTime: timestampToTime(info.ExecutionTime), + Memo: info.Memo.GetFields(), + SearchAttributes: info.SearchAttributes.GetIndexedFields(), + StateTransitionCount: info.StateTransitionCount, + HistoryLength: info.HistoryLength, + HistorySize: info.HistorySizeBytes, + ExecutionExpirationTime: timestampToTime(extendedInfo.ExecutionExpirationTime), + RunExpirationTime: timestampToTime(extendedInfo.RunExpirationTime), + CancelRequested: extendedInfo.CancelRequested, + LastResetTime: timestampToTime(extendedInfo.LastResetTime), + OriginalStartTime: timestampToTime(extendedInfo.OriginalStartTime), }, printer.StructuredOptions{}) staticSummary := resp.GetExecutionConfig().GetUserMetadata().GetSummary() From 94883d3b86f70b77f9d2e22c6a8015603fd88d05 Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Fri, 28 Feb 2025 09:32:02 -0800 Subject: [PATCH 04/15] added `workflow start-update-with-start` and `workflow execute-update-with-start` commands (#762) added `temporal workflow start-update-with-start` and `temporal workflow execute-update-with-start` commands `temporal workflow start-update-with-start` usage: ``` temporal workflow start-update-with-start \ --update-name YourUpdate \ --update-input '{"update-key": "update-value"}' \ --update-wait-for-stage accepted \ --workflow-id YourWorkflowId \ --type YourWorkflowType \ --task-queue YourTaskQueue \ --id-conflict-policy Fail \ --input '{"wf-key": "wf-value"}' ``` `temporal workflow execute-update-with-start` usage: ``` temporal workflow execute-update-with-start \ --update-name YourUpdate \ --update-input '{"update-key": "update-value"}' \ --workflow-id YourWorkflowId \ --type YourWorkflowType \ --task-queue YourTaskQueue \ --id-conflict-policy Fail \ --input '{"wf-key": "wf-value"}' ``` 1. Closes #664 2. How was this tested: 3. Any docs updates needed? Yes --- temporalcli/commands.gen.go | 114 +++++++- temporalcli/commands.workflow_exec.go | 182 ++++++++++++- temporalcli/commands.workflow_exec_test.go | 290 +++++++++++++++++---- temporalcli/commandsgen/commands.yml | 158 +++++++++++ 4 files changed, 693 insertions(+), 51 deletions(-) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index ccab78306..45a4069ca 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -2859,6 +2859,7 @@ func NewTemporalWorkflowCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalWorkflowDeleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowExecuteCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalWorkflowExecuteUpdateWithStartCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowFixHistoryJsonCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowListCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowMetadataCommand(cctx, &s).Command) @@ -2870,6 +2871,7 @@ func NewTemporalWorkflowCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalWorkflowSignalWithStartCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowStackCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowStartCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalWorkflowStartUpdateWithStartCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowTerminateCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowTraceCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowUpdateCommand(cctx, &s).Command) @@ -3026,6 +3028,58 @@ func NewTemporalWorkflowExecuteCommand(cctx *CommandContext, parent *TemporalWor return &s } +type TemporalWorkflowExecuteUpdateWithStartCommand struct { + Parent *TemporalWorkflowCommand + Command cobra.Command + SharedWorkflowStartOptions + WorkflowStartOptions + PayloadInputOptions + UpdateName string + UpdateFirstExecutionRunId string + UpdateId string + RunId string + UpdateInput []string + UpdateInputFile []string + UpdateInputMeta []string + UpdateInputBase64 bool +} + +func NewTemporalWorkflowExecuteUpdateWithStartCommand(cctx *CommandContext, parent *TemporalWorkflowCommand) *TemporalWorkflowExecuteUpdateWithStartCommand { + var s TemporalWorkflowExecuteUpdateWithStartCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "execute-update-with-start [flags]" + s.Command.Short = "Send an Update and wait for it to complete (Experimental)" + if hasHighlighting { + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete. If the Workflow Execution is not running, then a new workflow\nexecution is started and the update is sent.\n\nExperimental.\n\n\x1b[1mtemporal workflow execute-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\x1b[0m" + } else { + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete. If the Workflow Execution is not running, then a new workflow\nexecution is started and the update is sent.\n\nExperimental.\n\n```\ntemporal workflow execute-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.UpdateName, "update-name", "", "Update name. Required. Aliased as \"--update-type\".") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "update-name") + s.Command.Flags().StringVar(&s.UpdateFirstExecutionRunId, "update-first-execution-run-id", "", "Parent Run ID. The update is sent to the last Workflow Execution in the chain started with this Run ID.") + s.Command.Flags().StringVar(&s.UpdateId, "update-id", "", "Update ID. If unset, defaults to a UUID.") + s.Command.Flags().StringVarP(&s.RunId, "run-id", "r", "", "Run ID. If unset, looks for an Update against the currently-running Workflow Execution.") + s.Command.Flags().StringArrayVar(&s.UpdateInput, "update-input", nil, "Update input value. Use JSON content or set --update-input-meta to override. Can't be combined with --update-input-file. Can be passed multiple times to pass multiple arguments.") + s.Command.Flags().StringArrayVar(&s.UpdateInputFile, "update-input-file", nil, "A path or paths for input file(s). Use JSON content or set --update-input-meta to override. Can't be combined with --update-input. Can be passed multiple times to pass multiple arguments.") + s.Command.Flags().StringArrayVar(&s.UpdateInputMeta, "update-input-meta", nil, "Input update payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times.") + s.Command.Flags().BoolVar(&s.UpdateInputBase64, "update-input-base64", false, "Assume update inputs are base64-encoded and attempt to decode them.") + s.SharedWorkflowStartOptions.buildFlags(cctx, s.Command.Flags()) + s.WorkflowStartOptions.buildFlags(cctx, s.Command.Flags()) + s.PayloadInputOptions.buildFlags(cctx, s.Command.Flags()) + s.Command.Flags().SetNormalizeFunc(aliasNormalizer(map[string]string{ + "name": "type", + "update-type": "update-name", + })) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalWorkflowFixHistoryJsonCommand struct { Parent *TemporalWorkflowCommand Command cobra.Command @@ -3407,6 +3461,62 @@ func NewTemporalWorkflowStartCommand(cctx *CommandContext, parent *TemporalWorkf return &s } +type TemporalWorkflowStartUpdateWithStartCommand struct { + Parent *TemporalWorkflowCommand + Command cobra.Command + SharedWorkflowStartOptions + WorkflowStartOptions + PayloadInputOptions + UpdateName string + UpdateFirstExecutionRunId string + UpdateWaitForStage StringEnum + UpdateId string + RunId string + UpdateInput []string + UpdateInputFile []string + UpdateInputMeta []string + UpdateInputBase64 bool +} + +func NewTemporalWorkflowStartUpdateWithStartCommand(cctx *CommandContext, parent *TemporalWorkflowCommand) *TemporalWorkflowStartUpdateWithStartCommand { + var s TemporalWorkflowStartUpdateWithStartCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "start-update-with-start [flags]" + s.Command.Short = "Send an Update and wait for it to be accepted or rejected (Experimental)" + if hasHighlighting { + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. If the Workflow Execution is not running, \nthen a new workflow execution is started and the update is sent.\n\nExperimental.\n\n\x1b[1mtemporal workflow start-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --update-wait-for-stage accepted \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\x1b[0m" + } else { + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. If the Workflow Execution is not running, \nthen a new workflow execution is started and the update is sent.\n\nExperimental.\n\n```\ntemporal workflow start-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --update-wait-for-stage accepted \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.UpdateName, "update-name", "", "Update name. Required. Aliased as \"--update-type\".") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "update-name") + s.Command.Flags().StringVar(&s.UpdateFirstExecutionRunId, "update-first-execution-run-id", "", "Parent Run ID. The update is sent to the last Workflow Execution in the chain started with this Run ID.") + s.UpdateWaitForStage = NewStringEnum([]string{"accepted"}, "") + s.Command.Flags().Var(&s.UpdateWaitForStage, "update-wait-for-stage", "Update stage to wait for. The only option is `accepted`, but this option is required. This is to allow a future version of the CLI to choose a default value. Accepted values: accepted. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "update-wait-for-stage") + s.Command.Flags().StringVar(&s.UpdateId, "update-id", "", "Update ID. If unset, defaults to a UUID.") + s.Command.Flags().StringVarP(&s.RunId, "run-id", "r", "", "Run ID. If unset, looks for an Update against the currently-running Workflow Execution.") + s.Command.Flags().StringArrayVar(&s.UpdateInput, "update-input", nil, "Update input value. Use JSON content or set --update-input-meta to override. Can't be combined with --update-input-file. Can be passed multiple times to pass multiple arguments.") + s.Command.Flags().StringArrayVar(&s.UpdateInputFile, "update-input-file", nil, "A path or paths for input file(s). Use JSON content or set --update-input-meta to override. Can't be combined with --update-input. Can be passed multiple times to pass multiple arguments.") + s.Command.Flags().StringArrayVar(&s.UpdateInputMeta, "update-input-meta", nil, "Input update payload metadata as a `KEY=VALUE` pair. When the KEY is \"encoding\", this overrides the default (\"json/plain\"). Can be passed multiple times.") + s.Command.Flags().BoolVar(&s.UpdateInputBase64, "update-input-base64", false, "Assume update inputs are base64-encoded and attempt to decode them.") + s.SharedWorkflowStartOptions.buildFlags(cctx, s.Command.Flags()) + s.WorkflowStartOptions.buildFlags(cctx, s.Command.Flags()) + s.PayloadInputOptions.buildFlags(cctx, s.Command.Flags()) + s.Command.Flags().SetNormalizeFunc(aliasNormalizer(map[string]string{ + "name": "type", + "update-type": "update-name", + })) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalWorkflowTerminateCommand struct { Parent *TemporalWorkflowCommand Command cobra.Command @@ -3599,9 +3709,9 @@ func NewTemporalWorkflowUpdateStartCommand(cctx *CommandContext, parent *Tempora s.Command.Use = "start [flags]" s.Command.Short = "Send an Update and wait for it to be accepted or rejected (Experimental)" if hasHighlighting { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using \x1b[1mtemporal workflow update execute\x1b[0m.\n\nExperimental.\n\n\x1b[1mtemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\x1b[0m" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using \x1b[1mtemporal workflow update execute\x1b[0m.\n\nExperimental.\n\n\x1b[1mtemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\x1b[0m" } else { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using `temporal workflow update execute`.\n\nExperimental.\n\n```\ntemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n```" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using `temporal workflow update execute`.\n\nExperimental.\n\n```\ntemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\n```" } s.Command.Args = cobra.NoArgs s.WaitForStage = NewStringEnum([]string{"accepted"}, "") diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index cb5cf77f6..a88d89ba7 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "os" "reflect" @@ -121,7 +122,7 @@ func (c *TemporalWorkflowSignalWithStartCommand) run(cctx *CommandContext, _ []s signalPayloadInputOpts := PayloadInputOptions{ Input: c.SignalInput, InputFile: c.SignalInputFile, - InputMeta: c.InputMeta, + InputMeta: c.SignalInputMeta, InputBase64: c.SignalInputBase64, } signalInput, err := signalPayloadInputOpts.buildRawInputPayloads() @@ -194,16 +195,191 @@ func (c *TemporalWorkflowSignalWithStartCommand) run(cctx *CommandContext, _ []s RunId string `json:"runId"` Type string `json:"type"` Namespace string `json:"namespace"` - TaskQueue string `json:"taskQueue"` }{ WorkflowId: c.WorkflowId, RunId: resp.RunId, Type: c.Type, Namespace: c.Parent.Namespace, - TaskQueue: c.TaskQueue, }, printer.StructuredOptions{}) } +func (c *TemporalWorkflowStartUpdateWithStartCommand) run(cctx *CommandContext, _ []string) error { + waitForStage := client.WorkflowUpdateStageUnspecified + switch c.UpdateWaitForStage.Value { + case "accepted": + waitForStage = client.WorkflowUpdateStageAccepted + } + if waitForStage != client.WorkflowUpdateStageAccepted { + return fmt.Errorf("invalid wait for stage: %v, valid values are: 'accepted'", c.UpdateWaitForStage) + } + + updatePayloadInputOpts := PayloadInputOptions{ + Input: c.UpdateInput, + InputFile: c.UpdateInputFile, + InputMeta: c.UpdateInputMeta, + InputBase64: c.UpdateInputBase64, + } + updateInput, err := updatePayloadInputOpts.buildRawInput() + if err != nil { + return err + } + updateOpts := client.UpdateWorkflowOptions{ + UpdateID: c.UpdateId, + WorkflowID: c.WorkflowId, + RunID: c.RunId, + UpdateName: c.UpdateName, + Args: updateInput, + WaitForStage: waitForStage, + FirstExecutionRunID: c.UpdateFirstExecutionRunId, + } + + handle, err := executeUpdateWithStartWorkflow( + cctx, + c.Parent.ClientOptions, + c.SharedWorkflowStartOptions, + c.WorkflowStartOptions, + c.PayloadInputOptions, + updateOpts, + ) + if err != nil { + return err + } + + // Currently we only accept 'accepted' as a valid wait for stage value, but we intend + // to support more in the future. + if waitForStage == client.WorkflowUpdateStageAccepted { + // Use a canceled context to check whether the initial server response + // shows that the update has _already_ failed, without issuing a second request. + ctx, cancel := context.WithCancel(cctx) + cancel() + err = handle.Get(ctx, nil) + var timeoutOrCanceledErr *client.WorkflowUpdateServiceTimeoutOrCanceledError + if err != nil && !errors.As(err, &timeoutOrCanceledErr) { + return fmt.Errorf("unable to update workflow: %w", err) + } + } + + cctx.Printer.Println(color.MagentaString("Running execution:")) + return cctx.Printer.PrintStructured(struct { + WorkflowId string `json:"workflowId"` + RunId string `json:"runId"` + Type string `json:"type"` + Namespace string `json:"namespace"` + UpdateName string `json:"updateName"` + UpdateID string `json:"updateId"` + }{ + WorkflowId: c.WorkflowId, + RunId: handle.RunID(), + Type: c.Type, + Namespace: c.Parent.Namespace, + UpdateName: c.UpdateName, + UpdateID: handle.UpdateID(), + }, printer.StructuredOptions{}) +} + +func (c *TemporalWorkflowExecuteUpdateWithStartCommand) run(cctx *CommandContext, _ []string) error { + updatePayloadInputOpts := PayloadInputOptions{ + Input: c.UpdateInput, + InputFile: c.UpdateInputFile, + InputMeta: c.UpdateInputMeta, + InputBase64: c.UpdateInputBase64, + } + updateInput, err := updatePayloadInputOpts.buildRawInput() + if err != nil { + return err + } + + updateOpts := client.UpdateWorkflowOptions{ + UpdateName: c.UpdateName, + UpdateID: c.UpdateId, + WorkflowID: c.WorkflowId, + RunID: c.RunId, + Args: updateInput, + WaitForStage: client.WorkflowUpdateStageCompleted, + FirstExecutionRunID: c.UpdateFirstExecutionRunId, + } + + handle, err := executeUpdateWithStartWorkflow( + cctx, + c.Parent.ClientOptions, + c.SharedWorkflowStartOptions, + c.WorkflowStartOptions, + c.PayloadInputOptions, + updateOpts, + ) + if err != nil { + return err + } + + var valuePtr interface{} + err = handle.Get(cctx, &valuePtr) + if err != nil { + return fmt.Errorf("unable to update workflow: %w", err) + } + + cctx.Printer.Println(color.MagentaString("Running execution:")) + return cctx.Printer.PrintStructured(struct { + WorkflowId string `json:"workflowId"` + RunId string `json:"runId"` + Type string `json:"type"` + Namespace string `json:"namespace"` + UpdateName string `json:"updateName"` + UpdateID string `json:"updateId"` + UpdateResult interface{} `json:"updateResult"` + }{ + WorkflowId: c.WorkflowId, + RunId: handle.RunID(), + Type: c.Type, + Namespace: c.Parent.Namespace, + UpdateName: c.UpdateName, + UpdateID: c.UpdateId, + UpdateResult: valuePtr, + }, printer.StructuredOptions{}) +} + +func executeUpdateWithStartWorkflow( + cctx *CommandContext, + clientOpts ClientOptions, + sharedWfOpts SharedWorkflowStartOptions, + wfStartOpts WorkflowStartOptions, + wfInputOpts PayloadInputOptions, + updateWfOpts client.UpdateWorkflowOptions, +) (client.WorkflowUpdateHandle, error) { + if sharedWfOpts.WorkflowId == "" { + return nil, fmt.Errorf("--workflow-id flag must be provided") + } + if wfStartOpts.IdConflictPolicy.Value == "" { + return nil, fmt.Errorf("--id-conflict-policy flag must be provided") + } + cl, err := clientOpts.dialClient(cctx) + if err != nil { + return nil, err + } + defer cl.Close() + + clStartWfOpts, err := buildStartOptions(&sharedWfOpts, &wfStartOpts) + if err != nil { + return nil, err + } + + wfArgs, err := wfInputOpts.buildRawInput() + if err != nil { + return nil, err + } + + startOp := cl.NewWithStartWorkflowOperation( + clStartWfOpts, + sharedWfOpts.Type, + wfArgs..., + ) + + // Execute the update with start operation + return cl.UpdateWithStartWorkflow(cctx, client.UpdateWithStartWorkflowOptions{ + StartWorkflowOperation: startOp, + UpdateOptions: updateWfOpts, + }) +} + type workflowJSONResult struct { WorkflowId string `json:"workflowId"` RunId string `json:"runId"` diff --git a/temporalcli/commands.workflow_exec_test.go b/temporalcli/commands.workflow_exec_test.go index e706d9465..9d88a1100 100644 --- a/temporalcli/commands.workflow_exec_test.go +++ b/temporalcli/commands.workflow_exec_test.go @@ -675,8 +675,40 @@ func (s *SharedServerSuite) TestWorkflow_SignalWithStart_WorkflowIDMandatory() { s.ErrorContains(res.Err, "--workflow-id flag must be provided") } -func (s *SharedServerSuite) TestWorkflow_SignalWithStart_StartsWorkflow() { +func (s *SharedServerSuite) TestWorkflow_SignalWithStart_StartNewWorkflow() { + s.testSignalWithStartHelper(false) +} + +func (s *SharedServerSuite) TestWorkflow_SignalWithStart_SendSignalToExistingWorkflow() { + s.testSignalWithStartHelper(true) +} + +func (s *SharedServerSuite) testSignalWithStartHelper(useExistingWorkflow bool) { wfId := uuid.NewString() + signalWfInput := `"workflow-input"` + signalInput := `"signal-input"` + expectedWfOutput := map[string]string{ + "workflow": "workflow-input", + "signal": "signal-input", + } + + if useExistingWorkflow { + run, err := s.Client.ExecuteWorkflow(s.Context, client.StartWorkflowOptions{TaskQueue: s.Worker().Options.TaskQueue}, DevWorkflow, "not-signal-with-start-input") + s.NoError(err) + // Re-assign wfId for the signal to be sent to an existing workflow. + wfId = run.GetID() + expectedWfOutput["workflow"] = "not-signal-with-start-input" + } + + // Run workflow, block on signal. + s.Worker().OnDevWorkflow(func(ctx workflow.Context, wfInput any) (any, error) { + wfState := make(map[string]string) + wfState["workflow"] = wfInput.(string) + var sigReceived string + workflow.GetSignalChannel(ctx, "sigName").Receive(ctx, &sigReceived) + wfState["signal"] = sigReceived + return wfState, nil + }) // Send signal-with-start command. res := s.Execute( @@ -684,84 +716,250 @@ func (s *SharedServerSuite) TestWorkflow_SignalWithStart_StartsWorkflow() { "--address", s.Address(), "--workflow-id", wfId, "--type", "DevWorkflow", - "--input", `{"wf-signal-with-start": "workflow-input"}`, - "--task-queue", "tq", + "--input", signalWfInput, + "--task-queue", s.Worker().Options.TaskQueue, "--signal-name", "sigName", - "--signal-input", `{"signal-with-start": "signal-input"}`, + "--signal-input", signalInput, ) - s.NoError(res.Err) - // Confirm text output has key/vals as expected out := res.Stdout.String() s.ContainsOnSameLine(out, "WorkflowId", wfId) s.Contains(out, "RunId") - s.ContainsOnSameLine(out, "TaskQueue", "tq") s.ContainsOnSameLine(out, "Type", "DevWorkflow") s.ContainsOnSameLine(out, "Namespace", "default") - // Check that new workflow was started with expected workflow ID. - run := s.Client.GetWorkflow(s.Context, wfId, "") - s.Equal(wfId, run.GetID()) - - // Run workflow, block on signal. - var sigReceived any - s.StartDevWorker(s.t, DevWorkerOptions{TaskQueue: "tq"}).OnDevWorkflow(func(ctx workflow.Context, wfInput any) (any, error) { - workflow.GetSignalChannel(ctx, "sigName").Receive(ctx, &sigReceived) - return wfInput, nil - }) + // Check that a new workflow was started with expected workflow ID. + if !useExistingWorkflow { + run := s.Client.GetWorkflow(s.Context, wfId, "") + s.Equal(wfId, run.GetID()) + } // Wait for workflow to complete. - var wfReturn any + wfReturn := make(map[string]string) err := s.Client.GetWorkflow(s.Context, wfId, "").Get(s.Context, &wfReturn) s.NoError(err) - // Expect workflow to have received signal and given inputs from signal-with-start. - s.Equal(map[string]any{"signal-with-start": "signal-input"}, sigReceived) - s.Equal(map[string]any{"wf-signal-with-start": "workflow-input"}, wfReturn) + // Compare the extracted values with what the workflow returned + s.Equal(expectedWfOutput["signal"], wfReturn["signal"]) + s.Equal(expectedWfOutput["workflow"], wfReturn["workflow"]) } -func (s *SharedServerSuite) TestWorkflow_SignalWithStart_ExistingWorkflow() { - // Run workflow, block on signal. - var sigReceived any +func (s *SharedServerSuite) TestWorkflow_StartUpdateWithStart_RuntimeOptionChecks() { + res := s.Execute( + "workflow", "start-update-with-start", + "--type", "wfType", + "--task-queue", "tq", + "--update-name", "updateName", + "--update-wait-for-stage", "accepted", + ) + s.ErrorContains(res.Err, "--workflow-id flag must be provided") + res = s.Execute( + "workflow", "start-update-with-start", + "--type", "wfType", + "--task-queue", "tq", + "--update-name", "updateName", + "--update-wait-for-stage", "accepted", + "--workflow-id", "wfId", + ) + s.ErrorContains(res.Err, "--id-conflict-policy flag must be provided") +} + +func (s *SharedServerSuite) TestWorkflow_ExecuteUpdateWithStart_RuntimeOptionChecks() { + res := s.Execute( + "workflow", "execute-update-with-start", + "--type", "wfType", + "--task-queue", "tq", + "--update-name", "updateName", + ) + s.ErrorContains(res.Err, "--workflow-id flag must be provided") + res = s.Execute( + "workflow", "execute-update-with-start", + "--type", "wfType", + "--task-queue", "tq", + "--update-name", "updateName", + "--workflow-id", "wfId", + ) + s.ErrorContains(res.Err, "--id-conflict-policy flag must be provided") +} + +type updateWithStartTest struct { + updateWithStartSetup + useStart bool + idConflictPolicy string + expectedError string + expectedUpdateResult string + expectedWfOutput map[string]string +} + +type updateWithStartSetup struct { + wfId string + updateName string + updateId string + useExistingWorkflow bool +} + +func (s *SharedServerSuite) TestWorkflow_StartUpdateWithStart_StartsNewWorkflow() { + updateWithStartSetup := s.updateWithStartTestSetup(false) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: true, + idConflictPolicy: "Fail", + expectedWfOutput: map[string]string{"workflow": "workflow-input", "update": "update-input"}, + }) +} + +func (s *SharedServerSuite) TestWorkflow_StartUpdateWithStart_SendUpdateToExistingWorkflow() { + updateWithStartSetup := s.updateWithStartTestSetup(true) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: true, + idConflictPolicy: "Fail", + expectedError: "Workflow execution is already running", + }) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: true, + idConflictPolicy: "UseExisting", + expectedWfOutput: map[string]string{"workflow": "not-update-with-start-workflow-input", "update": "update-input"}, + }) +} + +func (s *SharedServerSuite) TestWorkflow_ExecuteUpdateWithStart_StartsWorkflow() { + updateWithStartSetup := s.updateWithStartTestSetup(false) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: false, + idConflictPolicy: "Fail", + expectedUpdateResult: "update-input", + expectedWfOutput: map[string]string{"workflow": "workflow-input", "update": "update-input"}, + }) +} + +func (s *SharedServerSuite) TestWorkflow_ExecuteUpdateWithStart_SendUpdateToExistingWorkflow() { + updateWithStartSetup := s.updateWithStartTestSetup(true) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: false, + idConflictPolicy: "Fail", + expectedError: "Workflow execution is already running", + expectedWfOutput: map[string]string{"workflow": "workflow-input", "update": "update-input"}, + }) + s.testStartUpdateWithStartHelper(updateWithStartTest{ + updateWithStartSetup: updateWithStartSetup, + useStart: false, + idConflictPolicy: "UseExisting", + expectedWfOutput: map[string]string{"workflow": "not-update-with-start-workflow-input", "update": "update-input"}, + }) +} + +func (s *SharedServerSuite) updateWithStartTestSetup(useExistingWorkflow bool) updateWithStartSetup { + wfId := uuid.NewString() + updateName := "test-update-name" + updateId := uuid.NewString() + if useExistingWorkflow { + // Start a workflow with a specific workflow ID. + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + }, + DevWorkflow, + "not-update-with-start-workflow-input", + ) + s.NoError(err) + // Re-assign wfId for the update to be sent to an existing workflow. + wfId = run.GetID() + } + + // Run workflow. s.Worker().OnDevWorkflow(func(ctx workflow.Context, wfInput any) (any, error) { - workflow.GetSignalChannel(ctx, "sigName").Receive(ctx, &sigReceived) - return wfInput, nil + wfState := make(map[string]string) + wfState["workflow"] = wfInput.(string) + + err := workflow.SetUpdateHandlerWithOptions( + ctx, + updateName, + func(ctx workflow.Context, updateInput string) (string, error) { + wfState["update"] = updateInput + return updateInput, nil + }, + workflow.UpdateHandlerOptions{}, + ) + if err != nil { + return nil, err + } + // Block workflow completion on signal. + workflow.GetSignalChannel(ctx, "complete").Receive(ctx, nil) + return wfState, nil }) + return updateWithStartSetup{wfId, updateName, updateId, useExistingWorkflow} +} - // Start workflow - run, err := s.Client.ExecuteWorkflow(s.Context, client.StartWorkflowOptions{TaskQueue: s.Worker().Options.TaskQueue}, DevWorkflow, "not-signal-with-start-input") - s.NoError(err) +func (s *SharedServerSuite) testStartUpdateWithStartHelper(opts updateWithStartTest) { + cmdName := "execute-update-with-start" + additionalArgs := []string{} - wfId := run.GetID() + if opts.useStart { + cmdName = "start-update-with-start" + additionalArgs = []string{"--update-wait-for-stage", "accepted"} + } - // Send signal-with-start command. - res := s.Execute( - "workflow", "signal-with-start", + baseArgs := []string{ + "workflow", cmdName, "--address", s.Address(), - "--workflow-id", wfId, + "--workflow-id", opts.wfId, "--type", "DevWorkflow", - "--input", `{"workflow": "workflow-input"}`, + "--input", `"workflow-input"`, "--task-queue", s.Worker().Options.TaskQueue, - "--signal-name", "sigName", - "--signal-input", `{"signal-with-start": "signal-input"}`, - ) + "--id-conflict-policy", opts.idConflictPolicy, + "--update-name", opts.updateName, + "--update-id", opts.updateId, + "--update-input", `"update-input"`, + } + + // Send start-update-with-start command. + args := append(baseArgs, additionalArgs...) + res := s.Execute(args...) + + // Check expected error. + if opts.expectedError != "" { + s.ErrorContains(res.Err, opts.expectedError) + return + } + s.NoError(res.Err) // Confirm text output has key/vals as expected out := res.Stdout.String() - s.ContainsOnSameLine(out, "WorkflowId", wfId) + s.ContainsOnSameLine(out, "WorkflowId", opts.wfId) s.Contains(out, "RunId") - s.ContainsOnSameLine(out, "TaskQueue", s.Worker().Options.TaskQueue) s.ContainsOnSameLine(out, "Type", "DevWorkflow") s.ContainsOnSameLine(out, "Namespace", "default") + s.ContainsOnSameLine(out, "UpdateName", opts.updateName) + s.ContainsOnSameLine(out, "UpdateID", opts.updateId) + + // Check expected update result. + if opts.expectedUpdateResult != "" { + s.ContainsOnSameLine(out, "UpdateResult", opts.expectedUpdateResult) + } + + // Check that new workflow was started with expected workflow ID. + if !opts.useExistingWorkflow { + run := s.Client.GetWorkflow(s.Context, opts.wfId, "") + s.Equal(opts.wfId, run.GetID()) + } + + // Send signal to complete workflow. + err := s.Client.SignalWorkflow(s.Context, opts.wfId, "", "complete", nil) + s.NoError(err) // Wait for workflow to complete. - var ret any - s.NoError(run.Get(s.Context, &ret)) + wfReturn := make(map[string]string) + err = s.Client.GetWorkflow(s.Context, opts.wfId, "").Get(s.Context, &wfReturn) + s.NoError(err) - // Expect workflow to have not been started by the signal-with-start command. - s.Equal("not-signal-with-start-input", ret) - // Expect signal to have been received with given input. - s.Equal(map[string]any{"signal-with-start": "signal-input"}, sigReceived) + // Expect workflow to have received update and given inputs from start-update-with-start. + s.Equal(opts.expectedWfOutput["workflow"], wfReturn["workflow"]) + s.Equal(opts.expectedWfOutput["update"], wfReturn["update"]) } diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index ea676a117..b0e6597a8 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -3673,6 +3673,7 @@ commands: --workflow-id YourWorkflowId \ --name YourUpdate \ --input '{"some-key": "some-value"}' + --wait-for-stage accepted ``` option-sets: - update-starting @@ -3688,6 +3689,163 @@ commands: - accepted required: true + - name: temporal workflow start-update-with-start + summary: Send an Update and wait for it to be accepted or rejected (Experimental) + description: | + Send a message to a Workflow Execution to invoke an Update handler, and wait for + the update to be accepted or rejected. If the Workflow Execution is not running, + then a new workflow execution is started and the update is sent. + + Experimental. + + ``` + temporal workflow start-update-with-start \ + --update-name YourUpdate \ + --update-input '{"update-key": "update-value"}' \ + --update-wait-for-stage accepted \ + --workflow-id YourWorkflowId \ + --type YourWorkflowType \ + --task-queue YourTaskQueue \ + --id-conflict-policy Fail \ + --input '{"wf-key": "wf-value"}' + ``` + option-sets: + # workflow-id and id-conflict-policy are "required" (runtime checks) + - shared-workflow-start + - workflow-start + - payload-input + options: + - name: update-name + type: string + description: Update name. + required: true + aliases: + - update-type + - name: update-first-execution-run-id + type: string + description: | + Parent Run ID. + The update is sent to the last Workflow Execution in the chain started + with this Run ID. + - name: update-wait-for-stage + type: string-enum + description: | + Update stage to wait for. + The only option is `accepted`, but this option is required. This is to allow + a future version of the CLI to choose a default value. + enum-values: + - accepted + required: true + - name: update-id + type: string + description: | + Update ID. + If unset, defaults to a UUID. + - name: run-id + type: string + short: r + description: | + Run ID. + If unset, looks for an Update against the currently-running Workflow Execution. + - name: update-input + type: string[] + description: | + Update input value. + Use JSON content or set --update-input-meta to override. + Can't be combined with --update-input-file. + Can be passed multiple times to pass multiple arguments. + - name: update-input-file + type: string[] + description: | + A path or paths for input file(s). + Use JSON content or set --update-input-meta to override. + Can't be combined with --update-input. + Can be passed multiple times to pass multiple arguments. + - name: update-input-meta + type: string[] + description: | + Input update payload metadata as a `KEY=VALUE` pair. + When the KEY is "encoding", this overrides the default ("json/plain"). + Can be passed multiple times. + - name: update-input-base64 + type: bool + description: | + Assume update inputs are base64-encoded and attempt to decode them. + + - name: temporal workflow execute-update-with-start + summary: Send an Update and wait for it to complete (Experimental) + description: | + Send a message to a Workflow Execution to invoke an Update handler, and wait for + the update to complete. If the Workflow Execution is not running, then a new workflow + execution is started and the update is sent. + + Experimental. + + ``` + temporal workflow execute-update-with-start \ + --update-name YourUpdate \ + --update-input '{"update-key": "update-value"}' \ + --workflow-id YourWorkflowId \ + --type YourWorkflowType \ + --task-queue YourTaskQueue \ + --id-conflict-policy Fail \ + --input '{"wf-key": "wf-value"}' + ``` + + option-sets: + # workflow-id and id-conflict-policy are "required" (runtime checks) + - shared-workflow-start + - workflow-start + - payload-input + options: + - name: update-name + type: string + description: Update name. + required: true + aliases: + - update-type + - name: update-first-execution-run-id + type: string + description: | + Parent Run ID. + The update is sent to the last Workflow Execution in the chain started + with this Run ID. + - name: update-id + type: string + description: | + Update ID. + If unset, defaults to a UUID. + - name: run-id + type: string + short: r + description: | + Run ID. + If unset, looks for an Update against the currently-running Workflow Execution. + - name: update-input + type: string[] + description: | + Update input value. + Use JSON content or set --update-input-meta to override. + Can't be combined with --update-input-file. + Can be passed multiple times to pass multiple arguments. + - name: update-input-file + type: string[] + description: | + A path or paths for input file(s). + Use JSON content or set --update-input-meta to override. + Can't be combined with --update-input. + Can be passed multiple times to pass multiple arguments. + - name: update-input-meta + type: string[] + description: | + Input update payload metadata as a `KEY=VALUE` pair. + When the KEY is "encoding", this overrides the default ("json/plain"). + Can be passed multiple times. + - name: update-input-base64 + type: bool + description: | + Assume update inputs are base64-encoded and attempt to decode them. + option-sets: - name: client options: From 57e8cecf5ecd6a69d77edc7aaa190036588eb257 Mon Sep 17 00:00:00 2001 From: Jacob Barzee Date: Wed, 5 Mar 2025 15:46:59 -0700 Subject: [PATCH 05/15] quote attribute type to make whitespace more obvious (#772) ## What was changed quote attribute type in error message ## Why? To make whitespace more obvious --------- Co-authored-by: Rodrigo Zhou <2068124+rodrigozhou@users.noreply.github.com> --- temporalcli/commands.operator_search_attribute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temporalcli/commands.operator_search_attribute.go b/temporalcli/commands.operator_search_attribute.go index 405a31b85..afdca478c 100644 --- a/temporalcli/commands.operator_search_attribute.go +++ b/temporalcli/commands.operator_search_attribute.go @@ -60,7 +60,7 @@ func searchAttributeTypeStringToEnum(search string) (enums.IndexedValueType, err return enums.IndexedValueType(v), nil } } - return enums.INDEXED_VALUE_TYPE_UNSPECIFIED, fmt.Errorf("unsupported search attribute type: %v", search) + return enums.INDEXED_VALUE_TYPE_UNSPECIFIED, fmt.Errorf("unsupported search attribute type: %q", search) } func (c *TemporalOperatorSearchAttributeRemoveCommand) run(cctx *CommandContext, args []string) error { From 1a88da6ffe6b2957f13ca3ec9954e4b549ec3948 Mon Sep 17 00:00:00 2001 From: Yuri Date: Fri, 7 Mar 2025 12:34:07 -0800 Subject: [PATCH 06/15] Fix for DescribeWorkflow (#773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What was changed Check if extended info is not nil. ## Why? https://github.com/temporalio/cli/pull/771 Code assumes that some extended info exists in proto. Which is not true for older server versions.. --- temporalcli/commands.workflow_view.go | 85 +++++++++++++++------------ 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/temporalcli/commands.workflow_view.go b/temporalcli/commands.workflow_view.go index 75f1db34e..94e721dec 100644 --- a/temporalcli/commands.workflow_view.go +++ b/temporalcli/commands.workflow_view.go @@ -102,49 +102,56 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin cctx.Printer.Println(color.MagentaString("Execution Info:")) info := resp.WorkflowExecutionInfo - extendedInfo := resp.WorkflowExtendedInfo _ = cctx.Printer.PrintStructured(struct { - WorkflowId string - RunId string - Type string - Namespace string - TaskQueue string - AssignedBuildId string - StartTime time.Time - CloseTime time.Time `cli:",cardOmitEmpty"` - ExecutionTime time.Time `cli:",cardOmitEmpty"` - Memo map[string]*common.Payload `cli:",cardOmitEmpty"` - SearchAttributes map[string]*common.Payload `cli:",cardOmitEmpty"` - StateTransitionCount int64 - HistoryLength int64 - HistorySize int64 - ExecutionExpirationTime time.Time `cli:",cardOmitEmpty"` - RunExpirationTime time.Time `cli:",cardOmitEmpty"` - CancelRequested bool - LastResetTime time.Time `cli:",cardOmitEmpty"` - OriginalStartTime time.Time `cli:",cardOmitEmpty"` + WorkflowId string + RunId string + Type string + Namespace string + TaskQueue string + AssignedBuildId string + StartTime time.Time + CloseTime time.Time `cli:",cardOmitEmpty"` + ExecutionTime time.Time `cli:",cardOmitEmpty"` + Memo map[string]*common.Payload `cli:",cardOmitEmpty"` + SearchAttributes map[string]*common.Payload `cli:",cardOmitEmpty"` + StateTransitionCount int64 + HistoryLength int64 + HistorySize int64 }{ - WorkflowId: info.Execution.WorkflowId, - RunId: info.Execution.RunId, - Type: info.Type.GetName(), - Namespace: c.Parent.Namespace, - TaskQueue: info.TaskQueue, - AssignedBuildId: info.GetAssignedBuildId(), - StartTime: timestampToTime(info.StartTime), - CloseTime: timestampToTime(info.CloseTime), - ExecutionTime: timestampToTime(info.ExecutionTime), - Memo: info.Memo.GetFields(), - SearchAttributes: info.SearchAttributes.GetIndexedFields(), - StateTransitionCount: info.StateTransitionCount, - HistoryLength: info.HistoryLength, - HistorySize: info.HistorySizeBytes, - ExecutionExpirationTime: timestampToTime(extendedInfo.ExecutionExpirationTime), - RunExpirationTime: timestampToTime(extendedInfo.RunExpirationTime), - CancelRequested: extendedInfo.CancelRequested, - LastResetTime: timestampToTime(extendedInfo.LastResetTime), - OriginalStartTime: timestampToTime(extendedInfo.OriginalStartTime), + WorkflowId: info.Execution.WorkflowId, + RunId: info.Execution.RunId, + Type: info.Type.GetName(), + Namespace: c.Parent.Namespace, + TaskQueue: info.TaskQueue, + AssignedBuildId: info.GetAssignedBuildId(), + StartTime: timestampToTime(info.StartTime), + CloseTime: timestampToTime(info.CloseTime), + ExecutionTime: timestampToTime(info.ExecutionTime), + Memo: info.Memo.GetFields(), + SearchAttributes: info.SearchAttributes.GetIndexedFields(), + StateTransitionCount: info.StateTransitionCount, + HistoryLength: info.HistoryLength, + HistorySize: info.HistorySizeBytes, }, printer.StructuredOptions{}) + extendedInfo := resp.WorkflowExtendedInfo + if extendedInfo != nil { + cctx.Printer.Println(color.MagentaString("Extended Execution Info:")) + _ = cctx.Printer.PrintStructured(struct { + CancelRequested bool + ExecutionExpirationTime time.Time `cli:",cardOmitEmpty"` + RunExpirationTime time.Time `cli:",cardOmitEmpty"` + LastResetTime time.Time `cli:",cardOmitEmpty"` + OriginalStartTime time.Time `cli:",cardOmitEmpty"` + }{ + CancelRequested: extendedInfo.CancelRequested, + ExecutionExpirationTime: timestampToTime(extendedInfo.ExecutionExpirationTime), + RunExpirationTime: timestampToTime(extendedInfo.RunExpirationTime), + LastResetTime: timestampToTime(extendedInfo.LastResetTime), + OriginalStartTime: timestampToTime(extendedInfo.OriginalStartTime), + }, printer.StructuredOptions{}) + } + staticSummary := resp.GetExecutionConfig().GetUserMetadata().GetSummary() staticDetails := resp.GetExecutionConfig().GetUserMetadata().GetDetails() if len(staticSummary.GetData()) > 0 || len(staticDetails.GetData()) > 0 { From cbd133b524e4a2f82f5075cc4c50cfa69fdbac91 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 25 Mar 2025 19:03:18 +0000 Subject: [PATCH 07/15] Expose parent and root execution in describe call (#781) --- temporalcli/commands.workflow_view.go | 8 +++ temporalcli/commands.workflow_view_test.go | 61 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/temporalcli/commands.workflow_view.go b/temporalcli/commands.workflow_view.go index 94e721dec..9bfb5337f 100644 --- a/temporalcli/commands.workflow_view.go +++ b/temporalcli/commands.workflow_view.go @@ -117,6 +117,10 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin StateTransitionCount int64 HistoryLength int64 HistorySize int64 + ParentWorkflowId string `cli:",cardOmitEmpty"` + ParentRunId string `cli:",cardOmitEmpty"` + RootWorkflowId string `cli:",cardOmitEmpty"` + RootRunId string `cli:",cardOmitEmpty"` }{ WorkflowId: info.Execution.WorkflowId, RunId: info.Execution.RunId, @@ -132,6 +136,10 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin StateTransitionCount: info.StateTransitionCount, HistoryLength: info.HistoryLength, HistorySize: info.HistorySizeBytes, + ParentWorkflowId: info.GetParentExecution().GetWorkflowId(), + ParentRunId: info.GetParentExecution().GetRunId(), + RootWorkflowId: info.GetRootExecution().GetWorkflowId(), + RootRunId: info.GetRootExecution().GetRunId(), }, printer.StructuredOptions{}) extendedInfo := resp.WorkflowExtendedInfo diff --git a/temporalcli/commands.workflow_view_test.go b/temporalcli/commands.workflow_view_test.go index 31bfc96af..a758b7815 100644 --- a/temporalcli/commands.workflow_view_test.go +++ b/temporalcli/commands.workflow_view_test.go @@ -993,3 +993,64 @@ func (s *SharedServerSuite) TestWorkflow_Describe_WorkflowMetadata() { s.NotNil(jsonOut.ExecutionConfig.UserMetadata.Summary) s.NotNil(jsonOut.ExecutionConfig.UserMetadata.Details) } + +func (s *SharedServerSuite) TestWorkflow_Describe_RootWorkflow() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, input any) (any, error) { + if input.(string) == "child" { + return "done", nil + } + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + childHandle := workflow.ExecuteChildWorkflow(ctx, DevWorkflow, "child") + var childWE workflow.Execution + err := childHandle.GetChildWorkflowExecution().Get(ctx, &childWE) + if err != nil { + return nil, err + } + err = childHandle.Get(ctx, nil) + if err != nil { + return nil, err + } + return childWE.ID, err + }) + + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{TaskQueue: s.Worker().Options.TaskQueue}, + DevWorkflow, + "ignored", + ) + s.NoError(err) + var wfRes string + err = run.Get(s.Context, &wfRes) + s.NoError(err) + + // Text + res := s.Execute( + "workflow", "describe", + "--address", s.Address(), + "-w", wfRes, + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.ContainsOnSameLine(out, "ParentWorkflowId", run.GetID()) + s.ContainsOnSameLine(out, "ParentRunId", run.GetRunID()) + s.ContainsOnSameLine(out, "RootWorkflowId", run.GetID()) + s.ContainsOnSameLine(out, "RootRunId", run.GetRunID()) + + // JSON + res = s.Execute( + "workflow", "describe", + "-o", "json", + "--address", s.Address(), + "-w", wfRes, + ) + s.NoError(res.Err) + var jsonOut workflowservice.DescribeWorkflowExecutionResponse + s.NoError(temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonOut, true)) + s.Equal(run.GetID(), jsonOut.WorkflowExecutionInfo.ParentExecution.GetWorkflowId()) + s.Equal(run.GetRunID(), jsonOut.WorkflowExecutionInfo.ParentExecution.GetRunId()) + s.Equal(run.GetID(), jsonOut.WorkflowExecutionInfo.RootExecution.GetWorkflowId()) + s.Equal(run.GetRunID(), jsonOut.WorkflowExecutionInfo.RootExecution.GetRunId()) +} From 99210f3dec51a7e3b4d20df96e510ae3ee2f3c27 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Thu, 27 Mar 2025 07:55:48 -0400 Subject: [PATCH 08/15] Remove experimental notice on Update (#782) --- temporalcli/commands.gen.go | 32 ++++++++++++++-------------- temporalcli/commandsgen/commands.yml | 24 ++++++--------------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 45a4069ca..d67c6c433 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -3049,7 +3049,7 @@ func NewTemporalWorkflowExecuteUpdateWithStartCommand(cctx *CommandContext, pare s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "execute-update-with-start [flags]" - s.Command.Short = "Send an Update and wait for it to complete (Experimental)" + s.Command.Short = "Send an Update-With-Start and wait for it to complete (Experimental)" if hasHighlighting { s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete. If the Workflow Execution is not running, then a new workflow\nexecution is started and the update is sent.\n\nExperimental.\n\n\x1b[1mtemporal workflow execute-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\x1b[0m" } else { @@ -3483,7 +3483,7 @@ func NewTemporalWorkflowStartUpdateWithStartCommand(cctx *CommandContext, parent s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "start-update-with-start [flags]" - s.Command.Short = "Send an Update and wait for it to be accepted or rejected (Experimental)" + s.Command.Short = "Send an Update-With-Start and wait for it to be accepted or rejected (Experimental)" if hasHighlighting { s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. If the Workflow Execution is not running, \nthen a new workflow execution is started and the update is sent.\n\nExperimental.\n\n\x1b[1mtemporal workflow start-update-with-start \\\n --update-name YourUpdate \\\n --update-input '{\"update-key\": \"update-value\"}' \\\n --update-wait-for-stage accepted \\\n --workflow-id YourWorkflowId \\\n --type YourWorkflowType \\\n --task-queue YourTaskQueue \\\n --id-conflict-policy Fail \\\n --input '{\"wf-key\": \"wf-value\"}'\x1b[0m" } else { @@ -3598,8 +3598,8 @@ func NewTemporalWorkflowUpdateCommand(cctx *CommandContext, parent *TemporalWork var s TemporalWorkflowUpdateCommand s.Parent = parent s.Command.Use = "update" - s.Command.Short = "Updates (Experimental)" - s.Command.Long = "An Update is a synchronous call to a Workflow Execution that can change its\nstate, control its flow, and return a result.\n\nExperimental." + s.Command.Short = "Send and interact with Updates" + s.Command.Long = "An Update is a synchronous call to a Workflow Execution that can change its\nstate, control its flow, and return a result." s.Command.Args = cobra.NoArgs s.Command.AddCommand(&NewTemporalWorkflowUpdateDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowUpdateExecuteCommand(cctx, &s).Command) @@ -3619,11 +3619,11 @@ func NewTemporalWorkflowUpdateDescribeCommand(cctx *CommandContext, parent *Temp s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "describe [flags]" - s.Command.Short = "Obtain status info about a specific Update (Experimental)" + s.Command.Short = "Obtain status info about a specific Update" if hasHighlighting { - s.Command.Long = "Given a Workflow Execution and an Update ID, return information about its current status, including\na result if it has finished.\n\nExperimental.\n\n\x1b[1mtemporal workflow update describe \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\x1b[0m" + s.Command.Long = "Given a Workflow Execution and an Update ID, return information about its current status, including\na result if it has finished.\n\n\x1b[1mtemporal workflow update describe \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\x1b[0m" } else { - s.Command.Long = "Given a Workflow Execution and an Update ID, return information about its current status, including\na result if it has finished.\n\nExperimental.\n\n```\ntemporal workflow update describe \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\n```" + s.Command.Long = "Given a Workflow Execution and an Update ID, return information about its current status, including\na result if it has finished.\n\n```\ntemporal workflow update describe \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\n```" } s.Command.Args = cobra.NoArgs s.UpdateTargetingOptions.buildFlags(cctx, s.Command.Flags()) @@ -3647,11 +3647,11 @@ func NewTemporalWorkflowUpdateExecuteCommand(cctx *CommandContext, parent *Tempo s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "execute [flags]" - s.Command.Short = "Send an Update and wait for it to complete (Experimental)" + s.Command.Short = "Send an Update and wait for it to complete" if hasHighlighting { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete or fail. You can also use this to wait for an existing\nupdate to complete, by submitting an existing update ID.\n\nExperimental.\n\n\x1b[1mtemporal workflow update execute \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\x1b[0m" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete or fail. You can also use this to wait for an existing\nupdate to complete, by submitting an existing update ID.\n\n\x1b[1mtemporal workflow update execute \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\x1b[0m" } else { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete or fail. You can also use this to wait for an existing\nupdate to complete, by submitting an existing update ID.\n\nExperimental.\n\n```\ntemporal workflow update execute \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n```" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to complete or fail. You can also use this to wait for an existing\nupdate to complete, by submitting an existing update ID.\n\n```\ntemporal workflow update execute \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n```" } s.Command.Args = cobra.NoArgs s.UpdateStartingOptions.buildFlags(cctx, s.Command.Flags()) @@ -3678,11 +3678,11 @@ func NewTemporalWorkflowUpdateResultCommand(cctx *CommandContext, parent *Tempor s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "result [flags]" - s.Command.Short = "Wait for a specific Update to complete (Experimental)" + s.Command.Short = "Wait for a specific Update to complete" if hasHighlighting { - s.Command.Long = "Given a Workflow Execution and an Update ID, wait for the Update to complete or fail and\nprint the result.\n\nExperimental.\n\n\x1b[1mtemporal workflow update result \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\x1b[0m" + s.Command.Long = "Given a Workflow Execution and an Update ID, wait for the Update to complete or fail and\nprint the result.\n\n\x1b[1mtemporal workflow update result \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\x1b[0m" } else { - s.Command.Long = "Given a Workflow Execution and an Update ID, wait for the Update to complete or fail and\nprint the result.\n\nExperimental.\n\n```\ntemporal workflow update result \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\n```" + s.Command.Long = "Given a Workflow Execution and an Update ID, wait for the Update to complete or fail and\nprint the result.\n\n```\ntemporal workflow update result \\\n --workflow-id YourWorkflowId \\\n --update-id YourUpdateId\n```" } s.Command.Args = cobra.NoArgs s.UpdateTargetingOptions.buildFlags(cctx, s.Command.Flags()) @@ -3707,11 +3707,11 @@ func NewTemporalWorkflowUpdateStartCommand(cctx *CommandContext, parent *Tempora s.Parent = parent s.Command.DisableFlagsInUseLine = true s.Command.Use = "start [flags]" - s.Command.Short = "Send an Update and wait for it to be accepted or rejected (Experimental)" + s.Command.Short = "Send an Update and wait for it to be accepted or rejected" if hasHighlighting { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using \x1b[1mtemporal workflow update execute\x1b[0m.\n\nExperimental.\n\n\x1b[1mtemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\x1b[0m" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using \x1b[1mtemporal workflow update execute\x1b[0m.\n\n\x1b[1mtemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\x1b[0m" } else { - s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using `temporal workflow update execute`.\n\nExperimental.\n\n```\ntemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\n```" + s.Command.Long = "Send a message to a Workflow Execution to invoke an Update handler, and wait for\nthe update to be accepted or rejected. You can subsequently wait for the update\nto complete by using `temporal workflow update execute`.\n\n```\ntemporal workflow update start \\\n --workflow-id YourWorkflowId \\\n --name YourUpdate \\\n --input '{\"some-key\": \"some-value\"}'\n --wait-for-stage accepted\n```" } s.Command.Args = cobra.NoArgs s.WaitForStage = NewStringEnum([]string{"accepted"}, "") diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index b0e6597a8..2e94dad79 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -3601,21 +3601,17 @@ commands: - workflow-reference - name: temporal workflow update - summary: Updates (Experimental) + summary: Send and interact with Updates description: | An Update is a synchronous call to a Workflow Execution that can change its state, control its flow, and return a result. - Experimental. - - name: temporal workflow update describe - summary: Obtain status info about a specific Update (Experimental) + summary: Obtain status info about a specific Update description: | Given a Workflow Execution and an Update ID, return information about its current status, including a result if it has finished. - Experimental. - ``` temporal workflow update describe \ --workflow-id YourWorkflowId \ @@ -3625,14 +3621,12 @@ commands: - update-targeting - name: temporal workflow update execute - summary: Send an Update and wait for it to complete (Experimental) + summary: Send an Update and wait for it to complete description: | Send a message to a Workflow Execution to invoke an Update handler, and wait for the update to complete or fail. You can also use this to wait for an existing update to complete, by submitting an existing update ID. - Experimental. - ``` temporal workflow update execute \ --workflow-id YourWorkflowId \ @@ -3644,13 +3638,11 @@ commands: - payload-input - name: temporal workflow update result - summary: Wait for a specific Update to complete (Experimental) + summary: Wait for a specific Update to complete description: | Given a Workflow Execution and an Update ID, wait for the Update to complete or fail and print the result. - Experimental. - ``` temporal workflow update result \ --workflow-id YourWorkflowId \ @@ -3660,14 +3652,12 @@ commands: - update-targeting - name: temporal workflow update start - summary: Send an Update and wait for it to be accepted or rejected (Experimental) + summary: Send an Update and wait for it to be accepted or rejected description: | Send a message to a Workflow Execution to invoke an Update handler, and wait for the update to be accepted or rejected. You can subsequently wait for the update to complete by using `temporal workflow update execute`. - Experimental. - ``` temporal workflow update start \ --workflow-id YourWorkflowId \ @@ -3690,7 +3680,7 @@ commands: required: true - name: temporal workflow start-update-with-start - summary: Send an Update and wait for it to be accepted or rejected (Experimental) + summary: Send an Update-With-Start and wait for it to be accepted or rejected (Experimental) description: | Send a message to a Workflow Execution to invoke an Update handler, and wait for the update to be accepted or rejected. If the Workflow Execution is not running, @@ -3773,7 +3763,7 @@ commands: Assume update inputs are base64-encoded and attempt to decode them. - name: temporal workflow execute-update-with-start - summary: Send an Update and wait for it to complete (Experimental) + summary: Send an Update-With-Start and wait for it to complete (Experimental) description: | Send a message to a Workflow Execution to invoke an Update handler, and wait for the update to complete. If the Workflow Execution is not running, then a new workflow From a466ce097c0d31736add4a2016f7ec5ccce89544 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Fri, 28 Mar 2025 12:21:51 -0700 Subject: [PATCH 09/15] Pin modernc/sqlite version 1.34.1 (#784) ## What was changed Pinned v1.34.1 for modernc/sqlite ## Why? 1.34.2 has a regression, waiting on https://gitlab.com/cznic/sqlite/-/issues/196 to be resolved. See https://github.com/temporalio/temporal/pull/7333 for more details ## Checklist 1. Closes #777 2. How was this tested: 3. Any docs updates needed? --- .github/workflows/ci.yaml | 2 +- cmd/temporal/main.go | 3 + go.mod | 122 ++++++++-------- go.sum | 278 ++++++++++++++++++------------------- temporalcli/sqlite_test.go | 23 +++ 5 files changed, 228 insertions(+), 200 deletions(-) create mode 100644 temporalcli/sqlite_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dec27bbdf..ee1a773a1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: jobs: build-test: strategy: - fail-fast: true + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, macos-13, windows-latest, ubuntu-arm] include: diff --git a/cmd/temporal/main.go b/cmd/temporal/main.go index 27a4c4f4e..82203bf0c 100644 --- a/cmd/temporal/main.go +++ b/cmd/temporal/main.go @@ -5,6 +5,9 @@ import ( "github.com/temporalio/cli/temporalcli" + // Prevent the pinned version of sqlite driver from unintentionally changing + // until https://gitlab.com/cznic/sqlite/-/issues/196 is resolved. + _ "modernc.org/sqlite" // Embed time zone database as a fallback if platform database can't be found _ "time/tzdata" ) diff --git a/go.mod b/go.mod index f251a0628..29df67da3 100644 --- a/go.mod +++ b/go.mod @@ -11,31 +11,32 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/nexus-rpc/sdk-go v0.3.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/temporalio/ui-server/v2 v2.36.0 - go.temporal.io/api v1.45.0 + go.temporal.io/api v1.46.0 go.temporal.io/sdk v1.33.0 go.temporal.io/server v1.27.1 - google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.5 + google.golang.org/grpc v1.71.0 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.34.1 ) require ( - cel.dev/expr v0.20.0 // indirect - cloud.google.com/go v0.118.2 // indirect - cloud.google.com/go/auth v0.14.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cel.dev/expr v0.22.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 cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.4.0 // indirect - cloud.google.com/go/monitoring v1.24.0 // indirect - cloud.google.com/go/storage v1.50.0 // indirect + cloud.google.com/go/iam v1.4.2 // indirect + cloud.google.com/go/monitoring v1.24.1 // indirect + cloud.google.com/go/storage v1.51.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/apache/thrift v0.21.0 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -45,8 +46,8 @@ require ( github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect - github.com/coreos/go-oidc/v3 v3.11.0 // indirect + github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect + github.com/coreos/go-oidc/v3 v3.13.0 // indirect github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect @@ -56,34 +57,35 @@ require ( github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.9.0 // indirect + github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/gocql/gocql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 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/grpc-gateway/v2 v2.26.1 // 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 github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/labstack/echo/v4 v4.9.1 // indirect - github.com/labstack/gommon v0.4.0 // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -97,10 +99,10 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.21.0 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.16.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -116,45 +118,49 @@ require ( github.com/uber-common/bark v1.3.0 // indirect github.com/uber-go/tally/v4 v4.1.17-0.20240412215630-22fe011f5ff0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.56.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.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 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + 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.0 // indirect + go.uber.org/dig v1.18.1 // indirect go.uber.org/fx v1.23.0 // indirect go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.10.0 // indirect - google.golang.org/api v0.222.0 // indirect - google.golang.org/genproto v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/api v0.228.0 // indirect + google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/validator.v2 v2.0.1 // indirect - modernc.org/libc v1.61.11 // indirect + modernc.org/cc/v4 v4.25.2 // indirect + modernc.org/gc/v2 v2.6.5 // indirect + modernc.org/gc/v3 v3.0.0 // indirect + modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.8.2 // indirect - modernc.org/sqlite v1.34.5 // indirect + modernc.org/memory v1.9.1 // indirect + modernc.org/strutil v1.2.1 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 62d3e7b05..c8a7eea02 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,24 @@ -cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= -cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +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= -cloud.google.com/go v0.118.2 h1:bKXO7RXMFDkniAAvvuMrAPtQ/VHrs9e7J5UT3yrGdTY= -cloud.google.com/go v0.118.2/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M= -cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= -cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +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= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.4.0 h1:ZNfy/TYfn2uh/ukvhp783WhnbVluqf/tzOaqVUPlIPA= -cloud.google.com/go/iam v1.4.0/go.mod h1:gMBgqPaERlriaOV0CUl//XUzDhSfXevn4OEUbg6VRs4= +cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q= +cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= -cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= -cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= -cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= -cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= -cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= +cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= +cloud.google.com/go/monitoring v1.24.1 h1:vKiypZVFD/5a3BbQMvI4gZdl8445ITzXFh257XBgrS0= +cloud.google.com/go/monitoring v1.24.1/go.mod h1:Z05d1/vn9NaujqY2voG6pVQXoJGbp+r3laV+LySt9K0= +cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= +cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -26,14 +26,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0 h1:nNMpRpnkWDAaqcpxMJvxa/Ud98gjbYwayJY4/9bdjiU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= @@ -70,11 +70,11 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF 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-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= -github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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= +github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -122,17 +122,15 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo= -github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= +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= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +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= @@ -145,28 +143,28 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW 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= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 h1:5lyLWsV+qCkoYqsKUDuycESh9DEIPVKN6iCFeL7ag50= -github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -175,10 +173,12 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX 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/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +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= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -187,8 +187,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= -github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -217,18 +217,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= -github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -264,15 +262,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a h1:AA9vgIBDjMHPC2McaGPojgV2dcI78ZC0TLNhYCXEKH8= github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a/go.mod h1:lzZQ3Noex5pfAy7mkAeCjcBDteYU85uWWnJ/y6gKU8k= -github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= -github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +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.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= +github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/rcrowley/go-metrics v0.0.0-20141108142129-dee209f2455f/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -296,9 +294,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -335,44 +332,44 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= +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= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= +go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +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.45.0 h1:2FZ3eUoOYjavBaQi3/V93MBl99Nq1ifFRjrRwT3MeC8= -go.temporal.io/api v1.45.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.46.0 h1:O1efPDB6O2B8uIeCDIa+3VZC7tZMvYsMZYQapSbHvCg= +go.temporal.io/api v1.46.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.33.0 h1:T91UzeRdlHTiMGgpygsItOH9+VSkg+M/mG85PqNjdog= go.temporal.io/sdk v1.33.0/go.mod h1:WwCmJZLy7zabz3ar5NRAQEygsdP8tgR9sDjISSHuWZw= go.temporal.io/server v1.27.1 h1:0dyBl25Ua7P4IOXifJg0xUXfnoYTNY1IlUFf1RL4OBo= @@ -383,8 +380,8 @@ 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= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +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= @@ -408,16 +405,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/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= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= 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= @@ -433,8 +430,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +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= @@ -449,11 +446,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 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= @@ -462,8 +459,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.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= @@ -475,11 +472,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20211103235746-7861aae1554b/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= @@ -487,8 +481,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -501,10 +495,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/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= @@ -523,8 +517,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -533,28 +527,28 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= -google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +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-20250218202821-56aae31c358a h1:Xx6e5r1AOINOgm2ZuzvwDueGlOOml4PKBUry8jqyS6U= -google.golang.org/genproto v0.0.0-20250218202821-56aae31c358a/go.mod h1:Cmg1ztsSOnOsWxOiPTOUX8gegyHg5xADRncIHdtec8U= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +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.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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/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= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -578,26 +572,28 @@ 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.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= -modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.23.15 h1:wFDan71KnYqeHz4eF63vmGE6Q6Pc0PUGDpP0PRMYjDc= -modernc.org/ccgo/v4 v4.23.15/go.mod h1:nJX30dks/IWuBOnVa7VRii9Me4/9TZ1SC9GNtmARTy0= +modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= +modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.20.4 h1:3pPOlMcblnu5CBU3w1BFtepwBnLezGjPYTH8xBeYZM8= +modernc.org/ccgo/v4 v4.20.4/go.mod h1:meYiLeaGpKQmHBw8roW4DXLkDvusG+MD7LJ/kYyAouU= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= -modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/libc v1.61.11 h1:6sZG8uB6EMMG7iTLPTndi8jyTdgAQNIeLGjCFICACZw= -modernc.org/libc v1.61.11/go.mod h1:HHX+srFdn839oaJRd0W8hBM3eg+mieyZCAjWwB08/nM= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.0.0 h1:JNEAEd0e/lnR1nlJemLPwS44KfBLBp4SAvZEZFaxfYU= +modernc.org/gc/v3 v3.0.0/go.mod h1:LG5UO1Ran4OO0JRKz2oNiXhR5nNrgz0PzH7UKhz0aMU= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= -modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= +modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= -modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= +modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk= +modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/temporalcli/sqlite_test.go b/temporalcli/sqlite_test.go new file mode 100644 index 000000000..fc95fd952 --- /dev/null +++ b/temporalcli/sqlite_test.go @@ -0,0 +1,23 @@ +package temporalcli + +import ( + _ "modernc.org/sqlite" + "os" + "strings" + "testing" +) + +// Pinning modernc.org/sqlite to this version until https://gitlab.com/cznic/sqlite/-/issues/196 is resolved +func TestSqliteVersion(t *testing.T) { + content, err := os.ReadFile("../go.mod") + if err != nil { + t.Fatalf("Failed to read go.mod: %v", err) + } + contentStr := string(content) + if !strings.Contains(contentStr, "modernc.org/sqlite v1.34.1") { + t.Errorf("go.mod missing dependency modernc.org/sqlite v1.34.1") + } + if !strings.Contains(contentStr, "modernc.org/libc v1.55.3") { + t.Errorf("go.mod missing dependency modernc.org/libc v1.55.3") + } +} From eabf08108ac8630fd2d312c4b91958d66273c4c8 Mon Sep 17 00:00:00 2001 From: Antonio Lain <135073478+antlai-temporal@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:47:29 -0700 Subject: [PATCH 10/15] Use `require` instead of `assert` in test (#785) --- temporalcli/commands.workflow_test.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/temporalcli/commands.workflow_test.go b/temporalcli/commands.workflow_test.go index 02fddb10f..2c3516743 100644 --- a/temporalcli/commands.workflow_test.go +++ b/temporalcli/commands.workflow_test.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/temporalio/cli/temporalcli" "go.temporal.io/api/enums/v1" "go.temporal.io/api/workflowservice/v1" @@ -505,6 +506,15 @@ func (s *SharedServerSuite) TestWorkflow_Batch_Update_Options_Versioning_Overrid } }, 30*time.Second, 100*time.Millisecond) + // Wait for all to appear in list + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: "CustomKeywordField = '" + searchAttr + "'", + }) + s.NoError(err) + return len(resp.Executions) == len(runs) + }, 3*time.Second, 100*time.Millisecond) + s.CommandHarness.Stdin.WriteString("y\n") res = s.Execute( "workflow", "update-options", @@ -524,15 +534,15 @@ func (s *SharedServerSuite) TestWorkflow_Batch_Update_Options_Versioning_Overrid "-w", run.GetID(), "--output", "json", ) - assert.NoError(t, res.Err) + require.NoError(t, res.Err) var jsonResp workflowservice.DescribeWorkflowExecutionResponse - assert.NoError(t, temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonResp, true)) + require.NoError(t, temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonResp, true)) versioningInfo := jsonResp.GetWorkflowExecutionInfo().GetVersioningInfo() - assert.NotNil(t, versioningInfo) - assert.NotNil(t, versioningInfo.VersioningOverride) - assert.Equal(t, version2, versioningInfo.VersioningOverride.PinnedVersion) + require.NotNil(t, versioningInfo) + require.NotNil(t, versioningInfo.VersioningOverride) + require.Equal(t, version2, versioningInfo.VersioningOverride.PinnedVersion) } }, 30*time.Second, 100*time.Millisecond) } From 63d2bea9dac76883186d2b6aca50aa284530195d Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 17 Apr 2025 09:03:24 -0700 Subject: [PATCH 11/15] Match tags in documentation repo (#791) ## What was changed Added a tags section to match what Documentation side has ## Why? We previously just used keywords, but that could be seen as keyword stuffing. The tags were updated on the docs side a few months ago, changes were never updated on this side. ## Checklist 1. Closes 2. How was this tested: 3. Any docs updates needed? --------- Co-authored-by: Chad Retz --- temporalcli/commands.gen.go | 16 ++++----- temporalcli/commandsgen/commands.yml | 52 ++++++++++++++++++++-------- temporalcli/commandsgen/docs.go | 9 +++-- temporalcli/commandsgen/parse.go | 4 +++ 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index d67c6c433..16e9224e0 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -2637,9 +2637,9 @@ func NewTemporalWorkerDeploymentDeleteVersionCommand(cctx *CommandContext, paren s.Command.Use = "delete-version [flags]" s.Command.Short = "Delete a Worker Deployment Version" if hasHighlighting { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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\x1b[1m--skip-drainage\x1b[0m.\n \n\x1b[1mtemporal worker deployment delete-version [options]\x1b[0m\n\nFor example, skipping the drainage restriction:\n\n\x1b[1mtemporal worker deployment delete-version \\\n --version YourDeploymentName.YourBuildID \\\n --skip-drainage \x1b[0m" + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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\x1b[1m--skip-drainage\x1b[0m.\n\n\x1b[1mtemporal worker deployment delete-version [options]\x1b[0m\n\nFor example, skipping the drainage restriction:\n\n\x1b[1mtemporal worker deployment delete-version \\\n --version YourDeploymentName.YourBuildID \\\n --skip-drainage \x1b[0m" } else { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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 \n```\ntemporal worker deployment delete-version [options]\n```\n\nFor example, skipping the drainage restriction:\n\n```\ntemporal worker deployment delete-version \\\n --version YourDeploymentName.YourBuildID \\\n --skip-drainage \n```" + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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\n```\ntemporal worker deployment delete-version [options]\n```\n\nFor example, skipping the drainage restriction:\n\n```\ntemporal worker deployment delete-version \\\n --version YourDeploymentName.YourBuildID \\\n --skip-drainage \n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.Identity, "identity", "", "Identity of the user submitting this request.") @@ -2749,9 +2749,9 @@ func NewTemporalWorkerDeploymentSetCurrentVersionCommand(cctx *CommandContext, p s.Command.Use = "set-current-version [flags]" s.Command.Short = "Make a Worker Deployment Version Current for a Deployment" if hasHighlighting { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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\x1b[1m--ignore-missing-task-queues\x1b[0m. Note that this would ignore task queues\nin a deployment that are not yet discovered, leading to inconsistent task\nqueue configuration.\n\n\x1b[1mtemporal worker deployment set-current-version [options]\x1b[0m\n\nFor example, to set the Current Version of a deployment\n\x1b[1mYourDeploymentName\x1b[0m, with a version with Build ID \x1b[1mYourBuildID\x1b[0m, and\nin the default namespace:\n\n\x1b[1mtemporal worker deployment set-current-version \\\n --version YourDeploymentName.YourBuildID\x1b[0m\n\nThe target of set-current-version can also be \x1b[1m__unversioned__\x1b[0m, which\nmoves tasks to unversioned workers, but in this case we also need to\nspecify the Deployment Name.\n\n\x1b[1mtemporal worker deployment set-current-version \\\n --version __unversioned__ \\\n --deployment-name YourDeploymentName\x1b[0m " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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\x1b[1m--ignore-missing-task-queues\x1b[0m. Note that this would ignore task queues\nin a deployment that are not yet discovered, leading to inconsistent task\nqueue configuration.\n\n\x1b[1mtemporal worker deployment set-current-version [options]\x1b[0m\n\nFor example, to set the Current Version of a deployment\n\x1b[1mYourDeploymentName\x1b[0m, with a version with Build ID \x1b[1mYourBuildID\x1b[0m, and\nin the default namespace:\n\n\x1b[1mtemporal worker deployment set-current-version \\\n --version YourDeploymentName.YourBuildID\x1b[0m\n\nThe target of set-current-version can also be \x1b[1m__unversioned__\x1b[0m, which\nmoves tasks to unversioned workers, but in this case we also need to\nspecify the Deployment Name.\n\n\x1b[1mtemporal worker deployment set-current-version \\\n --version __unversioned__ \\\n --deployment-name YourDeploymentName\x1b[0m" } else { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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 --version YourDeploymentName.YourBuildID\n```\n\nThe target of set-current-version can also be `__unversioned__`, which\nmoves tasks to unversioned workers, but in this case we also need to\nspecify the Deployment Name.\n\n```\ntemporal worker deployment set-current-version \\\n --version __unversioned__ \\\n --deployment-name YourDeploymentName\n``` " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\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 --version YourDeploymentName.YourBuildID\n```\n\nThe target of set-current-version can also be `__unversioned__`, which\nmoves tasks to unversioned workers, but in this case we also need to\nspecify the Deployment Name.\n\n```\ntemporal worker deployment set-current-version \\\n --version __unversioned__ \\\n --deployment-name YourDeploymentName\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.DeploymentName, "deployment-name", "", "Deployment name. Only needed when `--version` is `__unversioned__` or empty.") @@ -2786,9 +2786,9 @@ func NewTemporalWorkerDeploymentSetRampingVersionCommand(cctx *CommandContext, p s.Command.Use = "set-ramping-version [flags]" s.Command.Short = "Change Version Ramping settings for a Worker Deployment" if hasHighlighting { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n\nSet the Ramping Version and Percentage for a Deployment.\n\nThe Ramping Version can be set to a fully-qualified Version of the form\n\x1b[1mYourDeploymentName.YourBuildID\x1b[0m, or set to \"__unversioned__\", a special\nvalue that represents all the unversioned workers.\n\nThe Ramping Percentage is a float with values in the range [0, 100].\nA value of 100 does not make the Ramping Version Current, use\n\x1b[1mset-current-version\x1b[0m instead.\n\nTo remove a Ramping Version use the flag \x1b[1m--delete\x1b[0m.\n\nIf not all the expected Task Queues are being polled by Workers in the\nnew Ramping Version the request will fail. To override this protection use\n\x1b[1m--ignore-missing-task-queues\x1b[0m. Note that this would ignore task queues\nin a deployment that are not yet discovered, leading to inconsistent task\nqueue configuration.\n\n\x1b[1mtemporal worker deployment set-ramping-version [options]\x1b[0m\n\nFor example, to set the Ramping Version of a deployment\n\x1b[1mYourDeploymentName\x1b[0m, with a version with Build ID \x1b[1mYourBuildID\x1b[0m, with\n10 percent of tasks redirected to this version, and\nusing the default namespace:\n\n\x1b[1mtemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID\n --percentage 10.0\x1b[0m\n\nAnd to remove that ramping:\n \n\x1b[1mtemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID \\\n --delete\x1b[0m " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n\nSet the Ramping Version and Percentage for a Deployment.\n\nThe Ramping Version can be set to a fully-qualified Version of the form\n\x1b[1mYourDeploymentName.YourBuildID\x1b[0m, or set to \"__unversioned__\", a special\nvalue that represents all the unversioned workers.\n\nThe Ramping Percentage is a float with values in the range [0, 100].\nA value of 100 does not make the Ramping Version Current, use\n\x1b[1mset-current-version\x1b[0m instead.\n\nTo remove a Ramping Version use the flag \x1b[1m--delete\x1b[0m.\n\nIf not all the expected Task Queues are being polled by Workers in the\nnew Ramping Version the request will fail. To override this protection use\n\x1b[1m--ignore-missing-task-queues\x1b[0m. Note that this would ignore task queues\nin a deployment that are not yet discovered, leading to inconsistent task\nqueue configuration.\n\n\x1b[1mtemporal worker deployment set-ramping-version [options]\x1b[0m\n\nFor example, to set the Ramping Version of a deployment\n\x1b[1mYourDeploymentName\x1b[0m, with a version with Build ID \x1b[1mYourBuildID\x1b[0m, with\n10 percent of tasks redirected to this version, and\nusing the default namespace:\n\n\x1b[1mtemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID\n --percentage 10.0\x1b[0m\n\nAnd to remove that ramping:\n\n\x1b[1mtemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID \\\n --delete\x1b[0m" } else { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n\nSet the Ramping Version and Percentage for a Deployment.\n\nThe Ramping Version can be set to a fully-qualified Version of the form\n`YourDeploymentName.YourBuildID`, or set to \"__unversioned__\", a special\nvalue that represents all the unversioned workers.\n\nThe Ramping Percentage is a float with values in the range [0, 100].\nA value of 100 does not make the Ramping Version Current, use\n`set-current-version` instead.\n\nTo remove a Ramping Version use the flag `--delete`.\n\nIf not all the expected Task Queues are being polled by Workers in the\nnew Ramping 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-ramping-version [options]\n```\n\nFor example, to set the Ramping Version of a deployment\n`YourDeploymentName`, with a version with Build ID `YourBuildID`, with\n10 percent of tasks redirected to this version, and\nusing the default namespace:\n\n```\ntemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID\n --percentage 10.0\n```\n\nAnd to remove that ramping:\n \n```\ntemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID \\\n --delete\n``` " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\n\nSet the Ramping Version and Percentage for a Deployment.\n\nThe Ramping Version can be set to a fully-qualified Version of the form\n`YourDeploymentName.YourBuildID`, or set to \"__unversioned__\", a special\nvalue that represents all the unversioned workers.\n\nThe Ramping Percentage is a float with values in the range [0, 100].\nA value of 100 does not make the Ramping Version Current, use\n`set-current-version` instead.\n\nTo remove a Ramping Version use the flag `--delete`.\n\nIf not all the expected Task Queues are being polled by Workers in the\nnew Ramping 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-ramping-version [options]\n```\n\nFor example, to set the Ramping Version of a deployment\n`YourDeploymentName`, with a version with Build ID `YourBuildID`, with\n10 percent of tasks redirected to this version, and\nusing the default namespace:\n\n```\ntemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID\n --percentage 10.0\n```\n\nAnd to remove that ramping:\n\n```\ntemporal worker deployment set-ramping-version \\\n --version YourDeploymentName.YourBuildID \\\n --delete\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.DeploymentName, "deployment-name", "", "Deployment name. Only needed when `--version` is `__unversioned__`.") @@ -2821,9 +2821,9 @@ func NewTemporalWorkerDeploymentUpdateMetadataVersionCommand(cctx *CommandContex s.Command.Use = "update-metadata-version [flags]" s.Command.Short = "Change user-provided metadata for a Version" if hasHighlighting { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\nUpdate metadata associated with a Worker Deployment Version.\n\nFor example:\n\n\x1b[1m temporal worker deployment update-metadata-version \\\n --version YourDeploymentName.YourBuildID \\\n --metadata bar=1 \\\n --metadata foo=true\x1b[0m\n\nThe current metadata is also returned with \x1b[1mdescribe-version\x1b[0m:\n \n\x1b[1m temporal worker deployment describe-version \\\n --version YourDeploymentName.YourBuildID \\\x1b[0m " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\nUpdate metadata associated with a Worker Deployment Version.\n\nFor example:\n\n\x1b[1m temporal worker deployment update-metadata-version \\\n --version YourDeploymentName.YourBuildID \\\n --metadata bar=1 \\\n --metadata foo=true\x1b[0m\n\nThe current metadata is also returned with \x1b[1mdescribe-version\x1b[0m:\n\n\x1b[1m temporal worker deployment describe-version \\\n --version YourDeploymentName.YourBuildID \\\x1b[0m" } else { - s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\nUpdate metadata associated with a Worker Deployment Version.\n\nFor example:\n\n```\n temporal worker deployment update-metadata-version \\\n --version YourDeploymentName.YourBuildID \\\n --metadata bar=1 \\\n --metadata foo=true\n```\n\nThe current metadata is also returned with `describe-version`:\n \n```\n temporal worker deployment describe-version \\\n --version YourDeploymentName.YourBuildID \\\n``` " + s.Command.Long = "+---------------------------------------------------------------------+\n| CAUTION: Worker Deployment is experimental. Deployment commands are |\n| subject to change. |\n+---------------------------------------------------------------------+\nUpdate metadata associated with a Worker Deployment Version.\n\nFor example:\n\n```\n temporal worker deployment update-metadata-version \\\n --version YourDeploymentName.YourBuildID \\\n --metadata bar=1 \\\n --metadata foo=true\n```\n\nThe current metadata is also returned with `describe-version`:\n\n```\n temporal worker deployment describe-version \\\n --version YourDeploymentName.YourBuildID \\\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringArrayVar(&s.Metadata, "metadata", nil, "Set deployment metadata using `KEY=\"VALUE\"` pairs. Keys must be identifiers, and values must be JSON values. For example: 'YourKey={\"your\": \"value\"}'. Can be passed multiple times.") diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 2e94dad79..9eb0920dd 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -238,6 +238,9 @@ commands: - cli-feature - command-line-interface-cli - temporal cli + tags: + - Activities + - Temporal CLI - name: temporal activity complete summary: Complete an Activity @@ -447,7 +450,7 @@ commands: --reason YourReasonForTermination ``` - + Specify the Activity ID or Type and Workflow IDs: ``` @@ -593,6 +596,8 @@ commands: - cli-feature - command-line-interface-cli - temporal cli + tags: + - Temporal CLI - name: temporal batch describe summary: Show batch job progress @@ -670,6 +675,8 @@ commands: keywords: - worker - worker deployment + tags: + - Temporal CLI - name: temporal worker deployment summary: Describe, list, and operate on Worker Deployments and Versions @@ -706,7 +713,7 @@ commands: ``` Sets the current Deployment Version for a given Deployment. - + docs: description-header: >- Temporal Deployment commands enable operations on Worker Deployments @@ -780,7 +787,7 @@ commands: - 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 description: | @@ -801,7 +808,7 @@ commands: temporal worker deployment list \ --namespace YourDeploymentNamespace ``` - + - name: temporal worker deployment describe-version summary: Show properties of a Worker Deployment Version description: | @@ -829,7 +836,7 @@ commands: ``` option-sets: - deployment-version - + - name: temporal worker deployment delete-version summary: Delete a Worker Deployment Version description: | @@ -848,7 +855,7 @@ commands: Version have pollers. - It is not draining. This requirement can be ignored with the option `--skip-drainage`. - + ``` temporal worker deployment delete-version [options] ``` @@ -910,7 +917,7 @@ commands: temporal worker deployment set-current-version \ --version __unversioned__ \ --deployment-name YourDeploymentName - ``` + ``` option-sets: - deployment-version options: @@ -972,12 +979,12 @@ commands: ``` And to remove that ramping: - + ``` temporal worker deployment set-ramping-version \ --version YourDeploymentName.YourBuildID \ --delete - ``` + ``` option-sets: - deployment-version options: @@ -1004,7 +1011,7 @@ commands: short: y type: bool description: Don't prompt to confirm set Ramping Version. - + - name: temporal worker deployment update-metadata-version summary: Change user-provided metadata for a Version description: | @@ -1024,11 +1031,11 @@ commands: ``` The current metadata is also returned with `describe-version`: - + ``` temporal worker deployment describe-version \ --version YourDeploymentName.YourBuildID \ - ``` + ``` option-sets: - deployment-version options: @@ -1044,7 +1051,7 @@ commands: description: | Keys of entries to be deleted from metadata. Can be passed multiple times. - + - name: temporal env summary: Manage environments description: | @@ -1081,6 +1088,8 @@ commands: - env set - environment - temporal cli + tags: + - Temporal CLI - name: temporal env delete summary: Delete an environment or environment property @@ -1228,6 +1237,8 @@ commands: - system - temporal cli - update + tags: + - Temporal CLI - name: temporal operator cluster summary: Manage a Temporal Cluster @@ -1810,6 +1821,9 @@ commands: - schedule update - temporal cli - updates + tags: + - Temporal CLI + - Schedules - name: temporal schedule backfill summary: Backfill past actions @@ -2069,6 +2083,9 @@ commands: - server - server start-dev - temporal cli + tags: + - Temporal CLI + - Development Server - name: temporal server start-dev summary: Start Temporal development server @@ -2218,6 +2235,8 @@ commands: - task queue - task queue describe - temporal cli + tags: + - Temporal CLI - name: temporal task-queue describe summary: Show active Workers @@ -2961,6 +2980,9 @@ commands: - workflow terminate - workflow trace - workflow update-options + tags: + - Temporal CLI + - Workflows - name: temporal workflow cancel summary: Send cancellation to Workflow Execution @@ -4042,7 +4064,7 @@ option-sets: Fully-qualified name for a Worker Deployment Version. Use the format `YourDeploymentName.YourBuildID`. required: true - + - name: deployment-reference options: - name: series-name @@ -4187,7 +4209,7 @@ option-sets: - Fail - UseExisting - TerminateExisting - + - name: payload-input options: - name: input diff --git a/temporalcli/commandsgen/docs.go b/temporalcli/commandsgen/docs.go index fd00f2475..d26aa2456 100644 --- a/temporalcli/commandsgen/docs.go +++ b/temporalcli/commandsgen/docs.go @@ -61,8 +61,8 @@ func (w *docWriter) writeCommand(c *Command) { w.fileMap[fileName] = &bytes.Buffer{} w.fileMap[fileName].WriteString("---\n") w.fileMap[fileName].WriteString("id: " + fileName + "\n") - w.fileMap[fileName].WriteString("title: " + c.FullName + "\n") - w.fileMap[fileName].WriteString("sidebar_label: " + c.FullName + "\n") + w.fileMap[fileName].WriteString("title: Temporal CLI " + fileName + " command reference\n") + w.fileMap[fileName].WriteString("sidebar_label: " + fileName + "\n") w.fileMap[fileName].WriteString("description: " + c.Docs.DescriptionHeader + "\n") w.fileMap[fileName].WriteString("toc_max_heading_level: 4\n") @@ -70,10 +70,9 @@ func (w *docWriter) writeCommand(c *Command) { for _, keyword := range c.Docs.Keywords { w.fileMap[fileName].WriteString(" - " + keyword + "\n") } - // tags are the same as Keywords, but with `-` instead of ` ` w.fileMap[fileName].WriteString("tags:\n") - for _, keyword := range c.Docs.Keywords { - w.fileMap[fileName].WriteString(" - " + strings.ReplaceAll(keyword, " ", "-") + "\n") + for _, tag := range c.Docs.Tags { + w.fileMap[fileName].WriteString(" - " + tag + "\n") } w.fileMap[fileName].WriteString("---\n\n") } diff --git a/temporalcli/commandsgen/parse.go b/temporalcli/commandsgen/parse.go index 25cebfa86..670996dec 100644 --- a/temporalcli/commandsgen/parse.go +++ b/temporalcli/commandsgen/parse.go @@ -54,6 +54,7 @@ type ( Docs struct { Keywords []string `yaml:"keywords"` DescriptionHeader string `yaml:"description-header"` + Tags []string `yaml:"tags"` } // OptionSets represents the structure of option sets. @@ -149,6 +150,9 @@ func (c *Command) processSection() error { if c.Docs.DescriptionHeader == "" { return fmt.Errorf("missing description for root command: %s", c.FullName) } + if len(c.Docs.Tags) == 0 { + return fmt.Errorf("missing tags for root command: %s", c.FullName) + } } // Strip trailing newline for description From 4a0118a11288373f8530fa2b7dfe35b6b062ca76 Mon Sep 17 00:00:00 2001 From: Chad Retz Date: Mon, 21 Apr 2025 18:25:20 +0100 Subject: [PATCH 12/15] =?UTF-8?q?=F0=9F=92=A5=20Support=20environment=20co?= =?UTF-8?q?nfig=20[BREAKING=20CHANGES]=20(#764)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #751 --- .github/workflows/ci.yaml | 19 +- go.mod | 2 + go.sum | 4 + temporalcli/client.go | 273 ++++++++------ temporalcli/commands.config.go | 335 +++++++++++++++++ temporalcli/commands.config_test.go | 395 +++++++++++++++++++++ temporalcli/commands.env.go | 72 +++- temporalcli/commands.env_test.go | 20 +- temporalcli/commands.gen.go | 201 +++++++++-- temporalcli/commands.go | 124 +++---- temporalcli/commands.server.go | 6 +- temporalcli/commands.workflow_exec_test.go | 14 +- temporalcli/commands_test.go | 22 +- temporalcli/commandsgen/code.go | 10 + temporalcli/commandsgen/commands.yml | 211 +++++++++-- temporalcli/commandsgen/parse.go | 3 + temporalcli/internal/printer/printer.go | 4 + 17 files changed, 1442 insertions(+), 273 deletions(-) create mode 100644 temporalcli/commands.config.go create mode 100644 temporalcli/commands.config_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ee1a773a1..bf2750297 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,7 +56,7 @@ jobs: go run ./temporalcli/internal/cmd/gen-commands git diff --exit-code - - name: Test cloud + - name: Test cloud mTLS if: ${{ matrix.cloudTestTarget && env.HAS_SECRETS == 'true' }} env: TEMPORAL_ADDRESS: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }}.tmprl.cloud:7233 @@ -70,3 +70,20 @@ jobs: printf '%s\n' "$TEMPORAL_TLS_CERT_CONTENT" >> client.crt printf '%s\n' "$TEMPORAL_TLS_KEY_CONTENT" >> client.key go run ./cmd/temporal workflow list --limit 2 + + - name: Test cloud API key env var + if: ${{ matrix.cloudTestTarget && env.HAS_SECRETS == 'true' }} + env: + TEMPORAL_ADDRESS: us-west-2.aws.api.temporal.io:7233 + TEMPORAL_NAMESPACE: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }} + TEMPORAL_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} + shell: bash + run: go run ./cmd/temporal workflow list --limit 2 + + - name: Test cloud API key arg + if: ${{ matrix.cloudTestTarget && env.HAS_SECRETS == 'true' }} + env: + TEMPORAL_ADDRESS: us-west-2.aws.api.temporal.io:7233 + TEMPORAL_NAMESPACE: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }} + shell: bash + run: go run ./cmd/temporal workflow list --limit 2 --api-key ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} diff --git a/go.mod b/go.mod index 29df67da3..d08537cb9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/temporalio/cli go 1.23.2 require ( + github.com/BurntSushi/toml v1.4.0 github.com/alitto/pond v1.9.2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/dustin/go-humanize v1.0.1 @@ -17,6 +18,7 @@ require ( github.com/temporalio/ui-server/v2 v2.36.0 go.temporal.io/api v1.46.0 go.temporal.io/sdk v1.33.0 + go.temporal.io/sdk/contrib/envconfig v0.1.0 go.temporal.io/server v1.27.1 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index c8a7eea02..b02078eac 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= @@ -372,6 +374,8 @@ go.temporal.io/api v1.46.0 h1:O1efPDB6O2B8uIeCDIa+3VZC7tZMvYsMZYQapSbHvCg= go.temporal.io/api v1.46.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.33.0 h1:T91UzeRdlHTiMGgpygsItOH9+VSkg+M/mG85PqNjdog= go.temporal.io/sdk v1.33.0/go.mod h1:WwCmJZLy7zabz3ar5NRAQEygsdP8tgR9sDjISSHuWZw= +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.27.1 h1:0dyBl25Ua7P4IOXifJg0xUXfnoYTNY1IlUFf1RL4OBo= go.temporal.io/server v1.27.1/go.mod h1:ddxnsbsXvdZ/oRvjLHaL45NJUGMOPW+3RLkhpq9TOAs= go.temporal.io/version v0.3.0 h1:dMrei9l9NyHt8nG6EB8vAwDLLTwx2SvRyucCSumAiig= diff --git a/temporalcli/client.go b/temporalcli/client.go index cb75e9363..4b41bfc4a 100644 --- a/temporalcli/client.go +++ b/temporalcli/client.go @@ -2,8 +2,6 @@ package temporalcli import ( "context" - "crypto/tls" - "crypto/x509" "fmt" "net/http" "os" @@ -12,52 +10,184 @@ import ( "go.temporal.io/api/common/v1" "go.temporal.io/sdk/client" + "go.temporal.io/sdk/contrib/envconfig" "go.temporal.io/sdk/converter" "go.temporal.io/sdk/log" - "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) +// Dial a client. +// +// Note, this call may mutate the receiver [ClientOptions.Namespace] since it is +// so often used by callers after this call to know the currently configured +// namespace. func (c *ClientOptions) dialClient(cctx *CommandContext) (client.Client, error) { - clientOptions := client.Options{ - HostPort: c.Address, - Namespace: c.Namespace, - Logger: log.NewStructuredLogger(cctx.Logger), - 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 - // everywhere DataConverterWithRawValue is used. - DataConverter: DataConverterWithRawValue, - } - - // API key - if c.ApiKey != "" { - clientOptions.Credentials = client.NewAPIKeyStaticCredentials(c.ApiKey) + if cctx.RootCommand == nil { + return nil, fmt.Errorf("root command unexpectedly missing when dialing client") } - // Headers - if len(c.GrpcMeta) > 0 { - headers, err := NewStringMapHeaderProvider(c.GrpcMeta) + // Load a client config profile + var clientProfile envconfig.ClientConfigProfile + if !cctx.RootCommand.DisableConfigFile || !cctx.RootCommand.DisableConfigEnv { + var err error + clientProfile, err = envconfig.LoadClientConfigProfile(envconfig.LoadClientConfigProfileOptions{ + ConfigFilePath: cctx.RootCommand.ConfigFile, + ConfigFileProfile: cctx.RootCommand.Profile, + DisableFile: cctx.RootCommand.DisableConfigFile, + DisableEnv: cctx.RootCommand.DisableConfigEnv, + EnvLookup: cctx.Options.EnvLookup, + }) if err != nil { - return nil, fmt.Errorf("grpc-meta %s", err) + return nil, fmt.Errorf("failed loading client config: %w", err) } - clientOptions.HeadersProvider = headers } - // Remote codec - if c.CodecEndpoint != "" { - codecHeaders, err := NewStringMapHeaderProvider(c.CodecHeader) + // To support legacy TLS environment variables, if they are present, we will + // have them force-override anything loaded from existing file or env + if !cctx.RootCommand.DisableConfigEnv { + oldEnvTLSCert, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT") + oldEnvTLSCertData, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_CERT_DATA") + oldEnvTLSKey, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY") + oldEnvTLSKeyData, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_KEY_DATA") + oldEnvTLSCA, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_CA") + oldEnvTLSCAData, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_TLS_CA_DATA") + if oldEnvTLSCert != "" || oldEnvTLSCertData != "" || + oldEnvTLSKey != "" || oldEnvTLSKeyData != "" || + oldEnvTLSCA != "" || oldEnvTLSCAData != "" { + if clientProfile.TLS == nil { + clientProfile.TLS = &envconfig.ClientConfigTLS{} + } + if oldEnvTLSCert != "" { + clientProfile.TLS.ClientCertPath = oldEnvTLSCert + } + if oldEnvTLSCertData != "" { + clientProfile.TLS.ClientCertData = []byte(oldEnvTLSCertData) + } + if oldEnvTLSKey != "" { + clientProfile.TLS.ClientKeyPath = oldEnvTLSKey + } + if oldEnvTLSKeyData != "" { + clientProfile.TLS.ClientKeyData = []byte(oldEnvTLSKeyData) + } + if oldEnvTLSCA != "" { + clientProfile.TLS.ServerCACertPath = oldEnvTLSCA + } + if oldEnvTLSCAData != "" { + clientProfile.TLS.ServerCACertData = []byte(oldEnvTLSCAData) + } + } + } + + // Override some values in client config profile that come from CLI args. Some + // flags, like address and namespace, have CLI defaults, but we don't want to + // override the profile version unless it was _explicitly_ set. + var addressExplicitlySet, namespaceExplicitlySet bool + if cctx.CurrentCommand != nil { + addressExplicitlySet = cctx.CurrentCommand.Flags().Changed("address") + namespaceExplicitlySet = cctx.CurrentCommand.Flags().Changed("namespace") + } + if addressExplicitlySet { + clientProfile.Address = c.Address + } + if namespaceExplicitlySet { + clientProfile.Namespace = c.Namespace + } else if clientProfile.Namespace != "" { + // Since this namespace value is used by many commands after this call, + // we are mutating it to be the derived one + c.Namespace = clientProfile.Namespace + } + if c.ApiKey != "" { + clientProfile.APIKey = c.ApiKey + } + if len(c.GrpcMeta) > 0 { + // Append meta to the client profile + grpcMetaFromArg, err := stringKeysValues(c.GrpcMeta) if err != nil { - return nil, fmt.Errorf("codec-header %s", err) + return nil, fmt.Errorf("invalid gRPC meta: %w", err) } + if len(clientProfile.GRPCMeta) == 0 { + clientProfile.GRPCMeta = make(map[string]string, len(c.GrpcMeta)) + } + for k, v := range grpcMetaFromArg { + clientProfile.GRPCMeta[k] = v + } + } - if c.CodecAuth != "" { - codecHeaders["Authorization"] = c.CodecAuth + // If any of these values are present, set TLS if not set, and set values. + // NOTE: This means that tls=false does not explicitly disable TLS when set + // via envconfig. + if c.Tls || + c.TlsCertPath != "" || c.TlsKeyPath != "" || c.TlsCaPath != "" || + c.TlsCertData != "" || c.TlsKeyData != "" || c.TlsCaData != "" { + if clientProfile.TLS == nil { + clientProfile.TLS = &envconfig.ClientConfigTLS{} + } + if c.TlsCertPath != "" { + clientProfile.TLS.ClientCertPath = c.TlsCertPath } + if c.TlsCertData != "" { + clientProfile.TLS.ClientCertData = []byte(c.TlsCertData) + } + if c.TlsKeyPath != "" { + clientProfile.TLS.ClientKeyPath = c.TlsKeyPath + } + if c.TlsKeyData != "" { + clientProfile.TLS.ClientKeyData = []byte(c.TlsKeyData) + } + if c.TlsCaPath != "" { + clientProfile.TLS.ServerCACertPath = c.TlsCaPath + } + if c.TlsCaData != "" { + clientProfile.TLS.ServerCACertData = []byte(c.TlsCaData) + } + if c.TlsServerName != "" { + clientProfile.TLS.ServerName = c.TlsServerName + } + if c.TlsDisableHostVerification { + clientProfile.TLS.DisableHostVerification = c.TlsDisableHostVerification + } + } + + // If TLS is explicitly disabled, we turn it off. Otherwise it may be + // implicitly enabled if API key or any other TLS setting is set. + if cctx.CurrentCommand.Flags().Changed("tls") && !c.Tls { + clientProfile.TLS = &envconfig.ClientConfigTLS{Disabled: true} + } + + // If codec endpoint is set, create codec setting regardless. But if auth is + // set, it only overrides if codec is present. + if c.CodecEndpoint != "" { + if clientProfile.Codec == nil { + clientProfile.Codec = &envconfig.ClientConfigCodec{} + } + clientProfile.Codec.Endpoint = c.CodecEndpoint + } + if c.CodecAuth != "" && clientProfile.Codec != nil { + clientProfile.Codec.Auth = c.CodecAuth + } - interceptor, err := payloadCodecInterceptor(c.Namespace, c.CodecEndpoint, codecHeaders) + // Now load client options from the profile + clientOptions, err := clientProfile.ToClientOptions(envconfig.ToClientOptionsRequest{}) + if err != nil { + return nil, fmt.Errorf("failed creating client options: %w", err) + } + 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 + // everywhere DataConverterWithRawValue is used. + clientOptions.DataConverter = DataConverterWithRawValue + + // Remote codec + if clientProfile.Codec != nil && clientProfile.Codec.Endpoint != "" { + codecHeaders, err := stringKeysValues(c.CodecHeader) + if err != nil { + return nil, fmt.Errorf("invalid codec headers: %w", err) + } + interceptor, err := payloadCodecInterceptor( + clientProfile.Namespace, clientProfile.Codec.Endpoint, clientProfile.Codec.Auth, codecHeaders) if err != nil { return nil, fmt.Errorf("failed creating payload codec interceptor: %w", err) } @@ -73,64 +203,9 @@ func (c *ClientOptions) dialClient(cctx *CommandContext) (client.Client, error) clientOptions.ConnectionOptions.DialOptions = append( clientOptions.ConnectionOptions.DialOptions, cctx.Options.AdditionalClientGRPCDialOptions...) - // TLS - var err error - if clientOptions.ConnectionOptions.TLS, err = c.tlsConfig(); err != nil { - return nil, err - } - return client.Dial(clientOptions) } -func (c *ClientOptions) tlsConfig() (*tls.Config, error) { - // We need TLS if any of these TLS options are set - if !c.Tls && - c.TlsCaPath == "" && c.TlsCertPath == "" && c.TlsKeyPath == "" && - c.TlsCaData == "" && c.TlsCertData == "" && c.TlsKeyData == "" { - return nil, nil - } - - conf := &tls.Config{ - ServerName: c.TlsServerName, - InsecureSkipVerify: c.TlsDisableHostVerification, - } - - if c.TlsCertPath != "" { - if c.TlsCertData != "" { - return nil, fmt.Errorf("cannot specify both --tls-cert-path and --tls-cert-data") - } - clientCert, err := tls.LoadX509KeyPair(c.TlsCertPath, c.TlsKeyPath) - if err != nil { - return nil, fmt.Errorf("failed loading client cert key pair: %w", err) - } - conf.Certificates = append(conf.Certificates, clientCert) - } else if c.TlsCertData != "" { - clientCert, err := tls.X509KeyPair([]byte(c.TlsCertData), []byte(c.TlsKeyData)) - if err != nil { - return nil, fmt.Errorf("failed loading client cert key pair: %w", err) - } - conf.Certificates = append(conf.Certificates, clientCert) - } - - if c.TlsCaPath != "" { - if c.TlsCaData != "" { - return nil, fmt.Errorf("cannot specify both --tls-ca-path and --tls-ca-data") - } - conf.RootCAs = x509.NewCertPool() - if b, err := os.ReadFile(c.TlsCaPath); err != nil { - return nil, fmt.Errorf("failed reading CA cert from %v: %w", c.TlsCaPath, err) - } else if !conf.RootCAs.AppendCertsFromPEM(b) { - return nil, fmt.Errorf("invalid CA cert from %v", c.TlsCaPath) - } - } else if c.TlsCaData != "" { - conf.RootCAs = x509.NewCertPool() - if !conf.RootCAs.AppendCertsFromPEM([]byte(c.TlsCaData)) { - return nil, fmt.Errorf("invalid CA cert data") - } - } - return conf, nil -} - func fixedHeaderOverrideInterceptor( ctx context.Context, method string, req, reply any, @@ -150,7 +225,12 @@ func fixedHeaderOverrideInterceptor( return invoker(ctx, method, req, reply, cc, opts...) } -func payloadCodecInterceptor(namespace, codecEndpoint string, codecHeaders stringMapHeadersProvider) (grpc.UnaryClientInterceptor, error) { +func payloadCodecInterceptor( + namespace string, + codecEndpoint string, + codecAuth string, + codecHeaders map[string]string, +) (grpc.UnaryClientInterceptor, error) { codecEndpoint = strings.ReplaceAll(codecEndpoint, "{namespace}", namespace) payloadCodec := converter.NewRemotePayloadCodec( @@ -161,6 +241,9 @@ func payloadCodecInterceptor(namespace, codecEndpoint string, codecHeaders strin for headerName, headerValue := range codecHeaders { req.Header.Set(headerName, headerValue) } + if codecAuth != "" { + req.Header.Set("Authorization", codecAuth) + } return nil }, }, @@ -184,24 +267,6 @@ func clientIdentity() string { return "temporal-cli:" + username + "@" + hostname } -type stringMapHeadersProvider map[string]string - -func (s stringMapHeadersProvider) GetHeaders(context.Context) (map[string]string, error) { - return s, nil -} - -func NewStringMapHeaderProvider(config []string) (stringMapHeadersProvider, error) { - headers := make(stringMapHeadersProvider, len(config)) - for _, kv := range config { - pieces := strings.SplitN(kv, "=", 2) - if len(pieces) != 2 { - return nil, fmt.Errorf("%q does not have '='", kv) - } - headers[pieces[0]] = pieces[1] - } - return headers, nil -} - var DataConverterWithRawValue = converter.NewCompositeDataConverter( rawValuePayloadConverter{}, converter.NewNilPayloadConverter(), diff --git a/temporalcli/commands.config.go b/temporalcli/commands.config.go new file mode 100644 index 000000000..a4028ff46 --- /dev/null +++ b/temporalcli/commands.config.go @@ -0,0 +1,335 @@ +package temporalcli + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + + "github.com/BurntSushi/toml" + "github.com/temporalio/cli/temporalcli/internal/printer" + "go.temporal.io/sdk/contrib/envconfig" +) + +func (c *TemporalConfigDeleteCommand) run(cctx *CommandContext, _ []string) error { + // Load config + profileName := envConfigProfileName(cctx) + conf, confProfile, err := loadEnvConfigProfile(cctx, profileName, true) + if err != nil { + return err + } + if strings.HasPrefix(c.Prop, "grpc_meta.") { + key := strings.TrimPrefix(c.Prop, "grpc_meta.") + if _, ok := confProfile.GRPCMeta[key]; !ok { + return fmt.Errorf("gRPC meta key %q not found", key) + } + delete(confProfile.GRPCMeta, key) + } else { + reflectVal, err := reflectEnvConfigProp(confProfile, c.Prop, true) + if err != nil { + return err + } + reflectVal.SetZero() + } + + // Save + return writeEnvConfigFile(cctx, conf) +} + +func (c *TemporalConfigDeleteProfileCommand) run(cctx *CommandContext, _ []string) error { + // Load config + profileName := envConfigProfileName(cctx) + conf, _, err := loadEnvConfigProfile(cctx, profileName, true) + if err != nil { + return err + } + // To make extra sure they meant to do this, we require the profile name + // as an explicit CLI arg. This prevents accidentally deleting the + // "default" profile. + if cctx.RootCommand.Profile == "" { + return fmt.Errorf("to delete an entire profile, --profile must be provided explicitly") + } + delete(conf.Profiles, profileName) + + // Save + return writeEnvConfigFile(cctx, conf) +} + +func (c *TemporalConfigGetCommand) run(cctx *CommandContext, _ []string) error { + // Load config profile + profileName := envConfigProfileName(cctx) + conf, confProfile, err := loadEnvConfigProfile(cctx, profileName, true) + if err != nil { + return err + } + type prop struct { + Property string `json:"property"` + Value any `json:"value"` + } + // If there is a specific key requested, show it, otherwise show all + if c.Prop != "" { + // We do not support asking for structures with children at this time, + // but "tls" is a special case because it's also a bool. + if c.Prop == "codec" || c.Prop == "grpc_meta" { + return fmt.Errorf("must provide exact property, not parent property") + } + var reflectVal reflect.Value + // gRPC meta is special + if strings.HasPrefix(c.Prop, "grpc_meta.") { + v, ok := confProfile.GRPCMeta[strings.TrimPrefix(c.Prop, "grpc_meta.")] + if !ok { + return fmt.Errorf("unknown property %q", c.Prop) + } + reflectVal = reflect.ValueOf(v) + } else { + // Single value goes into property-value structure + reflectVal, err = reflectEnvConfigProp(confProfile, c.Prop, false) + if err != nil { + return err + } + // Pointers become true/false + if reflectVal.Kind() == reflect.Pointer { + reflectVal = reflect.ValueOf(!reflectVal.IsNil()) + } + } + return cctx.Printer.PrintStructured( + prop{Property: c.Prop, Value: reflectVal.Interface()}, + printer.StructuredOptions{Table: &printer.TableOptions{}}, + ) + } else if cctx.JSONOutput { + // If it is JSON and not prop specific, we want to dump the TOML + // structure in JSON form + var tomlConf struct { + Profiles map[string]any `toml:"profile"` + } + if b, err := conf.ToTOML(envconfig.ClientConfigToTOMLOptions{}); err != nil { + return fmt.Errorf("failed converting to TOML: %w", err) + } else if err := toml.Unmarshal(b, &tomlConf); err != nil { + return fmt.Errorf("failed converting from TOML: %w", err) + } + return cctx.Printer.PrintStructured(tomlConf.Profiles[profileName], printer.StructuredOptions{}) + } else { + // Get every property individually as a property-value pair except zero + // vals + var props []prop + for k := range envConfigPropsToFieldNames { + // TLS is a special case + if k == "tls" { + if confProfile.TLS != nil { + props = append(props, prop{Property: "tls", Value: true}) + } + continue + } + if val, err := reflectEnvConfigProp(confProfile, k, false); err != nil { + return err + } else if !val.IsZero() { + props = append(props, prop{Property: k, Value: val.Interface()}) + } + } + + // Add "grpc_meta" + for k, v := range confProfile.GRPCMeta { + props = append(props, prop{Property: "grpc_meta." + k, Value: v}) + } + + // Sort and display + sort.Slice(props, func(i, j int) bool { return props[i].Property < props[j].Property }) + return cctx.Printer.PrintStructured(props, printer.StructuredOptions{Table: &printer.TableOptions{}}) + } +} + +func (c *TemporalConfigListCommand) run(cctx *CommandContext, _ []string) error { + clientConfig, err := envconfig.LoadClientConfig(envconfig.LoadClientConfigOptions{ + ConfigFilePath: cctx.RootCommand.ConfigFile, + EnvLookup: cctx.Options.EnvLookup, + }) + if err != nil { + return err + } + type profile struct { + Name string `json:"name"` + } + profiles := make([]profile, 0, len(clientConfig.Profiles)) + for k := range clientConfig.Profiles { + profiles = append(profiles, profile{Name: k}) + } + sort.Slice(profiles, func(i, j int) bool { return profiles[i].Name < profiles[j].Name }) + return cctx.Printer.PrintStructured(profiles, printer.StructuredOptions{Table: &printer.TableOptions{}}) +} + +func (c *TemporalConfigSetCommand) run(cctx *CommandContext, _ []string) error { + // Load config + conf, confProfile, err := loadEnvConfigProfile(cctx, envConfigProfileName(cctx), false) + if err != nil { + return err + } + // As a special case, "grpc_meta." values are handled specifically + if strings.HasPrefix(c.Prop, "grpc_meta.") { + if confProfile.GRPCMeta == nil { + confProfile.GRPCMeta = map[string]string{} + } + confProfile.GRPCMeta[strings.TrimPrefix(c.Prop, "grpc_meta.")] = c.Value + } else { + // Get reflect value + reflectVal, err := reflectEnvConfigProp(confProfile, c.Prop, false) + if err != nil { + return err + } + // Set it from string + switch reflectVal.Kind() { + case reflect.String: + reflectVal.SetString(c.Value) + case reflect.Pointer: + // Used for "tls", true makes an empty object, false sets nil + switch c.Value { + case "true": + // Only set if not set + if reflectVal.IsZero() { + reflectVal.Set(reflect.New(reflectVal.Type().Elem())) + } + case "false": + reflectVal.SetZero() + default: + return fmt.Errorf("must be 'true' or 'false' to set this property") + } + case reflect.Slice: + if reflectVal.Type().Elem().Kind() != reflect.Uint8 { + return fmt.Errorf("unexpected slice of type %v", reflectVal.Type()) + } + reflectVal.SetBytes([]byte(c.Value)) + case reflect.Bool: + if c.Value != "true" && c.Value != "false" { + return fmt.Errorf("must be 'true' or 'false' to set this property") + } + reflectVal.SetBool(c.Value == "true") + case reflect.Map: + return fmt.Errorf("must set each individual value of a map") + default: + return fmt.Errorf("unexpected type %v", reflectVal.Type()) + } + } + + // Save + return writeEnvConfigFile(cctx, conf) +} + +func envConfigProfileName(cctx *CommandContext) string { + if cctx.RootCommand.Profile != "" { + return cctx.RootCommand.Profile + } else if p, _ := cctx.Options.EnvLookup.LookupEnv("TEMPORAL_PROFILE"); p != "" { + return p + } + return envconfig.DefaultConfigFileProfile +} + +func loadEnvConfigProfile( + cctx *CommandContext, + profile string, + failIfNotFound bool, +) (*envconfig.ClientConfig, *envconfig.ClientConfigProfile, error) { + clientConfig, err := envconfig.LoadClientConfig(envconfig.LoadClientConfigOptions{ + ConfigFilePath: cctx.RootCommand.ConfigFile, + EnvLookup: cctx.Options.EnvLookup, + }) + if err != nil { + return nil, nil, err + } + + // Load profile + clientProfile := clientConfig.Profiles[profile] + if clientProfile == nil { + if failIfNotFound { + return nil, nil, fmt.Errorf("profile %q not found", profile) + } + clientProfile = &envconfig.ClientConfigProfile{} + clientConfig.Profiles[profile] = clientProfile + } + return &clientConfig, clientProfile, nil +} + +var envConfigPropsToFieldNames = map[string]string{ + "address": "Address", + "namespace": "Namespace", + "api_key": "APIKey", + "tls": "TLS", + "tls.disabled": "Disabled", + "tls.client_cert_path": "ClientCertPath", + "tls.client_cert_data": "ClientCertData", + "tls.client_key_path": "ClientKeyPath", + "tls.client_key_data": "ClientKeyData", + "tls.server_ca_cert_path": "ServerCACertPath", + "tls.server_ca_cert_data": "ServerCACertData", + "tls.server_name": "ServerName", + "tls.disable_host_verification": "DisableHostVerification", + "codec.endpoint": "Endpoint", + "codec.auth": "Auth", +} + +func reflectEnvConfigProp( + prof *envconfig.ClientConfigProfile, + prop string, + failIfParentNotFound bool, +) (reflect.Value, error) { + // Get field name + field := envConfigPropsToFieldNames[prop] + if field == "" { + return reflect.Value{}, fmt.Errorf("unknown property %q", prop) + } + + // Load reflect val + parentVal := reflect.ValueOf(prof) + if strings.HasPrefix(prop, "tls.") { + if prof.TLS == nil { + if failIfParentNotFound { + return reflect.Value{}, fmt.Errorf("no TLS options found") + } + prof.TLS = &envconfig.ClientConfigTLS{} + } + parentVal = reflect.ValueOf(prof.TLS) + } else if strings.HasPrefix(prop, "codec.") { + if prof.Codec == nil { + if failIfParentNotFound { + return reflect.Value{}, fmt.Errorf("no codec options found") + } + prof.Codec = &envconfig.ClientConfigCodec{} + } + parentVal = reflect.ValueOf(prof.Codec) + } + + // Return reflected field + if parentVal.Kind() == reflect.Pointer { + parentVal = parentVal.Elem() + } + return parentVal.FieldByName(field), nil +} + +func writeEnvConfigFile(cctx *CommandContext, conf *envconfig.ClientConfig) error { + // Get file + configFile := cctx.RootCommand.ConfigFile + if configFile == "" { + configFile, _ = cctx.Options.EnvLookup.LookupEnv("TEMPORAL_CONFIG_FILE") + if configFile == "" { + var err error + if configFile, err = envconfig.DefaultConfigFilePath(); err != nil { + return err + } + } + } + + // Convert to TOML + b, err := conf.ToTOML(envconfig.ClientConfigToTOMLOptions{}) + if err != nil { + return fmt.Errorf("failed building TOML: %w", err) + } + + // Write to file, making dirs as needed + cctx.Logger.Info("Writing config file", "file", configFile) + if err := os.MkdirAll(filepath.Dir(configFile), 0700); err != nil { + return fmt.Errorf("failed making config file parent dirs: %w", err) + } else if err := os.WriteFile(configFile, b, 0600); err != nil { + return fmt.Errorf("failed writing config file: %w", err) + } + return nil +} diff --git a/temporalcli/commands.config_test.go b/temporalcli/commands.config_test.go new file mode 100644 index 000000000..5cc89ec86 --- /dev/null +++ b/temporalcli/commands.config_test.go @@ -0,0 +1,395 @@ +package temporalcli_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/BurntSushi/toml" +) + +func TestConfig_Get(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + env := EnvLookupMap{} + h.Options.EnvLookup = env + + // Put some data in temp file + f, err := os.CreateTemp("", "") + h.NoError(err) + defer os.Remove(f.Name()) + _, err = f.Write([]byte(` +[profile.foo] +address = "my-address" +namespace = "my-namespace" +api_key = "my-api-key" +codec = { endpoint = "my-endpoint", auth = "my-auth" } +grpc_meta = { some-heAder1 = "some-value1", some-header2 = "some-value2", some_heaDer3 = "some-value3" } +some_future_key = "some future value not handled" + +[profile.foo.tls] +disabled = true +client_cert_path = "my-client-cert-path" +client_cert_data = "my-client-cert-data" +client_key_path = "my-client-key-path" +client_key_data = "my-client-key-data" +server_ca_cert_path = "my-server-ca-cert-path" +server_ca_cert_data = "my-server-ca-cert-data" +# Intentionally absent +# server_name = "my-server-name" +disable_host_verification = true`)) + f.Close() + h.NoError(err) + env["TEMPORAL_CONFIG_FILE"] = f.Name() + + // Bad profile default + res := h.Execute("config", "get") + h.ErrorContains(res.Err, `profile "default" not found`) + + // Bad profile env var + env["TEMPORAL_PROFILE"] = "env-prof" + res = h.Execute("config", "get") + h.ErrorContains(res.Err, `profile "env-prof" not found`) + delete(env, "TEMPORAL_PROFILE") + + // Bad profile CLI arg + res = h.Execute("config", "get", "--profile", "arg-prof") + h.ErrorContains(res.Err, `profile "arg-prof" not found`) + + // Unknown prop + env["TEMPORAL_PROFILE"] = "foo" + res = h.Execute("config", "get", "--prop", "blah") + h.ErrorContains(res.Err, `unknown property "blah"`) + + // Unknown meta + res = h.Execute("config", "get", "--prop", "grpc_meta.wrong") + h.ErrorContains(res.Err, `unknown property "grpc_meta.wrong"`) + res = h.Execute("config", "get", "--prop", "grpc_meta.some-heAder1") + h.ErrorContains(res.Err, `unknown property "grpc_meta.some-heAder1"`) + + // All props + expectedJSON := map[string]any{ + "address": "my-address", + "namespace": "my-namespace", + "api_key": "my-api-key", + "codec.endpoint": "my-endpoint", + "codec.auth": "my-auth", + "grpc_meta.some-header1": "some-value1", + "tls": true, + "tls.disabled": true, + "tls.client_cert_path": "my-client-cert-path", + "tls.client_cert_data": []byte("my-client-cert-data"), + "tls.client_key_path": "my-client-key-path", + "tls.client_key_data": []byte("my-client-key-data"), + "tls.server_ca_cert_path": "my-server-ca-cert-path", + "tls.server_ca_cert_data": []byte("my-server-ca-cert-data"), + "tls.server_name": "", + "tls.disable_host_verification": true, + } + expectedNonJSON := make(map[string]any, len(expectedJSON)) + for prop, expectedVal := range expectedJSON { + if b, ok := expectedVal.([]byte); ok { + expectedVal = "bytes(" + base64.StdEncoding.EncodeToString(b) + ")" + } else { + expectedNonJSON[prop] = expectedVal + } + } + + // JSON individual + for prop, expectedVal := range expectedJSON { + res = h.Execute("config", "get", "--prop", prop, "-o", "json") + b, _ := json.Marshal(expectedVal) + h.JSONEq(fmt.Sprintf(`{"property": %q, "value": %s}`, prop, b), res.Stdout.String()) + } + + // Non-JSON individual + for prop, expectedVal := range expectedNonJSON { + res := h.Execute("config", "get", "--prop", prop) + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), prop, fmt.Sprintf("%v", expectedVal)) + } + + // JSON all together + res = h.Execute("config", "get", "-o", "json") + h.NoError(res.Err) + h.JSONEq(`{ + "address": "my-address", + "api_key": "my-api-key", + "codec": { + "auth": "my-auth", + "endpoint": "my-endpoint" + }, + "grpc_meta": { + "some-header1": "some-value1", + "some-header2": "some-value2", + "some-header3": "some-value3" + }, + "namespace": "my-namespace", + "tls": { + "client_cert_data": "my-client-cert-data", + "client_cert_path": "my-client-cert-path", + "client_key_data": "my-client-key-data", + "client_key_path": "my-client-key-path", + "disable_host_verification": true, + "disabled": true, + "server_ca_cert_data": "my-server-ca-cert-data", + "server_ca_cert_path": "my-server-ca-cert-path" + } + }`, res.Stdout.String()) + + // Non-JSON all together + res = h.Execute("config", "get") + h.NoError(res.Err) + for prop, expectedVal := range expectedNonJSON { + // Server name is excluded because it's a zero val + if prop == "tls.server_name" { + continue + } + h.ContainsOnSameLine(res.Stdout.String(), prop, fmt.Sprintf("%v", expectedVal)) + } +} + +func TestConfig_TLS_Boolean(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + // Put some data in temp file + f, err := os.CreateTemp("", "") + h.NoError(err) + defer os.Remove(f.Name()) + _, err = f.Write([]byte(` +[profile.foo] +address = "my-address" + +[profile.foo.tls]`)) + f.Close() + h.NoError(err) + h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": f.Name(), "TEMPORAL_PROFILE": "foo"} + + // Check that it shows TLS as true + res := h.Execute("config", "get", "--prop", "tls") + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), "tls", "true") + + // Now set it as false and confirm deleted + res = h.Execute("config", "set", "--prop", "tls", "--value", "false") + h.NoError(res.Err) + b, err := os.ReadFile(f.Name()) + h.NoError(err) + h.NotContains(string(b), "tls") + res = h.Execute("config", "get", "--prop", "tls") + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), "tls", "false") + + // Set it as true and confirm it is there again + res = h.Execute("config", "set", "--prop", "tls", "--value", "true") + h.NoError(res.Err) + b, err = os.ReadFile(f.Name()) + h.NoError(err) + h.Contains(string(b), "tls") + res = h.Execute("config", "get", "--prop", "tls") + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), "tls", "true") + + // Delete and confirm gone + res = h.Execute("config", "delete", "--prop", "tls") + h.NoError(res.Err) + b, err = os.ReadFile(f.Name()) + h.NoError(err) + h.NotContains(string(b), "tls") + res = h.Execute("config", "get", "--prop", "tls") + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), "tls", "false") +} + +func TestConfig_Delete(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + // Put some data in temp file + f, err := os.CreateTemp("", "") + h.NoError(err) + defer os.Remove(f.Name()) + _, err = f.Write([]byte(` +[profile.foo] +address = "my-address" +namespace = "my-namespace" + +[profile.foo.tls]`)) + f.Close() + h.NoError(err) + h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": f.Name(), "TEMPORAL_PROFILE": "foo"} + + // Confirm address and namespace is there + res := h.Execute("config", "get") + h.NoError(res.Err) + h.Contains(res.Stdout.String(), "my-address") + h.Contains(res.Stdout.String(), "my-namespace") + + // Delete namespace and confirm gone but address still there + res = h.Execute("config", "delete", "--prop", "namespace") + h.NoError(res.Err) + res = h.Execute("config", "get") + h.NoError(res.Err) + h.Contains(res.Stdout.String(), "my-address") + h.NotContains(res.Stdout.String(), "my-namespace") + + // Delete entire profile + res = h.Execute("config", "delete-profile", "--profile", "foo") + h.NoError(res.Err) + res = h.Execute("config", "get") + h.ErrorContains(res.Err, `profile "foo" not found`) +} + +func TestConfig_List(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + // Put some data in temp file + f, err := os.CreateTemp("", "") + h.NoError(err) + defer os.Remove(f.Name()) + _, err = f.Write([]byte(` +[profile.foo] +address = "my-address-foo" +[profile.bar] +address = "my-address-bar"`)) + f.Close() + h.NoError(err) + h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": f.Name()} + + // Confirm both profiles are there + res := h.Execute("config", "list") + h.NoError(res.Err) + h.Contains(res.Stdout.String(), "foo") + h.Contains(res.Stdout.String(), "bar") + + // Same in JSON + res = h.Execute("config", "list", "-o", "json") + h.NoError(res.Err) + h.Contains(res.Stdout.String(), `"foo"`) + h.Contains(res.Stdout.String(), `"bar"`) + + // Now delete and try again + res = h.Execute("config", "delete-profile", "--profile", "foo") + h.NoError(res.Err) + res = h.Execute("config", "list") + h.NoError(res.Err) + h.NotContains(res.Stdout.String(), "foo") + h.Contains(res.Stdout.String(), "bar") + + // Same in JSON + res = h.Execute("config", "list", "-o", "json") + h.NoError(res.Err) + h.NotContains(res.Stdout.String(), `"foo"`) + h.Contains(res.Stdout.String(), `"bar"`) +} + +func TestConfig_Set(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + // Create a temp file then delete it immediately to confirm set lazily + // creates as needed + f, err := os.CreateTemp("", "") + h.NoError(err) + h.NoError(f.Close()) + h.NoError(os.Remove(f.Name())) + _, err = os.Stat(f.Name()) + h.True(os.IsNotExist(err)) + // Also remove again at the end + defer os.Remove(f.Name()) + h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": f.Name()} + + // Now set an address which will be on default profile and confirm in file + // and "get" + res := h.Execute("config", "set", "--prop", "address", "--value", "some-address") + h.NoError(res.Err) + b, err := os.ReadFile(f.Name()) + h.NoError(err) + h.Contains(string(b), "[profile.default]") + h.Contains(string(b), `"some-address"`) + res = h.Execute("config", "get", "--prop", "address") + h.NoError(res.Err) + h.ContainsOnSameLine(res.Stdout.String(), "address", "some-address") + + // Set a bunch of other things + toSet := map[string]string{ + "address": "my-address", + "namespace": "my-namespace", + "api_key": "my-api-key", + "codec.endpoint": "my-endpoint", + "codec.auth": "my-auth", + "grpc_meta.sOme_header1": "some-value1", + "tls": "true", + "tls.disabled": "true", + "tls.client_cert_path": "my-client-cert-path", + "tls.client_cert_data": "my-client-cert-data", + "tls.client_key_path": "my-client-key-path", + "tls.client_key_data": "my-client-key-data", + "tls.server_ca_cert_path": "my-server-ca-cert-path", + "tls.server_ca_cert_data": "my-server-ca-cert-data", + "tls.disable_host_verification": "true", + } + for k, v := range toSet { + res = h.Execute("config", "set", "--prop", k, "--value", v) + h.NoError(res.Err) + } + + // TOML parse that whole thing and confirm equals + b, err = os.ReadFile(f.Name()) + h.NoError(err) + var all any + h.NoError(toml.Unmarshal(b, &all)) + h.Equal( + map[string]any{ + "profile": map[string]any{ + "default": map[string]any{ + "address": "my-address", + "namespace": "my-namespace", + "api_key": "my-api-key", + "tls": map[string]any{ + "disabled": true, + "client_cert_path": "my-client-cert-path", + "client_cert_data": "my-client-cert-data", + "client_key_path": "my-client-key-path", + "client_key_data": "my-client-key-data", + "server_ca_cert_path": "my-server-ca-cert-path", + "server_ca_cert_data": "my-server-ca-cert-data", + "disable_host_verification": true, + }, + "codec": map[string]any{ + "endpoint": "my-endpoint", + "auth": "my-auth", + }, + "grpc_meta": map[string]any{ + "some-header1": "some-value1", + }, + }, + }, + }, + all) +} + +func (s *SharedServerSuite) TestAPIKey_DefaultsTLS() { + // A workflow list with an API key should fail because TLS is enabled by + // default when --api-key is present + res := s.Execute( + "workflow", "count", + "--address", s.Address(), + "--api-key", "does-not-matter", + ) + s.ErrorContains(res.Err, "tls") + + // But it should succeed with TLS explicitly disabled + res = s.Execute( + "workflow", "count", + "--address", s.Address(), + "--api-key", "does-not-matter", + "--tls=false", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Total") +} diff --git a/temporalcli/commands.env.go b/temporalcli/commands.env.go index 6d5363981..c39921689 100644 --- a/temporalcli/commands.env.go +++ b/temporalcli/commands.env.go @@ -2,10 +2,13 @@ package temporalcli import ( "fmt" + "os" + "path/filepath" "sort" "strings" "github.com/temporalio/cli/temporalcli/internal/printer" + "gopkg.in/yaml.v3" ) func (c *TemporalEnvCommand) envNameAndKey(cctx *CommandContext, args []string, keyFlag string) (string, string, error) { @@ -43,16 +46,16 @@ func (c *TemporalEnvDeleteCommand) run(cctx *CommandContext, args []string) erro } // Env is guaranteed to already be present - env, _ := cctx.EnvConfigValues[envName] + env, _ := cctx.DeprecatedEnvConfigValues[envName] // User can remove single flag or all in env if key != "" { cctx.Logger.Info("Deleting env property", "env", envName, "property", key) delete(env, key) } else { cctx.Logger.Info("Deleting env", "env", env) - delete(cctx.EnvConfigValues, envName) + delete(cctx.DeprecatedEnvConfigValues, envName) } - return cctx.WriteEnvConfigToFile() + return writeDeprecatedEnvConfigToFile(cctx) } func (c *TemporalEnvGetCommand) run(cctx *CommandContext, args []string) error { @@ -62,7 +65,7 @@ func (c *TemporalEnvGetCommand) run(cctx *CommandContext, args []string) error { } // Env is guaranteed to already be present - env, _ := cctx.EnvConfigValues[envName] + env, _ := cctx.DeprecatedEnvConfigValues[envName] type prop struct { Property string `json:"property"` Value string `json:"value"` @@ -86,8 +89,8 @@ func (c *TemporalEnvListCommand) run(cctx *CommandContext, args []string) error type env struct { Name string `json:"name"` } - envs := make([]env, 0, len(cctx.EnvConfigValues)) - for k := range cctx.EnvConfigValues { + envs := make([]env, 0, len(cctx.DeprecatedEnvConfigValues)) + for k := range cctx.DeprecatedEnvConfigValues { envs = append(envs, env{Name: k}) } // Print as table @@ -120,13 +123,58 @@ func (c *TemporalEnvSetCommand) run(cctx *CommandContext, args []string) error { return fmt.Errorf("too many arguments provided; see --help") } - if cctx.EnvConfigValues == nil { - cctx.EnvConfigValues = map[string]map[string]string{} + if cctx.DeprecatedEnvConfigValues == nil { + cctx.DeprecatedEnvConfigValues = map[string]map[string]string{} } - if cctx.EnvConfigValues[envName] == nil { - cctx.EnvConfigValues[envName] = map[string]string{} + if cctx.DeprecatedEnvConfigValues[envName] == nil { + cctx.DeprecatedEnvConfigValues[envName] = map[string]string{} } cctx.Logger.Info("Setting env property", "env", envName, "property", key, "value", value) - cctx.EnvConfigValues[envName][key] = value - return cctx.WriteEnvConfigToFile() + cctx.DeprecatedEnvConfigValues[envName][key] = value + return writeDeprecatedEnvConfigToFile(cctx) +} + +func writeDeprecatedEnvConfigToFile(cctx *CommandContext) error { + if cctx.Options.DeprecatedEnvConfig.EnvConfigFile == "" { + return fmt.Errorf("unable to find place for env file (unknown HOME dir)") + } + cctx.Logger.Info("Writing env file", "file", cctx.Options.DeprecatedEnvConfig.EnvConfigFile) + return writeDeprecatedEnvConfigFile(cctx.Options.DeprecatedEnvConfig.EnvConfigFile, cctx.DeprecatedEnvConfigValues) +} + +// May be empty result if can't get user home dir +func defaultDeprecatedEnvConfigFile(appName, configName string) string { + // No env file if no $HOME + if dir, err := os.UserHomeDir(); err == nil { + return filepath.Join(dir, ".config", appName, configName+".yaml") + } + return "" +} + +func readDeprecatedEnvConfigFile(file string) (env map[string]map[string]string, err error) { + b, err := os.ReadFile(file) + if os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, fmt.Errorf("failed reading env file: %w", err) + } + var m map[string]map[string]map[string]string + if err := yaml.Unmarshal(b, &m); err != nil { + return nil, fmt.Errorf("failed unmarshalling env YAML: %w", err) + } + return m["env"], nil +} + +func writeDeprecatedEnvConfigFile(file string, env map[string]map[string]string) error { + b, err := yaml.Marshal(map[string]any{"env": env}) + if err != nil { + return fmt.Errorf("failed marshaling YAML: %w", err) + } + // Make parent directories as needed + if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil { + return fmt.Errorf("failed making env file parent dirs: %w", err) + } else if err := os.WriteFile(file, b, 0600); err != nil { + return fmt.Errorf("failed writing env file: %w", err) + } + return nil } diff --git a/temporalcli/commands.env_test.go b/temporalcli/commands.env_test.go index 0956485e8..cdd1157a9 100644 --- a/temporalcli/commands.env_test.go +++ b/temporalcli/commands.env_test.go @@ -12,21 +12,21 @@ func TestEnv_Simple(t *testing.T) { defer h.Close() // Non-existent file, no env found for get - h.Options.EnvConfigFile = "does-not-exist" + h.Options.DeprecatedEnvConfig.EnvConfigFile = "does-not-exist" res := h.Execute("env", "get", "--env", "myenv1") h.ErrorContains(res.Err, `environment "myenv1" not found`) // Temp file for env tmpFile, err := os.CreateTemp("", "") h.NoError(err) - h.Options.EnvConfigFile = tmpFile.Name() - defer os.Remove(h.Options.EnvConfigFile) + h.Options.DeprecatedEnvConfig.EnvConfigFile = tmpFile.Name() + defer os.Remove(h.Options.DeprecatedEnvConfig.EnvConfigFile) // Store a key res = h.Execute("env", "set", "--env", "myenv1", "-k", "foo", "-v", "bar") h.NoError(res.Err) // Confirm file is YAML with expected values - b, err := os.ReadFile(h.Options.EnvConfigFile) + b, err := os.ReadFile(h.Options.DeprecatedEnvConfig.EnvConfigFile) h.NoError(err) var yamlVals map[string]map[string]map[string]string h.NoError(yaml.Unmarshal(b, &yamlVals)) @@ -69,14 +69,6 @@ func TestEnv_Simple(t *testing.T) { res = h.Execute("env", "list") h.NoError(res.Err) h.NotContains(res.Stdout.String(), "myenv2") - - // Ensure env var overrides env file - res = h.Execute("env", "set", "--env", "myenv1", "-k", "address", "-v", "something:1234") - h.NoError(res.Err) - h.NoError(os.Setenv("TEMPORAL_ADDRESS", "overridden:1235")) - defer os.Unsetenv("TEMPORAL_ADDRESS") - res = h.Execute("workflow", "list", "--env", "myenv1") - h.Contains(res.Stderr.String(), "Env var overrode --env setting") } func TestEnv_InputValidation(t *testing.T) { @@ -86,8 +78,8 @@ func TestEnv_InputValidation(t *testing.T) { // myenv1 needs to exist tmpFile, err := os.CreateTemp("", "") h.NoError(err) - h.Options.EnvConfigFile = tmpFile.Name() - defer os.Remove(h.Options.EnvConfigFile) + h.Options.DeprecatedEnvConfig.EnvConfigFile = tmpFile.Name() + defer os.Remove(h.Options.DeprecatedEnvConfig.EnvConfigFile) res := h.Execute("env", "set", "--env", "myenv1", "-k", "foo", "-v", "bar") h.NoError(res.Err) diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 16e9224e0..ace142da3 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -37,34 +37,20 @@ 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.") - cctx.BindFlagEnvVar(f.Lookup("address"), "TEMPORAL_ADDRESS") f.StringVarP(&v.Namespace, "namespace", "n", "default", "Temporal Service Namespace.") - cctx.BindFlagEnvVar(f.Lookup("namespace"), "TEMPORAL_NAMESPACE") f.StringVar(&v.ApiKey, "api-key", "", "API key for request.") - cctx.BindFlagEnvVar(f.Lookup("api-key"), "TEMPORAL_API_KEY") - f.StringArrayVar(&v.GrpcMeta, "grpc-meta", nil, "HTTP headers for requests. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers.") - f.BoolVar(&v.Tls, "tls", false, "Enable base TLS encryption. Does not have additional options like mTLS or client certs.") - cctx.BindFlagEnvVar(f.Lookup("tls"), "TEMPORAL_TLS") + f.StringArrayVar(&v.GrpcMeta, "grpc-meta", nil, "HTTP headers for requests. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers. Can also be made available via environment variable as `TEMPORAL_GRPC_META_[name]`.") + f.BoolVar(&v.Tls, "tls", false, "Enable base TLS encryption. Does not have additional options like mTLS or client certs. This is defaulted to true if api-key or any other TLS options are present. Use --tls=false to explicitly disable.") f.StringVar(&v.TlsCertPath, "tls-cert-path", "", "Path to x509 certificate. Can't be used with --tls-cert-data.") - cctx.BindFlagEnvVar(f.Lookup("tls-cert-path"), "TEMPORAL_TLS_CERT") f.StringVar(&v.TlsCertData, "tls-cert-data", "", "Data for x509 certificate. Can't be used with --tls-cert-path.") - cctx.BindFlagEnvVar(f.Lookup("tls-cert-data"), "TEMPORAL_TLS_CERT_DATA") f.StringVar(&v.TlsKeyPath, "tls-key-path", "", "Path to x509 private key. Can't be used with --tls-key-data.") - cctx.BindFlagEnvVar(f.Lookup("tls-key-path"), "TEMPORAL_TLS_KEY") f.StringVar(&v.TlsKeyData, "tls-key-data", "", "Private certificate key data. Can't be used with --tls-key-path.") - cctx.BindFlagEnvVar(f.Lookup("tls-key-data"), "TEMPORAL_TLS_KEY_DATA") f.StringVar(&v.TlsCaPath, "tls-ca-path", "", "Path to server CA certificate. Can't be used with --tls-ca-data.") - cctx.BindFlagEnvVar(f.Lookup("tls-ca-path"), "TEMPORAL_TLS_CA") f.StringVar(&v.TlsCaData, "tls-ca-data", "", "Data for server CA certificate. Can't be used with --tls-ca-path.") - cctx.BindFlagEnvVar(f.Lookup("tls-ca-data"), "TEMPORAL_TLS_CA_DATA") f.BoolVar(&v.TlsDisableHostVerification, "tls-disable-host-verification", false, "Disable TLS host-name verification.") - cctx.BindFlagEnvVar(f.Lookup("tls-disable-host-verification"), "TEMPORAL_TLS_DISABLE_HOST_VERIFICATION") f.StringVar(&v.TlsServerName, "tls-server-name", "", "Override target TLS server name.") - cctx.BindFlagEnvVar(f.Lookup("tls-server-name"), "TEMPORAL_TLS_SERVER_NAME") f.StringVar(&v.CodecEndpoint, "codec-endpoint", "", "Remote Codec Server endpoint.") - cctx.BindFlagEnvVar(f.Lookup("codec-endpoint"), "TEMPORAL_CODEC_ENDPOINT") f.StringVar(&v.CodecAuth, "codec-auth", "", "Authorization header for Codec Server requests.") - cctx.BindFlagEnvVar(f.Lookup("codec-auth"), "TEMPORAL_CODEC_AUTH") 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.") } @@ -208,8 +194,8 @@ func (v *SharedWorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.F f.Var(&v.TaskTimeout, "task-timeout", "Fail a Workflow Task if it lasts longer than `DURATION`. This is the Start-to-close timeout for a Workflow Task.") f.StringArrayVar(&v.SearchAttribute, "search-attribute", nil, "Search Attribute in `KEY=VALUE` format. Keys must be identifiers, and values must be JSON values. For example: 'YourKey={\"your\": \"value\"}'. Can be passed multiple times.") 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.") - f.StringVar(&v.StaticDetails, "static-details", "", "Static Workflow details for human consumption in UIs. Uses Temporal Markdown formatting, may be multiple lines.") + 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.") } type WorkflowStartOptions struct { @@ -221,7 +207,8 @@ type WorkflowStartOptions struct { } func (v *WorkflowStartOptions) buildFlags(cctx *CommandContext, f *pflag.FlagSet) { - f.StringVar(&v.Cron, "cron", "", "Cron schedule for the Workflow. Deprecated. Use Schedules instead.") + f.StringVar(&v.Cron, "cron", "", "Cron schedule for the Workflow.") + _ = f.MarkDeprecated("cron", "Use Schedules instead.") f.BoolVar(&v.FailExisting, "fail-existing", false, "Fail if the Workflow already exists.") v.StartDelay = 0 f.Var(&v.StartDelay, "start-delay", "Delay before starting the Workflow Execution. Can't be used with cron schedules. If the Workflow receives a signal or update prior to this time, the Workflow Execution starts immediately.") @@ -299,7 +286,7 @@ func (v *NexusEndpointConfigOptions) buildFlags(cctx *CommandContext, f *pflag.F f.StringVar(&v.DescriptionFile, "description-file", "", "Path to the Nexus Endpoint description file. The contents of the description file may use Markdown formatting.") f.StringVar(&v.TargetNamespace, "target-namespace", "", "Namespace where a handler Worker polls for Nexus tasks.") f.StringVar(&v.TargetTaskQueue, "target-task-queue", "", "Task Queue that a handler Worker polls for Nexus tasks.") - f.StringVar(&v.TargetUrl, "target-url", "", "An external Nexus Endpoint that receives forwarded Nexus requests. May be used as an alternative to `--target-namespace` and `--target-task-queue`.") + f.StringVar(&v.TargetUrl, "target-url", "", "An external Nexus Endpoint that receives forwarded Nexus requests. May be used as an alternative to `--target-namespace` and `--target-task-queue`. EXPERIMENTAL.") } type QueryModifiersOptions struct { @@ -315,6 +302,10 @@ type TemporalCommand struct { Command cobra.Command Env string EnvFile string + ConfigFile string + Profile string + DisableConfigFile bool + DisableConfigEnv bool LogLevel StringEnum LogFormat StringEnum Output StringEnum @@ -336,6 +327,7 @@ func NewTemporalCommand(cctx *CommandContext) *TemporalCommand { s.Command.Args = cobra.NoArgs s.Command.AddCommand(&NewTemporalActivityCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalBatchCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalConfigCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalEnvCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalOperatorCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleCommand(cctx, &s).Command) @@ -346,6 +338,10 @@ func NewTemporalCommand(cctx *CommandContext) *TemporalCommand { s.Command.PersistentFlags().StringVar(&s.Env, "env", "default", "Active environment name (`ENV`).") cctx.BindFlagEnvVar(s.Command.PersistentFlags().Lookup("env"), "TEMPORAL_ENV") s.Command.PersistentFlags().StringVar(&s.EnvFile, "env-file", "", "Path to environment settings file. Defaults to `$HOME/.config/temporalio/temporal.yaml`.") + s.Command.PersistentFlags().StringVar(&s.ConfigFile, "config-file", "", "File path to read TOML config from, defaults to `$CONFIG_PATH/temporal/temporal.toml` where `$CONFIG_PATH` is defined as `$HOME/.config` on Unix, \"$HOME/Library/Application Support\" on macOS, and %AppData% on Windows. EXPERIMENTAL.") + s.Command.PersistentFlags().StringVar(&s.Profile, "profile", "", "Profile to use for config file. EXPERIMENTAL.") + s.Command.PersistentFlags().BoolVar(&s.DisableConfigFile, "disable-config-file", false, "If set, disables loading environment config from config file. EXPERIMENTAL.") + s.Command.PersistentFlags().BoolVar(&s.DisableConfigEnv, "disable-config-env", false, "If set, disables loading environment config from environment variables. EXPERIMENTAL.") s.LogLevel = NewStringEnum([]string{"debug", "info", "warn", "error", "never"}, "info") s.Command.PersistentFlags().Var(&s.LogLevel, "log-level", "Log level. Default is \"info\" for most commands and \"warn\" for `server start-dev`. Accepted values: debug, info, warn, error, never.") s.LogFormat = NewStringEnum([]string{"text", "json", "pretty"}, "text") @@ -739,6 +735,166 @@ func NewTemporalBatchTerminateCommand(cctx *CommandContext, parent *TemporalBatc return &s } +type TemporalConfigCommand struct { + Parent *TemporalCommand + Command cobra.Command +} + +func NewTemporalConfigCommand(cctx *CommandContext, parent *TemporalCommand) *TemporalConfigCommand { + var s TemporalConfigCommand + s.Parent = parent + s.Command.Use = "config" + s.Command.Short = "Manage config files (EXPERIMENTAL)" + if hasHighlighting { + s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal. \n\n\x1b[1mtemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\x1b[0m\n\nThe default config file path is \x1b[1m$CONFIG_PATH/temporalio/temporal.toml\x1b[0m where\n\x1b[1m$CONFIG_PATH\x1b[0m is defined as \x1b[1m$HOME/.config\x1b[0m on Unix,\n\x1b[1m$HOME/Library/Application Support\x1b[0m on macOS, and \x1b[1m%AppData%\x1b[0m on Windows.\nThis can be overridden with the \x1b[1mTEMPORAL_CONFIG_FILE\x1b[0m environment\nvariable or \x1b[1m--config-file\x1b[0m.\n\nThe default profile is \x1b[1mdefault\x1b[0m. This can be overridden with the\n\x1b[1mTEMPORAL_PROFILE\x1b[0m environment variable or \x1b[1m--profile\x1b[0m." + } else { + s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal. \n\n```\ntemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\n```\n\nThe default config file path is `$CONFIG_PATH/temporalio/temporal.toml` where\n`$CONFIG_PATH` is defined as `$HOME/.config` on Unix,\n`$HOME/Library/Application Support` on macOS, and `%AppData%` on Windows.\nThis can be overridden with the `TEMPORAL_CONFIG_FILE` environment\nvariable or `--config-file`.\n\nThe default profile is `default`. This can be overridden with the\n`TEMPORAL_PROFILE` environment variable or `--profile`." + } + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewTemporalConfigDeleteCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalConfigDeleteProfileCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalConfigGetCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalConfigListCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalConfigSetCommand(cctx, &s).Command) + return &s +} + +type TemporalConfigDeleteCommand struct { + Parent *TemporalConfigCommand + Command cobra.Command + Prop string +} + +func NewTemporalConfigDeleteCommand(cctx *CommandContext, parent *TemporalConfigCommand) *TemporalConfigDeleteCommand { + var s TemporalConfigDeleteCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "delete [flags]" + s.Command.Short = "Delete a config file property (EXPERIMENTAL)\n" + if hasHighlighting { + s.Command.Long = "Remove a property within a profile.\n\n\x1b[1mtemporal env delete \\\n --prop tls.client_cert_path\x1b[0m" + } else { + s.Command.Long = "Remove a property within a profile.\n\n```\ntemporal env delete \\\n --prop tls.client_cert_path\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.Prop, "prop", "p", "", "Specific property to delete. If unset, deletes entire profile. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "prop") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalConfigDeleteProfileCommand struct { + Parent *TemporalConfigCommand + Command cobra.Command +} + +func NewTemporalConfigDeleteProfileCommand(cctx *CommandContext, parent *TemporalConfigCommand) *TemporalConfigDeleteProfileCommand { + var s TemporalConfigDeleteProfileCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "delete-profile [flags]" + s.Command.Short = "Delete an entire config profile (EXPERIMENTAL)\n" + if hasHighlighting { + s.Command.Long = "Remove a full profile entirely. The \x1b[1m--profile\x1b[0m must be set explicitly.\n\n\x1b[1mtemporal env delete-profile \\\n --profile my-profile\x1b[0m" + } else { + s.Command.Long = "Remove a full profile entirely. The `--profile` must be set explicitly.\n\n```\ntemporal env delete-profile \\\n --profile my-profile\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalConfigGetCommand struct { + Parent *TemporalConfigCommand + Command cobra.Command + Prop string +} + +func NewTemporalConfigGetCommand(cctx *CommandContext, parent *TemporalConfigCommand) *TemporalConfigGetCommand { + var s TemporalConfigGetCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "get [flags]" + s.Command.Short = "Show config file properties (EXPERIMENTAL)" + if hasHighlighting { + s.Command.Long = "Display specific properties or the entire profile.\n\n\x1b[1mtemporal config get \\\n --prop address\x1b[0m\n\nor\n\n\x1b[1mtemporal config get\x1b[0m" + } else { + s.Command.Long = "Display specific properties or the entire profile.\n\n```\ntemporal config get \\\n --prop address\n```\n\nor\n\n```\ntemporal config get\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.Prop, "prop", "p", "", "Specific property to get.") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalConfigListCommand struct { + Parent *TemporalConfigCommand + Command cobra.Command +} + +func NewTemporalConfigListCommand(cctx *CommandContext, parent *TemporalConfigCommand) *TemporalConfigListCommand { + var s TemporalConfigListCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list [flags]" + s.Command.Short = "Show config file profiles (EXPERIMENTAL)" + if hasHighlighting { + s.Command.Long = "List profile names in the config file.\n\n\x1b[1mtemporal config list\x1b[0m" + } else { + s.Command.Long = "List profile names in the config file.\n\n```\ntemporal config list\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalConfigSetCommand struct { + Parent *TemporalConfigCommand + Command cobra.Command + Prop string + Value string +} + +func NewTemporalConfigSetCommand(cctx *CommandContext, parent *TemporalConfigCommand) *TemporalConfigSetCommand { + var s TemporalConfigSetCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "set [flags]" + s.Command.Short = "Set config file properties (EXPERIMENTAL)" + if hasHighlighting { + s.Command.Long = "Assign a value to a property and store it in the config file:\n\n\x1b[1mtemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\x1b[0m" + } else { + s.Command.Long = "Assign a value to a property and store it in the config file:\n\n```\ntemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.Prop, "prop", "p", "", "Property name. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "prop") + s.Command.Flags().StringVarP(&s.Value, "value", "v", "", "Property value. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "value") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalEnvCommand struct { Parent *TemporalCommand Command cobra.Command @@ -3132,7 +3288,7 @@ func NewTemporalWorkflowListCommand(cctx *CommandContext, parent *TemporalWorkfl } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.Query, "query", "q", "", "Content for an SQL-like `QUERY` List Filter.") - s.Command.Flags().BoolVar(&s.Archived, "archived", false, "Limit output to archived Workflow Executions.") + s.Command.Flags().BoolVar(&s.Archived, "archived", false, "Limit output to archived Workflow Executions. EXPERIMENTAL.") s.Command.Flags().IntVar(&s.Limit, "limit", 0, "Maximum number of Workflow Executions to display.") s.Command.Flags().IntVar(&s.PageSize, "page-size", 0, "Maximum number of Workflow Executions to fetch at a time from the server.") s.Command.Run = func(c *cobra.Command, args []string) { @@ -3242,7 +3398,8 @@ func NewTemporalWorkflowResetCommand(cctx *CommandContext, parent *TemporalWorkf s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for reset. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "reason") s.ReapplyType = NewStringEnum([]string{"All", "Signal", "None"}, "All") - s.Command.Flags().Var(&s.ReapplyType, "reapply-type", "Types of events to re-apply after reset point. Deprecated. Use --reapply-exclude instead. Accepted values: All, Signal, None.") + s.Command.Flags().Var(&s.ReapplyType, "reapply-type", "Types of events to re-apply after reset point. Accepted values: All, Signal, None.") + _ = s.Command.Flags().MarkDeprecated("reapply-type", "Use --reapply-exclude instead.") s.ReapplyExclude = NewStringEnumArray([]string{"All", "Signal", "Update"}, []string{}) s.Command.Flags().Var(&s.ReapplyExclude, "reapply-exclude", "Exclude these event types from re-application. Accepted values: All, Signal, Update.") s.Type = NewStringEnum([]string{"FirstWorkflowTask", "LastWorkflowTask", "LastContinuedAsNew", "BuildId"}, "") diff --git a/temporalcli/commands.go b/temporalcli/commands.go index 217f2f299..6df20f923 100644 --- a/temporalcli/commands.go +++ b/temporalcli/commands.go @@ -10,7 +10,6 @@ import ( "log/slog" "os" "os/signal" - "path/filepath" "slices" "strings" "syscall" @@ -26,13 +25,13 @@ import ( commonpb "go.temporal.io/api/common/v1" "go.temporal.io/api/failure/v1" "go.temporal.io/api/temporalproto" + "go.temporal.io/sdk/contrib/envconfig" "go.temporal.io/sdk/converter" "go.temporal.io/sdk/temporal" "go.temporal.io/server/common/headers" "google.golang.org/grpc" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" - "gopkg.in/yaml.v3" ) // Version is the value put as the default command version. This is often @@ -42,9 +41,9 @@ var Version = "0.0.0-DEV" type CommandContext struct { // This context is closed on interrupt context.Context - Options CommandOptions - EnvConfigValues map[string]map[string]string - FlagsWithEnvVars []*pflag.Flag + Options CommandOptions + DeprecatedEnvConfigValues map[string]map[string]string + FlagsWithEnvVars []*pflag.Flag // These values may not be available until after pre-run of main command Printer *printer.Printer @@ -55,20 +54,19 @@ type CommandContext struct { // Is set to true if any command actually started running. This is a hack to workaround the fact // that cobra does not properly exit nonzero if an unknown command/subcommand is given. ActuallyRanCommand bool + + // Root/current command only set inside of pre-run + RootCommand *TemporalCommand + CurrentCommand *cobra.Command } type CommandOptions struct { // If empty, assumed to be os.Args[1:] Args []string - // If unset, defaulted to $HOME/.config/temporalio/temporal.yaml - EnvConfigFile string - // If unset, attempts to extract --env from Args (which defaults to "default") - EnvConfigName string - // If true, does not do any env config reading - DisableEnvConfig bool - // If nil, os.LookupEnv is used. This is for environment variables and not - // related to env config stuff above. - LookupEnv func(string) (string, bool) + // Deprecated `--env` and `--env-file` approach + DeprecatedEnvConfig DeprecatedEnvConfig + // If nil, [envconfig.EnvLookupOS] is used. + EnvLookup envconfig.EnvLookup // These three fields below default to OS values Stdin io.Reader @@ -81,6 +79,15 @@ type CommandOptions struct { AdditionalClientGRPCDialOptions []grpc.DialOption } +type DeprecatedEnvConfig struct { + // If unset, defaulted to $HOME/.config/temporalio/temporal.yaml + EnvConfigFile string + // If unset, attempts to extract --env from Args (which defaults to "default") + EnvConfigName string + // If true, does not do any env config reading + DisableEnvConfig bool +} + // NewCommandContext creates a CommandContext for use by the rest of the CLI. // Among other things, this parses the env config file and modifies // options/flags according to the parameters set there. @@ -106,8 +113,8 @@ func (c *CommandContext) preprocessOptions() error { if len(c.Options.Args) == 0 { c.Options.Args = os.Args[1:] } - if c.Options.LookupEnv == nil { - c.Options.LookupEnv = os.LookupEnv + if c.Options.EnvLookup == nil { + c.Options.EnvLookup = envconfig.EnvLookupOS } if c.Options.Stdin == nil { @@ -142,37 +149,38 @@ func (c *CommandContext) preprocessOptions() error { // Why last? Callers need the CommandContext to be usable no matter what, // because they rely on it to print errors even if env parsing fails. In // that situation, we will return both the CommandContext AND an error. - if !c.Options.DisableEnvConfig { - if c.Options.EnvConfigFile == "" { + if !c.Options.DeprecatedEnvConfig.DisableEnvConfig { + if c.Options.DeprecatedEnvConfig.EnvConfigFile == "" { // Default to --env-file, prefetched from CLI args for i, arg := range c.Options.Args { if arg == "--env-file" && i+1 < len(c.Options.Args) { - c.Options.EnvConfigFile = c.Options.Args[i+1] + c.Options.DeprecatedEnvConfig.EnvConfigFile = c.Options.Args[i+1] } } // Default to inside home dir - if c.Options.EnvConfigFile == "" { - c.Options.EnvConfigFile = defaultEnvConfigFile("temporalio", "temporal") + if c.Options.DeprecatedEnvConfig.EnvConfigFile == "" { + c.Options.DeprecatedEnvConfig.EnvConfigFile = defaultDeprecatedEnvConfigFile("temporalio", "temporal") } } - if c.Options.EnvConfigName == "" { - c.Options.EnvConfigName = "default" - if envVal, ok := c.Options.LookupEnv(temporalEnv); ok { - c.Options.EnvConfigName = envVal + if c.Options.DeprecatedEnvConfig.EnvConfigName == "" { + c.Options.DeprecatedEnvConfig.EnvConfigName = "default" + if envVal, _ := c.Options.EnvLookup.LookupEnv(temporalEnv); envVal != "" { + c.Options.DeprecatedEnvConfig.EnvConfigName = envVal } // Default to --env, prefetched from CLI args for i, arg := range c.Options.Args { if arg == "--env" && i+1 < len(c.Options.Args) { - c.Options.EnvConfigName = c.Options.Args[i+1] + c.Options.DeprecatedEnvConfig.EnvConfigName = c.Options.Args[i+1] } } } // Load env flags - if c.Options.EnvConfigFile != "" { + if c.Options.DeprecatedEnvConfig.EnvConfigFile != "" { var err error - if c.EnvConfigValues, err = readEnvConfigFile(c.Options.EnvConfigFile); err != nil { + c.DeprecatedEnvConfigValues, err = readDeprecatedEnvConfigFile(c.Options.DeprecatedEnvConfig.EnvConfigFile) + if err != nil { return err } } @@ -191,14 +199,6 @@ func (c *CommandContext) BindFlagEnvVar(flag *pflag.Flag, envVar string) { c.FlagsWithEnvVars = append(c.FlagsWithEnvVars, flag) } -func (c *CommandContext) WriteEnvConfigToFile() error { - if c.Options.EnvConfigFile == "" { - return fmt.Errorf("unable to find place for env file (unknown HOME dir)") - } - c.Logger.Info("Writing env file", "file", c.Options.EnvConfigFile) - return writeEnvConfigFile(c.Options.EnvConfigFile, c.EnvConfigValues) -} - func (c *CommandContext) MarshalFriendlyJSONPayloads(m *common.Payloads) (json.RawMessage, error) { if m == nil { return []byte("null"), nil @@ -266,7 +266,7 @@ func (c *CommandContext) populateFlagsFromEnv(flags *pflag.FlagSet) (func(*slog. return } // Env config first, then environ - if v, ok := c.EnvConfigValues[c.Options.EnvConfigName][flag.Name]; ok { + if v, ok := c.DeprecatedEnvConfigValues[c.Options.DeprecatedEnvConfig.EnvConfigName][flag.Name]; ok { if err := flag.Value.Set(v); err != nil { flagErr = fmt.Errorf("failed setting flag %v from config with value %v: %w", flag.Name, v, err) return @@ -274,7 +274,7 @@ func (c *CommandContext) populateFlagsFromEnv(flags *pflag.FlagSet) (func(*slog. flag.Changed = true } if anns := flag.Annotations[flagEnvVarAnnotation]; len(anns) == 1 { - if envVal, ok := c.Options.LookupEnv(anns[0]); ok { + if envVal, _ := c.Options.EnvLookup.LookupEnv(anns[0]); envVal != "" { if err := flag.Value.Set(envVal); err != nil { flagErr = fmt.Errorf("failed setting flag %v with env name %v and value %v: %w", flag.Name, anns[0], envVal, err) @@ -380,6 +380,8 @@ func (c *TemporalCommand) initCommand(cctx *CommandContext) { // must unset in post-run origNoColor := color.NoColor c.Command.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + // Set command + cctx.CurrentCommand = cmd // Populate environ. We will make the error return here which will cause // usage to be printed. logCalls, err := cctx.populateFlagsFromEnv(cmd.Flags()) @@ -403,13 +405,13 @@ func (c *TemporalCommand) initCommand(cctx *CommandContext) { } cctx.ActuallyRanCommand = true - if cctx.Options.EnvConfigName != "default" { - if _, ok := cctx.EnvConfigValues[cctx.Options.EnvConfigName]; !ok { + if cctx.Options.DeprecatedEnvConfig.EnvConfigName != "default" { + if _, ok := cctx.DeprecatedEnvConfigValues[cctx.Options.DeprecatedEnvConfig.EnvConfigName]; !ok { if _, ok := cmd.Annotations["ignoresMissingEnv"]; !ok { // stfu about help output cmd.SilenceErrors = true cmd.SilenceUsage = true - return fmt.Errorf("environment %q not found", cctx.Options.EnvConfigName) + return fmt.Errorf("environment %q not found", cctx.Options.DeprecatedEnvConfig.EnvConfigName) } } } @@ -433,6 +435,9 @@ func VersionString() string { } func (c *TemporalCommand) preRun(cctx *CommandContext) error { + // Set this command as the root + cctx.RootCommand = c + // Configure logger if not already on context if cctx.Logger == nil { // If level is never, make noop logger @@ -508,43 +513,6 @@ func (c *TemporalCommand) preRun(cctx *CommandContext) error { return nil } -// May be empty result if can't get user home dir -func defaultEnvConfigFile(appName, configName string) string { - // No env file if no $HOME - if dir, err := os.UserHomeDir(); err == nil { - return filepath.Join(dir, ".config", appName, configName+".yaml") - } - return "" -} - -func readEnvConfigFile(file string) (env map[string]map[string]string, err error) { - b, err := os.ReadFile(file) - if os.IsNotExist(err) { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("failed reading env file: %w", err) - } - var m map[string]map[string]map[string]string - if err := yaml.Unmarshal(b, &m); err != nil { - return nil, fmt.Errorf("failed unmarshalling env YAML: %w", err) - } - return m["env"], nil -} - -func writeEnvConfigFile(file string, env map[string]map[string]string) error { - b, err := yaml.Marshal(map[string]any{"env": env}) - if err != nil { - return fmt.Errorf("failed marshaling YAML: %w", err) - } - // Make parent directories as needed - if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil { - return fmt.Errorf("failed making env file parent dirs: %w", err) - } else if err := os.WriteFile(file, b, 0600); err != nil { - return fmt.Errorf("failed writing env file: %w", err) - } - return nil -} - func aliasNormalizer(aliases map[string]string) func(f *pflag.FlagSet, name string) pflag.NormalizedName { return func(f *pflag.FlagSet, name string) pflag.NormalizedName { if actual := aliases[name]; actual != "" { diff --git a/temporalcli/commands.server.go b/temporalcli/commands.server.go index 3edaef80f..5b2ffc253 100644 --- a/temporalcli/commands.server.go +++ b/temporalcli/commands.server.go @@ -179,19 +179,19 @@ func persistentClusterID() string { // If there is not a database file in use, we want a cluster ID to be the same // for every re-run, so we set it as an environment config in a special env // file. We do not error if we can neither read nor write the file. - file := defaultEnvConfigFile("temporalio", "version-info") + file := defaultDeprecatedEnvConfigFile("temporalio", "version-info") if file == "" { // No file, can do nothing here return uuid.NewString() } // Try to get existing first - env, _ := readEnvConfigFile(file) + env, _ := readDeprecatedEnvConfigFile(file) if id := env["default"]["cluster-id"]; id != "" { return id } // Create and try to write id := uuid.NewString() - _ = writeEnvConfigFile(file, map[string]map[string]string{"default": {"cluster-id": id}}) + _ = writeDeprecatedEnvConfigFile(file, map[string]map[string]string{"default": {"cluster-id": id}}) return id } diff --git a/temporalcli/commands.workflow_exec_test.go b/temporalcli/commands.workflow_exec_test.go index 9d88a1100..108d8cb5d 100644 --- a/temporalcli/commands.workflow_exec_test.go +++ b/temporalcli/commands.workflow_exec_test.go @@ -433,12 +433,7 @@ func (s *SharedServerSuite) TestWorkflow_Execute_ClientHeaders() { } func (s *SharedServerSuite) TestWorkflow_Execute_EnvVars() { - s.CommandHarness.Options.LookupEnv = func(key string) (string, bool) { - if key == "TEMPORAL_ADDRESS" { - return s.Address(), true - } - return "", false - } + s.CommandHarness.Options.EnvLookup = EnvLookupMap{"TEMPORAL_ADDRESS": s.Address()} res := s.Execute( "workflow", "execute", "--task-queue", s.Worker().Options.TaskQueue, @@ -492,12 +487,7 @@ func (s *SharedServerSuite) TestWorkflow_Execute_EnvConfig() { s.ContainsOnSameLine(res.Stdout.String(), "Result", `"env-conf-input"`) // And we can specify `env` with a property - s.CommandHarness.Options.LookupEnv = func(key string) (string, bool) { - if key == "TEMPORAL_ENV" { - return "myenv", true - } - return "", false - } + s.CommandHarness.Options.EnvLookup = EnvLookupMap{"TEMPORAL_ENV": "myenv"} res = s.Execute( "workflow", "execute", "--env-file", tmpFile.Name(), diff --git a/temporalcli/commands_test.go b/temporalcli/commands_test.go index 74606eef2..5d1e50289 100644 --- a/temporalcli/commands_test.go +++ b/temporalcli/commands_test.go @@ -163,10 +163,11 @@ func (h *CommandHarness) Execute(args ...string) *CommandResult { // Set args options.Args = args // Disable env if no env file and no --env-file arg - options.DisableEnvConfig = options.EnvConfigFile == "" && !slices.Contains(args, "--env-file") + options.DeprecatedEnvConfig.DisableEnvConfig = + options.DeprecatedEnvConfig.EnvConfigFile == "" && !slices.Contains(args, "--env-file") // Set default env name if disabled, otherwise we'll fail with missing environment - if options.DisableEnvConfig { - options.EnvConfigName = "default" + if options.DeprecatedEnvConfig.DisableEnvConfig { + options.DeprecatedEnvConfig.EnvConfigName = "default" } // Capture error options.Fail = func(err error) { @@ -191,6 +192,21 @@ func (h *CommandHarness) Execute(args ...string) *CommandResult { return res } +type EnvLookupMap map[string]string + +func (e EnvLookupMap) Environ() []string { + ret := make([]string, 0, len(e)) + for k := range e { + ret = append(ret, k) + } + return ret +} + +func (e EnvLookupMap) LookupEnv(key string) (string, bool) { + v, ok := e[key] + return v, ok +} + // Run shared server suite func TestSharedServerSuite(t *testing.T) { suite.Run(t, new(SharedServerSuite)) diff --git a/temporalcli/commandsgen/code.go b/temporalcli/commandsgen/code.go index 5c10057b5..e51296d46 100644 --- a/temporalcli/commandsgen/code.go +++ b/temporalcli/commandsgen/code.go @@ -208,6 +208,9 @@ func (c *Command) writeCode(w *codeWriter) error { w.writeLinef("s.Command.Annotations = make(map[string]string)") w.writeLinef("s.Command.Annotations[\"ignoresMissingEnv\"] = \"true\"") } + if c.Deprecated != "" { + w.writeLinef("s.Command.Deprecated = %q", c.Deprecated) + } // Add subcommands for _, subCommand := range subCommands { w.writeLinef("s.Command.AddCommand(&New%v(cctx, &s).Command)", subCommand.structName()) @@ -408,6 +411,10 @@ func (o *Option) writeFlagBuilding(selfVar, flagVar string, w *codeWriter) error for _, alias := range o.Aliases { desc += fmt.Sprintf(` Aliased as "--%v".`, alias) } + // If experimental, make obvious + if o.Experimental { + desc += " EXPERIMENTAL." + } if setDefault != "" { // set default before calling Var so that it stores thedefault value into the flag @@ -426,5 +433,8 @@ func (o *Option) writeFlagBuilding(selfVar, flagVar string, w *codeWriter) error if o.Env != "" { w.writeLinef("cctx.BindFlagEnvVar(%v.Lookup(%q), %q)", flagVar, o.Name, o.Env) } + if o.Deprecated != "" { + w.writeLinef("_ = %v.MarkDeprecated(%q, %q)", flagVar, o.Name, o.Deprecated) + } return nil } diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 9eb0920dd..2a551a5f7 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -101,6 +101,8 @@ # required: Whether the option is required. (bool) # short: The single letter short version of name (i.e. a for address). (string) # env: Binds the environment variable to this flag. (string) +# implied-env: Documents the environment variable as bound to the flag, +# but doesn't actually bind it. # default: The default value. No default means zero value of the type. (string) # enum-values: A list of possible values for the string-enum type. (string[]) # aliases: A list of aliases for the option. (string[]) @@ -143,11 +145,44 @@ commands: description: Active environment name (`ENV`). default: default env: TEMPORAL_ENV + # TODO(cretz): Deprecate when `config` GA + # deprecated: | + # Use `profile` instead. If an env file is present, it will take + # precedent over `config` file or config environment variables. - name: env-file type: string description: | Path to environment settings file. Defaults to `$HOME/.config/temporalio/temporal.yaml`. + # TODO(cretz): Deprecate when `config` GA + # deprecated: | + # Use `config-file` instead. If an env file is present, it will take + # precedent over `config` file or config environment variables. + - name: config-file + type: string + description: | + File path to read TOML config from, defaults to + `$CONFIG_PATH/temporal/temporal.toml` where `$CONFIG_PATH` is defined + as `$HOME/.config` on Unix, "$HOME/Library/Application Support" on + macOS, and %AppData% on Windows. + experimental: true + implied-env: TEMPORAL_CONFIG_FILE + - name: profile + type: string + description: Profile to use for config file. + experimental: true + implied-env: TEMPORAL_PROFILE + - name: disable-config-file + type: bool + description: | + If set, disables loading environment config from config file. + experimental: true + - name: disable-config-env + type: bool + description: | + If set, disables loading environment config from environment + variables. + experimental: true - name: log-level type: string-enum enum-values: @@ -652,6 +687,125 @@ commands: description: Reason for terminating the batch job. required: true + - name: temporal config + summary: Manage config files (EXPERIMENTAL) + description: | + Config files are TOML files that contain profiles, with each profile + containing configuration for connecting to Temporal. + + ``` + temporal config set \ + --prop address \ + --value us-west-2.aws.api.temporal.io:7233 + ``` + + The default config file path is `$CONFIG_PATH/temporalio/temporal.toml` where + `$CONFIG_PATH` is defined as `$HOME/.config` on Unix, + `$HOME/Library/Application Support` on macOS, and `%AppData%` on Windows. + This can be overridden with the `TEMPORAL_CONFIG_FILE` environment + variable or `--config-file`. + + The default profile is `default`. This can be overridden with the + `TEMPORAL_PROFILE` environment variable or `--profile`. + docs: + description-header: >- + Temporal CLI 'config' commands allow the getting, setting, deleting, and + listing of configuration properties for connecting to Temporal. + keywords: + - cli reference + - command-line-interface-cli + - configuration + - config + - config delete + - config get + - config list + - config set + - environment + - temporal cli + tags: + - Temporal CLI + + - name: temporal config delete + summary: | + Delete a config file property (EXPERIMENTAL) + description: | + Remove a property within a profile. + + ``` + temporal env delete \ + --prop tls.client_cert_path + ``` + options: + - name: prop + short: p + type: string + description: | + Specific property to delete. If unset, deletes entire profile. + required: true + + - name: temporal config delete-profile + summary: | + Delete an entire config profile (EXPERIMENTAL) + description: | + Remove a full profile entirely. The `--profile` must be set explicitly. + + ``` + temporal env delete-profile \ + --profile my-profile + ``` + + - name: temporal config get + summary: Show config file properties (EXPERIMENTAL) + description: | + Display specific properties or the entire profile. + + ``` + temporal config get \ + --prop address + ``` + + or + + ``` + temporal config get + ``` + options: + - name: prop + short: p + type: string + description: Specific property to get. + + - name: temporal config list + summary: Show config file profiles (EXPERIMENTAL) + description: | + List profile names in the config file. + + ``` + temporal config list + ``` + + - name: temporal config set + summary: Set config file properties (EXPERIMENTAL) + description: | + Assign a value to a property and store it in the config file: + + ``` + temporal config set \ + --prop address \ + --value us-west-2.aws.api.temporal.io:7233 + ``` + options: + - name: prop + short: p + type: string + description: Property name. + required: true + - name: value + short: v + type: string + description: Property value. + required: true + - name: temporal worker summary: Read or update Worker state description: | @@ -1072,6 +1226,8 @@ commands: Temporal CLI checks for an `--env` option first, then checks for the `TEMPORAL_ENV` environment variable. If neither is set, the CLI uses the "default" environment. + # TODO(cretz): Deprecate when `config` GA + # deprecated: Use `config` subcommands instead. docs: description-header: >- Temporal CLI 'env' commands allow the configuration, setting, deleting, @@ -1110,6 +1266,8 @@ commands: --env prod \ --key tls-key-path ``` + # TODO(cretz): Deprecate when `config` GA + # deprecated: Use `config` subcommands instead. maximum-args: 1 options: - name: key @@ -1138,6 +1296,8 @@ commands: If you don't specify an environment (with `--env` or by setting the `TEMPORAL_ENV` variable), this command lists properties of the "default" environment. + # TODO(cretz): Deprecate when `config` GA + # deprecated: Use `config` subcommands instead. maximum-args: 1 options: - name: key @@ -1150,6 +1310,8 @@ commands: description: | List the environments you have set up on your local computer. Environments are stored in "$HOME/.config/temporalio/temporal.yaml". + # TODO(cretz): Deprecate when `config` GA + # deprecated: Use `config` subcommands instead. ignores-missing-env: true - name: temporal env set @@ -1171,6 +1333,8 @@ commands: Storing keys with CLI option names lets the CLI automatically set those options for you. This reduces effort and helps avoid typos when issuing commands. + # TODO(cretz): Deprecate when `config` GA + # deprecated: Use `config` subcommands instead. maximum-args: 2 ignores-missing-env: true options: @@ -3317,10 +3481,8 @@ commands: required: true - name: reapply-type type: string-enum - description: | - Types of events to re-apply after reset point. - Deprecated. - Use --reapply-exclude instead. + description: Types of events to re-apply after reset point. + deprecated: Use --reapply-exclude instead. enum-values: - All - Signal @@ -3865,81 +4027,84 @@ option-sets: type: string description: Temporal Service gRPC endpoint. default: 127.0.0.1:7233 - env: TEMPORAL_ADDRESS + implied-env: TEMPORAL_ADDRESS - name: namespace short: n type: string description: Temporal Service Namespace. default: default - env: TEMPORAL_NAMESPACE + implied-env: TEMPORAL_NAMESPACE - name: api-key type: string description: API key for request. - env: TEMPORAL_API_KEY + implied-env: TEMPORAL_API_KEY - name: grpc-meta type: string[] description: | HTTP headers for requests. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers. + Can also be made available via environment variable as + `TEMPORAL_GRPC_META_[name]`. - name: tls type: bool description: | - Enable base TLS encryption. - Does not have additional options like mTLS or client certs. - env: TEMPORAL_TLS + Enable base TLS encryption. Does not have additional options like mTLS + or client certs. This is defaulted to true if api-key or any other TLS + options are present. Use --tls=false to explicitly disable. + implied-env: TEMPORAL_TLS - name: tls-cert-path type: string description: | Path to x509 certificate. Can't be used with --tls-cert-data. - env: TEMPORAL_TLS_CERT + implied-env: TEMPORAL_TLS_CLIENT_CERT_PATH - name: tls-cert-data type: string description: | Data for x509 certificate. Can't be used with --tls-cert-path. - env: TEMPORAL_TLS_CERT_DATA + implied-env: TEMPORAL_TLS_CLIENT_CERT_DATA - name: tls-key-path type: string description: | Path to x509 private key. Can't be used with --tls-key-data. - env: TEMPORAL_TLS_KEY + implied-env: TEMPORAL_TLS_CLIENT_KEY_PATH - name: tls-key-data type: string description: | Private certificate key data. Can't be used with --tls-key-path. - env: TEMPORAL_TLS_KEY_DATA + implied-env: TEMPORAL_TLS_CLIENT_KEY_DATA - name: tls-ca-path type: string description: | Path to server CA certificate. Can't be used with --tls-ca-data. - env: TEMPORAL_TLS_CA + implied-env: TEMPORAL_TLS_SERVER_CA_CERT_PATH - name: tls-ca-data type: string description: | Data for server CA certificate. Can't be used with --tls-ca-path. - env: TEMPORAL_TLS_CA_DATA + implied-env: TEMPORAL_TLS_SERVER_CA_CERT_DATA - name: tls-disable-host-verification type: bool description: Disable TLS host-name verification. - env: TEMPORAL_TLS_DISABLE_HOST_VERIFICATION + implied-env: TEMPORAL_TLS_DISABLE_HOST_VERIFICATION - name: tls-server-name type: string description: Override target TLS server name. - env: TEMPORAL_TLS_SERVER_NAME + implied-env: TEMPORAL_TLS_SERVER_NAME - name: codec-endpoint type: string description: Remote Codec Server endpoint. - env: TEMPORAL_CODEC_ENDPOINT + implied-env: TEMPORAL_CODEC_ENDPOINT - name: codec-auth type: string description: Authorization header for Codec Server requests. - env: TEMPORAL_CODEC_AUTH + implied-env: TEMPORAL_CODEC_AUTH - name: codec-header type: string[] description: | @@ -4178,10 +4343,8 @@ option-sets: options: - name: cron type: string - description: | - Cron schedule for the Workflow. - Deprecated. - Use Schedules instead. + description: Cron schedule for the Workflow. + deprecated: Use Schedules instead. - name: fail-existing type: bool description: Fail if the Workflow already exists. diff --git a/temporalcli/commandsgen/parse.go b/temporalcli/commandsgen/parse.go index 670996dec..a93c69482 100644 --- a/temporalcli/commandsgen/parse.go +++ b/temporalcli/commandsgen/parse.go @@ -23,9 +23,11 @@ type ( Name string `yaml:"name"` Type string `yaml:"type"` Description string `yaml:"description"` + Deprecated string `yaml:"deprecated"` Short string `yaml:"short,omitempty"` Default string `yaml:"default,omitempty"` Env string `yaml:"env,omitempty"` + ImpliedEnv string `yaml:"implied-env,omitempty"` Required bool `yaml:"required,omitempty"` Aliases []string `yaml:"aliases,omitempty"` EnumValues []string `yaml:"enum-values,omitempty"` @@ -41,6 +43,7 @@ type ( Description string `yaml:"description"` DescriptionPlain string DescriptionHighlighted string + Deprecated string `yaml:"deprecated"` HasInit bool `yaml:"has-init"` ExactArgs int `yaml:"exact-args"` MaximumArgs int `yaml:"maximum-args"` diff --git a/temporalcli/internal/printer/printer.go b/temporalcli/internal/printer/printer.go index d20b806f7..87d2713be 100644 --- a/temporalcli/internal/printer/printer.go +++ b/temporalcli/internal/printer/printer.go @@ -1,6 +1,7 @@ package printer import ( + "encoding/base64" "encoding/json" "fmt" "io" @@ -428,6 +429,9 @@ func (p *Printer) textVal(v any) string { return fmt.Sprintf("", err) } return string(b) + } else if ref.Kind() == reflect.Slice && ref.Type().Elem().Kind() == reflect.Uint8 { + b, _ := ref.Interface().([]byte) + return "bytes(" + base64.StdEncoding.EncodeToString(b) + ")" } else if ref.Kind() == reflect.Slice { // We don't want to reimplement all of fmt.Sprintf, but expanding one level of // slice helps format lists more consistently. From 9d4c649ae7ffdc8b54504c082c7a772ff41b440d Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 23 Apr 2025 19:07:44 +0100 Subject: [PATCH 13/15] Add CODEOWNERS (#794) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..0f4298b7d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @temporalio/sdk From 46a13e868387882c7f615baf176f53df2dc47110 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 30 Apr 2025 10:13:21 -0700 Subject: [PATCH 14/15] Auto generate and publish CLI docs on each release (#796) ## What was changed Created a new Github Action that auto-generates and publishes a PR to the [documentation repo](https://github.com/temporalio/documentation) with the latest generated CLI docs. Corresponding docs side PR is already merged, https://github.com/temporalio/documentation/pull/3525, and first PR of the auto-generated docs is merged in https://github.com/temporalio/documentation/pull/3528. Docs side YML: https://github.com/temporalio/documentation/blob/main/.github/workflows/update-cli-docs.yml ## Why? Makes keeping CLI docs up to date significantly easier. ## Checklist 1. Closes 2. How was this tested: Tested in this PR, but there's a small chance I missed something in the actual release trigger workflow. No way to fully test that until a new release is cut after this action is on main. 3. Any docs updates needed? --- .github/workflows/trigger-docs.yml | 52 ++++++++++++++++++++++++++++ temporalcli/commands.gen.go | 4 +-- temporalcli/commandsgen/commands.yml | 2 -- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/trigger-docs.yml diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml new file mode 100644 index 000000000..2013a0177 --- /dev/null +++ b/.github/workflows/trigger-docs.yml @@ -0,0 +1,52 @@ +name: Trigger CLI docs update +on: + workflow_dispatch: + release: + types: [published] +jobs: + update: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Get user info from GitHub API + id: get_user + run: | + echo "GitHub actor: ${{ github.actor }}" + # Query the GitHub API for the user's details. + curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/users/${{ github.actor }} > user.json + + # Extract the user's full name if available, default to the username otherwise. + git_name=$(jq -r '.name // empty' user.json) + if [ -z "$git_name" ]; then + git_name="${{ github.actor }}" + fi + + git_email="${{ github.actor }}@users.noreply.github.com" + + # Set the outputs for subsequent steps. + echo "GIT_NAME=$git_name" >> $GITHUB_OUTPUT + echo "GIT_EMAIL=$git_email" >> $GITHUB_OUTPUT + + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} + private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: documentation # generate a token with permissions to trigger GHA in documentation repo + + - name: Trigger Documentation Workflow + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + gh workflow run update-cli-docs.yml \ + -R temporalio/documentation \ + -r cli-docs-autoupdate \ + -f cli_release_tag="${{ github.ref_name }}" \ + -f commit_author="${{ steps.get_user.outputs.GIT_NAME }}" \ + -f commit_author_email="${{ steps.get_user.outputs.GIT_EMAIL }}" \ + -f commit_message="Update CLI docs for release ${{ github.ref_name }}" diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index ace142da3..a171230e1 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -473,9 +473,9 @@ func NewTemporalActivityPauseCommand(cctx *CommandContext, parent *TemporalActiv s.Command.Use = "pause [flags]" s.Command.Short = "Pause an Activity" if hasHighlighting { - s.Command.Long = "Pause an Activity.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run to completion.\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nActivities can be specified by their Activity ID or Activity Type. \nOne of those parameters must be provided. If both are provided - Activity\nType will be used, and Activity ID will be ignored.\n\n\x1b[1mSpecify the Activity and Workflow IDs:\x1b[0m\ntemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n```" + s.Command.Long = "Pause an Activity.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run to completion.\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nActivities can be specified by their Activity ID or Activity Type. \nOne of those parameters must be provided. If both are provided - Activity\nType will be used, and Activity ID will be ignored.\n\nSpecify the Activity and Workflow IDs:\n\n\x1b[1mtemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\x1b[0m" } else { - s.Command.Long = "Pause an Activity.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run to completion.\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nActivities can be specified by their Activity ID or Activity Type. \nOne of those parameters must be provided. If both are provided - Activity\nType will be used, and Activity ID will be ignored.\n\n```\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n```" + s.Command.Long = "Pause an Activity.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run to completion.\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nActivities can be specified by their Activity ID or Activity Type. \nOne of those parameters must be provided. If both are provided - Activity\nType will be used, and Activity ID will be ignored.\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "Activity ID to pause.") diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index 2a551a5f7..e79518a32 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -433,8 +433,6 @@ commands: One of those parameters must be provided. If both are provided - Activity Type will be used, and Activity ID will be ignored. - ``` - Specify the Activity and Workflow IDs: ``` From 1b48223b133965e0f6f9061f52956c12ba178966 Mon Sep 17 00:00:00 2001 From: maniram Date: Fri, 9 May 2025 14:28:25 +0000 Subject: [PATCH 15/15] Cli console message url formatting fixed --- temporalcli/commands.server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temporalcli/commands.server.go b/temporalcli/commands.server.go index 5b2ffc253..feec22115 100644 --- a/temporalcli/commands.server.go +++ b/temporalcli/commands.server.go @@ -154,10 +154,10 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string) defer s.Stop() cctx.Printer.Printlnf("CLI %v\n", VersionString()) - cctx.Printer.Printlnf("%-8s %v:%v", "Server:", toFriendlyIp(opts.FrontendIP), opts.FrontendPort) + cctx.Printer.Printlnf("%-8s http://%v:%v", "Server:", toFriendlyIp(opts.FrontendIP), opts.FrontendPort) // Only print HTTP port if explicitly provided to avoid promoting the unstable HTTP API. if opts.FrontendHTTPPort > 0 { - cctx.Printer.Printlnf("%-8s %v:%v", "HTTP:", toFriendlyIp(opts.FrontendIP), opts.FrontendHTTPPort) + cctx.Printer.Printlnf("%-8s http://%v:%v", "HTTP:", toFriendlyIp(opts.FrontendIP), opts.FrontendHTTPPort) } if !t.Headless { cctx.Printer.Printlnf("%-8s http://%v:%v%v", "UI:", toFriendlyIp(opts.UIIP), opts.UIPort, opts.PublicPath)