diff --git a/temporalcli/commands.gen.go b/temporalcli/commands.gen.go index 9aa526b8..5c2106ab 100644 --- a/temporalcli/commands.gen.go +++ b/temporalcli/commands.gen.go @@ -1261,9 +1261,9 @@ func NewTemporalOperatorSearchAttributeCommand(cctx *CommandContext, parent *Tem s.Command.Use = "search-attribute" s.Command.Short = "Search Attribute operations" if hasHighlighting { - s.Command.Long = "Create, list, or remove Search Attributes fields stored in a Workflow\nExecution's metadata:\n\n\x1b[1mtemporal operator search-attribute create \\\n --name YourAttributeName \\\n --type Keyword\x1b[0m\n\nSupported types include: Text, Keyword, Int, Double, Bool, Datetime, and\nKeywordList.\n\nIf you wish to delete a Search Attribute, please contact support\nat https://support.temporal.io." + s.Command.Long = "Create, list, or remove Search Attributes fields stored in a Workflow\nExecution's metadata:\n\n\x1b[1mtemporal operator search-attribute create \\\n --name YourAttributeName \\\n --type Keyword\x1b[0m\n\nSupported types include: text, keyword, int, double, bool, datetime, and\nkeyword-list.\n\nIf you wish to delete a Search Attribute, please contact support\nat https://support.temporal.io." } else { - s.Command.Long = "Create, list, or remove Search Attributes fields stored in a Workflow\nExecution's metadata:\n\n```\ntemporal operator search-attribute create \\\n --name YourAttributeName \\\n --type Keyword\n```\n\nSupported types include: Text, Keyword, Int, Double, Bool, Datetime, and\nKeywordList.\n\nIf you wish to delete a Search Attribute, please contact support\nat https://support.temporal.io." + s.Command.Long = "Create, list, or remove Search Attributes fields stored in a Workflow\nExecution's metadata:\n\n```\ntemporal operator search-attribute create \\\n --name YourAttributeName \\\n --type Keyword\n```\n\nSupported types include: text, keyword, int, double, bool, datetime, and\nkeyword-list.\n\nIf you wish to delete a Search Attribute, please contact support\nat https://support.temporal.io." } s.Command.Args = cobra.NoArgs s.Command.AddCommand(&NewTemporalOperatorSearchAttributeCreateCommand(cctx, &s).Command) @@ -1293,7 +1293,7 @@ func NewTemporalOperatorSearchAttributeCreateCommand(cctx *CommandContext, paren s.Command.Args = cobra.NoArgs s.Command.Flags().StringArrayVar(&s.Name, "name", nil, "Search Attribute name. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "name") - s.Type = NewStringEnumArray([]string{"text", "keyword", "int", "double", "bool", "datetime", "keyword-list"}, []string{}) + s.Type = NewStringEnumArray([]string{"text", "keyword", "int", "double", "bool", "datetime", "keyword-list", "KeywordList"}, []string{}) s.Command.Flags().Var(&s.Type, "type", "Search Attribute type. Accepted values: text, keyword, int, double, bool, datetime, keyword-list. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "type") s.Command.Run = func(c *cobra.Command, args []string) { @@ -1714,7 +1714,7 @@ func NewTemporalServerStartDevCommand(cctx *CommandContext, parent *TemporalServ s.Command.Flags().StringArrayVar(&s.SqlitePragma, "sqlite-pragma", nil, "SQLite pragma statements in \"PRAGMA=VALUE\" format.") s.Command.Flags().StringArrayVar(&s.DynamicConfigValue, "dynamic-config-value", nil, "Dynamic configuration value using `KEY=VALUE` pairs. Keys must be identifiers, and values must be JSON values. For example: 'YourKey=\"YourString\"'. Can be passed multiple times.") s.Command.Flags().BoolVar(&s.LogConfig, "log-config", false, "Log the server config to stderr.") - s.Command.Flags().StringArrayVar(&s.SearchAttribute, "search-attribute", nil, "Search attributes to register using `KEY=VALUE` pairs. Keys must be identifiers, and values must be the search attribute type, which is one of the following: Text, Keyword, Int, Double, Bool, Datetime, KeywordList.") + s.Command.Flags().StringArrayVar(&s.SearchAttribute, "search-attribute", nil, "Search attributes to register using `KEY=VALUE` pairs. Keys must be identifiers, and values must be the search attribute type, which is one of the following: text, keyword, int, double, bool, datetime, keyword-list.") s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) diff --git a/temporalcli/commands.operator_search_attribute.go b/temporalcli/commands.operator_search_attribute.go index 405a31b8..10a0dcc0 100644 --- a/temporalcli/commands.operator_search_attribute.go +++ b/temporalcli/commands.operator_search_attribute.go @@ -29,6 +29,9 @@ func (c *TemporalOperatorSearchAttributeCreateCommand) run(cctx *CommandContext, searchAttributes := make(map[string]enums.IndexedValueType, len(c.Type.Values)) for i, saType := range c.Type.Values { + if strings.EqualFold(saType, "keyword-list") { + saType = "KeywordList" + } saName := c.Name[i] typeInt, err := searchAttributeTypeStringToEnum(saType) if err != nil { diff --git a/temporalcli/commands.operator_search_attribute_test.go b/temporalcli/commands.operator_search_attribute_test.go index efed38b6..1c2bd27f 100644 --- a/temporalcli/commands.operator_search_attribute_test.go +++ b/temporalcli/commands.operator_search_attribute_test.go @@ -98,3 +98,21 @@ func (s *SharedServerSuite) TestOperator_SearchAttribute() { s.Equal(enums.INDEXED_VALUE_TYPE_DATETIME, jsonOut.SystemAttributes["StartTime"]) s.Equal(enums.INDEXED_VALUE_TYPE_KEYWORD, jsonOut.SystemAttributes["WorkflowId"]) } + +func (s *SharedServerSuite) TestOperator_SearchAttribute_LegacyType() { + res := s.Execute( + "operator", "search-attribute", "create", + "--address", s.Address(), + "--name", "RandomKeywordField", + "--type", "keyword-list", + ) + s.NoError(res.Err) + + res = s.Execute( + "operator", "search-attribute", "create", + "--address", s.Address(), + "--name", "OtherRandomKeywordField", + "--type", "keywordlist", + ) + s.NoError(res.Err) +} diff --git a/temporalcli/commands.schedule.go b/temporalcli/commands.schedule.go index e458ba91..00537bee 100644 --- a/temporalcli/commands.schedule.go +++ b/temporalcli/commands.schedule.go @@ -156,7 +156,11 @@ func (c *TemporalScheduleBackfillCommand) run(cctx *CommandContext, args []strin defer cl.Close() sch := cl.ScheduleClient().GetHandle(cctx, c.ScheduleId) - overlap, err := enumspb.ScheduleOverlapPolicyFromString(c.OverlapPolicy.Value) + overlapPolicy, err := mapLegacyOverlapPolicy(c.OverlapPolicy.Value) + if err != nil { + return err + } + overlap, err := enumspb.ScheduleOverlapPolicyFromString(overlapPolicy) if err != nil { return err } @@ -296,11 +300,15 @@ func (c *TemporalScheduleCreateCommand) run(cctx *CommandContext, args []string) // ScheduleBackfill not supported } + overlapPolicy, err := mapLegacyOverlapPolicy(c.OverlapPolicy.Value) + if err != nil { + return err + } if err = c.toScheduleSpec(&opts.Spec); err != nil { return err } else if opts.Action, err = toScheduleAction(&c.SharedWorkflowStartOptions, &c.PayloadInputOptions); err != nil { return err - } else if opts.Overlap, err = enumspb.ScheduleOverlapPolicyFromString(c.OverlapPolicy.Value); err != nil { + } else if opts.Overlap, err = enumspb.ScheduleOverlapPolicyFromString(overlapPolicy); err != nil { return err } else if opts.Memo, err = stringKeysJSONValues(c.ScheduleMemo, false); err != nil { return fmt.Errorf("invalid memo values: %w", err) @@ -496,7 +504,11 @@ func (c *TemporalScheduleTriggerCommand) run(cctx *CommandContext, args []string defer cl.Close() sch := cl.ScheduleClient().GetHandle(cctx, c.ScheduleId) - overlap, err := enumspb.ScheduleOverlapPolicyFromString(c.OverlapPolicy.Value) + overlapPolicy, err := mapLegacyOverlapPolicy(c.OverlapPolicy.Value) + if err != nil { + return err + } + overlap, err := enumspb.ScheduleOverlapPolicyFromString(overlapPolicy) if err != nil { return err } @@ -530,7 +542,11 @@ func (c *TemporalScheduleUpdateCommand) run(cctx *CommandContext, args []string) }, } - if newSchedule.Policy.Overlap, err = enumspb.ScheduleOverlapPolicyFromString(c.OverlapPolicy.Value); err != nil { + overlapPolicy, err := mapLegacyOverlapPolicy(c.OverlapPolicy.Value) + if err != nil { + return err + } + if newSchedule.Policy.Overlap, err = enumspb.ScheduleOverlapPolicyFromString(overlapPolicy); err != nil { return err } @@ -622,3 +638,22 @@ func encodeSearchAttributesToPayloads(in map[string]any) (map[string]*commonpb.P } return out, nil } + +func mapLegacyOverlapPolicy(in string) (string, error) { + switch strings.ToLower(in) { + case "skip": + return "Skip", nil + case "buffer-one", "bufferone": + return "BufferOne", nil + case "buffer-all", "bufferall": + return "BufferAll", nil + case "cancel-other", "cancelother": + return "CancelOther", nil + case "terminate-other", "terminateother": + return "TerminateOther", nil + case "allow-all", "allowall": + return "AllowAll", nil + default: + return "", fmt.Errorf("invalid overlap policy: %q", in) + } +} diff --git a/temporalcli/commands.schedule_test.go b/temporalcli/commands.schedule_test.go index ec216743..e1237d46 100644 --- a/temporalcli/commands.schedule_test.go +++ b/temporalcli/commands.schedule_test.go @@ -439,6 +439,33 @@ func (s *SharedServerSuite) TestSchedule_Backfill() { }, 10*time.Second, 100*time.Millisecond) } +func (s *SharedServerSuite) TestSchedule_Backfill_New_OverlapPolicy() { + schedId, schedWfId, res := s.createSchedule("--interval", "10d/5h") + s.NoError(res.Err) + + res = s.Execute( + "schedule", "backfill", + "--address", s.Address(), + "-s", schedId, + "--start-time", "2022-02-02T00:00:00Z", + "--end-time", "2022-02-28T00:00:00Z", + "--overlap-policy", "allow-all", + ) + s.NoError(res.Err) + + s.Eventually(func() bool { + res = s.Execute( + "workflow", "list", + "--address", s.Address(), + "-q", fmt.Sprintf(`TemporalScheduledById = "%s"`, schedId), + ) + s.NoError(res.Err) + out := res.Stdout.String() + re := regexp.MustCompile(regexp.QuoteMeta(schedWfId + "-2022-02")) + return len(re.FindAllString(out, -1)) == 3 + }, 10*time.Second, 100*time.Millisecond) +} + func (s *SharedServerSuite) TestSchedule_Update() { schedId, schedWfId, res := s.createSchedule("--interval", "10d") s.NoError(res.Err) diff --git a/temporalcli/commands.workflow_exec.go b/temporalcli/commands.workflow_exec.go index b885e4ca..e5af05f7 100644 --- a/temporalcli/commands.workflow_exec.go +++ b/temporalcli/commands.workflow_exec.go @@ -276,17 +276,19 @@ func buildStartOptions(sw *SharedWorkflowStartOptions, w *WorkflowStartOptions) StartDelay: w.StartDelay.Duration(), } if w.IdReusePolicy.Value != "" { + idReusePolicy := convertReusePolicy(w.IdReusePolicy.Value) var err error o.WorkflowIDReusePolicy, err = stringToProtoEnum[enums.WorkflowIdReusePolicy]( - w.IdReusePolicy.Value, enums.WorkflowIdReusePolicy_shorthandValue, enums.WorkflowIdReusePolicy_value) + idReusePolicy, enums.WorkflowIdReusePolicy_shorthandValue, enums.WorkflowIdReusePolicy_value) if err != nil { return o, fmt.Errorf("invalid workflow ID reuse policy: %w", err) } } if w.IdConflictPolicy.Value != "" { + idConflictPolicy := convertConflictPolicy(w.IdConflictPolicy.Value) var err error o.WorkflowIDConflictPolicy, err = stringToProtoEnum[enums.WorkflowIdConflictPolicy]( - w.IdConflictPolicy.Value, enums.WorkflowIdConflictPolicy_shorthandValue, enums.WorkflowIdConflictPolicy_value) + idConflictPolicy, enums.WorkflowIdConflictPolicy_shorthandValue, enums.WorkflowIdConflictPolicy_value) if err != nil { return o, fmt.Errorf("invalid workflow ID conflict policy: %w", err) } @@ -588,3 +590,29 @@ func isWorkflowTerminatingEvent(t enums.EventType) bool { } return false } + +func convertReusePolicy(s string) string { + switch strings.ToLower(s) { + case "allow-duplicate": + return "AllowDuplicate" + case "allow-duplicate-failed-only": + return "AllowDuplicateFailedOnly" + case "reject-duplicate": + return "RejectDuplicate" + case "terminate-if-running": + return "TerminateIfRunning" + } + return s +} + +func convertConflictPolicy(s string) string { + switch strings.ToLower(s) { + case "fail": + return "Fail" + case "use-existing": + return "UseExisting" + case "terminate-existing": + return "TerminateExisting" + } + return s +} diff --git a/temporalcli/commands.workflow_reset.go b/temporalcli/commands.workflow_reset.go index e5e802a3..7ad66fd0 100644 --- a/temporalcli/commands.workflow_reset.go +++ b/temporalcli/commands.workflow_reset.go @@ -96,7 +96,7 @@ func (c *TemporalWorkflowResetCommand) doWorkflowReset(cctx *CommandContext, cl } reapplyType := enums.RESET_REAPPLY_TYPE_ALL_ELIGIBLE - if c.ReapplyType.Value != "All" { + if strings.ToLower(c.ReapplyType.Value) != "all" { if len(c.ReapplyExclude.Values) > 0 { return errors.New("--reapply-type cannot be used with --reapply-exclude. Use --reapply-exclude.") } @@ -161,16 +161,17 @@ func (c *TemporalWorkflowResetCommand) runBatchReset(cctx *CommandContext, cl cl } func (c *TemporalWorkflowResetCommand) batchResetOptions(resetType string) *common.ResetOptions { - switch resetType { - case "FirstWorkflowTask": + + switch strings.ToLower(resetType) { + case "firstworkflowtask", "first-workflow-task": return &common.ResetOptions{ Target: &common.ResetOptions_FirstWorkflowTask{}, } - case "LastWorkflowTask": + case "lastworkflowtask", "last-workflow-task": return &common.ResetOptions{ Target: &common.ResetOptions_LastWorkflowTask{}, } - case "BuildId": + case "buildid", "build-id": return &common.ResetOptions{ Target: &common.ResetOptions_BuildId{ BuildId: c.BuildId, @@ -184,12 +185,12 @@ func (c *TemporalWorkflowResetCommand) batchResetOptions(resetType string) *comm func (c *TemporalWorkflowResetCommand) getResetEventIDByType(ctx context.Context, cl client.Client) (string, int64, error) { resetType, namespace, wid, rid := c.Type.Value, c.Parent.Namespace, c.WorkflowId, c.RunId wfsvc := cl.WorkflowService() - switch resetType { - case "LastWorkflowTask": + switch strings.ToLower(resetType) { + case "lastworkflowtask", "last-workflow-task": return getLastWorkflowTaskEventID(ctx, namespace, wid, rid, wfsvc) - case "LastContinuedAsNew": + case "lastcontinuedasnew", "last-continued-as-new": return getLastContinueAsNewID(ctx, namespace, wid, rid, wfsvc) - case "FirstWorkflowTask": + case "firstworkflowtask", "first-workflow-task": return getFirstWorkflowTaskEventID(ctx, namespace, wid, rid, wfsvc) default: return "", -1, fmt.Errorf("invalid reset type: %s", resetType) diff --git a/temporalcli/commands.workflow_reset_test.go b/temporalcli/commands.workflow_reset_test.go index e8da74dd..8bf3aa64 100644 --- a/temporalcli/commands.workflow_reset_test.go +++ b/temporalcli/commands.workflow_reset_test.go @@ -77,6 +77,48 @@ func (s *SharedServerSuite) TestWorkflow_Reset_ToFirstWorkflowTask() { s.Greater(activityExecutions, 1, "Should have re-executed the workflow from the beginning") } +func (s *SharedServerSuite) TestWorkflow_Reset_ToFirstWorkflowTask_New_Type() { + var wfExecutions, activityExecutions int + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + activityExecutions++ + return nil, nil + }) + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + workflow.ExecuteActivity(ctx, DevActivity, 1).Get(ctx, nil) + wfExecutions++ + return nil, nil + }) + + // Start the workflow + searchAttr := "keyword-" + uuid.NewString() + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + SearchAttributes: map[string]any{"CustomKeywordField": searchAttr}, + }, + DevWorkflow, + "ignored", + ) + s.NoError(err) + var junk any + s.NoError(run.Get(s.Context, &junk)) + s.Equal(1, wfExecutions) + + // Reset to the first workflow task + res := s.Execute( + "workflow", "reset", + "--address", s.Address(), + "-w", run.GetID(), + "-t", "first-workflow-task", + "--reason", "test-reset-FirstWorkflowTask", + ) + require.NoError(s.T(), res.Err) + s.awaitNextWorkflow(searchAttr) + s.Equal(2, wfExecutions, "Should have re-executed the workflow from the beginning") + s.Greater(activityExecutions, 1, "Should have re-executed the workflow from the beginning") +} + func (s *SharedServerSuite) TestWorkflow_Reset_ToLastWorkflowTask() { var wfExecutions, activityExecutions int s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { diff --git a/temporalcli/commandsgen/commands.yml b/temporalcli/commandsgen/commands.yml index e6b296a6..4836e0e3 100644 --- a/temporalcli/commandsgen/commands.yml +++ b/temporalcli/commandsgen/commands.yml @@ -1049,8 +1049,8 @@ commands: --type Keyword ``` - Supported types include: Text, Keyword, Int, Double, Bool, Datetime, and - KeywordList. + Supported types include: text, keyword, int, double, bool, datetime, and + keyword-list. If you wish to delete a Search Attribute, please contact support at https://support.temporal.io. @@ -1081,6 +1081,8 @@ commands: - bool - datetime - keyword-list + hidden-legacy-values: + - KeywordList required: true - name: temporal operator search-attribute list @@ -1534,7 +1536,7 @@ commands: Search attributes to register using `KEY=VALUE` pairs. Keys must be identifiers, and values must be the search attribute type, which is one of the following: - Text, Keyword, Int, Double, Bool, Datetime, KeywordList. + text, keyword, int, double, bool, datetime, keyword-list. - name: temporal task-queue summary: Manage Task Queues