Skip to content

Commit 57b7157

Browse files
feat(cli): Add workflow refresh tasks command (#7657)
<!-- 1-2 line summary of WHAT changed technically: - Always link the relevant projects GitHub issue, unless it is a minor bugfix - Good: "Modified FailoverDomain mapper to allow null ActiveClusterName #320" - Bad: "added nil check" --> **What changed?** This PR exposes the existing workflow API through a new CLI command **workflow refresh-tasks**, enabling users without admin access to refresh tasks for their workflows when tasks are lost or stuck. Usage of the command: cadence --domain <domain> workflow refresh-tasks -w <workflow_id> -r <run_id> <!-- Your goal is to provide all the required context for a future maintainer to understand the reasons for making this change (see https://cbea.ms/git-commit/#why-not-how). How did this work previously (and what was wrong with it)? What has changed, and why did you solve it this way? - Good: "Active-active domains have independent cluster attributes per region. Previously, modifying cluster attributes required spedifying the default ActiveClusterName which updates the global domain default. This prevents operators from updating regional configurations without affecting the primary cluster designation. This change allows attribute updates to be independent of active cluster selection." - Bad: "Improves domain handling" --> **Why?** Currently, refreshing workflow tasks is only available via cadence admin workflow refresh, which requires admin-level permissions. However, the equivalent RefreshWorkflowTasks API already exists on the workflow frontend service with write-level permissions, so adding this workflow refresh CLI command to allow non-admin users to refresh workflow tasks. <!-- Include specific test commands and setup. Please include the exact commands such that another maintainer or contributor can reproduce the test steps taken. - e.g Unit test commands with exact invocation `go test -v ./common/types/mapper/proto -run TestFailoverDomainRequest` - For integration tests include setup steps and test commands Example: "Started local server with `./cadence start`, then ran `make test_e2e`" - For local simulation testing include setup steps for the server and how you ran the tests - Good: Full commands that reviewers can copy-paste to verify - Bad: "Tested locally" or "Added tests" --> **How did you test it?** - Unit tests: go test -run Test_RefreshWorkflowTasks ./tools/cli/... - Manual test by building the CLI locally and running `cadence workflow refresh-tasks` <!-- If there are risks that the release engineer should know about document them here. For example: - Has an API/IDL been modified? Is it backwards/forwards compatible? If not, what are the repecussions? - Has a schema change been introduced? Is it possible to roll back? - Has a feature flag been re-used for a new purpose? - Is there a potential performance concern? Is the change modifying core task processing logic? - If truly N/A, you can mark it as such --> **Potential risks** Low risk as it is adding a new CLI command. <!-- If this PR completes a user facing feature or changes functionality add release notes here. Your release notes should allow a user and the release engineer to understand the changes with little context. Always ensure that the description contains a link to the relevant GitHub issue. --> **Release notes** CLI: Added cadence workflow refresh command to refresh workflow tasks without requiring admin permissions. This command uses the workflow API with write-level authorization, making it accessible to domain owners and users with write access. Previously, refreshing workflow tasks was only available via the admin CLI. <!-- Consider whether this change requires documentation updates in the Cadence-Docs repo - If yes: mention what needs updating (or link to docs PR in cadence-docs repo) - If in doubt, add a note about potential doc needs - Only mark N/A if you're certain no docs are affected --> **Documentation Changes** Command: cadence workflow refresh-tasks Refreshes workflow tasks to resume progress. This is useful when workflow tasks are lost or stuck due to transient failures. Usage: cadence --domain <domain> workflow refresh-tasks -w <workflow_id> -r <run_id> --- ## Reviewer Validation **PR Description Quality** (check these before reviewing code): - [ ] **"What changed"** provides a clear 1-2 line summary - [ ] Project Issue is linked - [ ] **"Why"** explains the full motivation with sufficient context - [ ] **Testing is documented:** - [ ] Unit test commands are included (with exact `go test` invocation) - [ ] Integration test setup/commands included (if integration tests were run) - [ ] Canary testing details included (if canary was mentioned) - [ ] **Potential risks** section is thoughtfully filled out (or legitimately N/A) - [ ] **Release notes** included if this completes a user-facing feature - [ ] **Documentation** needs are addressed (or noted if uncertain) --------- Signed-off-by: Gaziza Yestemirova <gaziza@uber.com>
1 parent 6a4e77a commit 57b7157

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

tools/cli/workflow.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ func newWorkflowCommands() []*cli.Command {
4545
Flags: flagsForExecution,
4646
Action: DiagnoseWorkflow,
4747
},
48+
{
49+
Name: "refresh-tasks",
50+
Aliases: []string{"rt"},
51+
Usage: "refreshes all the workflow tasks to resume progress",
52+
Flags: flagsForExecution,
53+
Action: RefreshWorkflowTasks,
54+
},
4855
{
4956
Name: "activity",
5057
Aliases: []string{"act"},

tools/cli/workflow_commands.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,43 @@ func DiagnoseWorkflow(c *cli.Context) error {
134134
return nil
135135
}
136136

137+
// RefreshWorkflowTasks refreshes all the tasks of a workflow
138+
func RefreshWorkflowTasks(c *cli.Context) error {
139+
wfClient, err := getWorkflowClient(c)
140+
if err != nil {
141+
return err
142+
}
143+
144+
domain, err := getRequiredOption(c, FlagDomain)
145+
if err != nil {
146+
return commoncli.Problem("Required flag not found: ", err)
147+
}
148+
wid, err := getRequiredOption(c, FlagWorkflowID)
149+
if err != nil {
150+
return commoncli.Problem("Required flag not found: ", err)
151+
}
152+
rid := c.String(FlagRunID)
153+
154+
ctx, cancel, err := newContext(c)
155+
if err != nil {
156+
return commoncli.Problem("Error creating context: ", err)
157+
}
158+
defer cancel()
159+
160+
err = wfClient.RefreshWorkflowTasks(ctx, &types.RefreshWorkflowTasksRequest{
161+
Domain: domain,
162+
Execution: &types.WorkflowExecution{
163+
WorkflowID: wid,
164+
RunID: rid,
165+
},
166+
})
167+
if err != nil {
168+
return commoncli.Problem("Refresh workflow tasks failed.", err)
169+
}
170+
fmt.Println("Refresh workflow tasks succeeded.")
171+
return nil
172+
}
173+
137174
// ShowHistory shows the history of given workflow execution based on workflowID and runID.
138175
func ShowHistory(c *cli.Context) error {
139176
wid, err := getRequiredOption(c, FlagWorkflowID)

tools/cli/workflow_commands_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2579,6 +2579,79 @@ func Test_DescribeWorkflow_Errors(t *testing.T) {
25792579
assert.ErrorContains(t, err, fmt.Sprintf("%s is required", FlagWorkflowID))
25802580
}
25812581

2582+
func Test_RefreshWorkflowTasks(t *testing.T) {
2583+
tests := []struct {
2584+
name string
2585+
setupMock func(*frontend.MockClient)
2586+
args []clitest.CliArgument
2587+
errContains string
2588+
}{
2589+
{
2590+
name: "missing domain",
2591+
setupMock: func(_ *frontend.MockClient) {},
2592+
args: []clitest.CliArgument{},
2593+
errContains: "Required flag not found",
2594+
},
2595+
{
2596+
name: "missing workflowID",
2597+
setupMock: func(_ *frontend.MockClient) {},
2598+
args: []clitest.CliArgument{
2599+
clitest.StringArgument(FlagDomain, "test-domain"),
2600+
},
2601+
errContains: "Required flag not found",
2602+
},
2603+
{
2604+
name: "success",
2605+
setupMock: func(client *frontend.MockClient) {
2606+
client.EXPECT().RefreshWorkflowTasks(gomock.Any(), &types.RefreshWorkflowTasksRequest{
2607+
Domain: "test-domain",
2608+
Execution: &types.WorkflowExecution{
2609+
WorkflowID: "test-workflow-id",
2610+
RunID: "test-run-id",
2611+
},
2612+
}).Return(nil)
2613+
},
2614+
args: []clitest.CliArgument{
2615+
clitest.StringArgument(FlagDomain, "test-domain"),
2616+
clitest.StringArgument(FlagWorkflowID, "test-workflow-id"),
2617+
clitest.StringArgument(FlagRunID, "test-run-id"),
2618+
},
2619+
errContains: "",
2620+
},
2621+
{
2622+
name: "api error",
2623+
setupMock: func(client *frontend.MockClient) {
2624+
client.EXPECT().RefreshWorkflowTasks(gomock.Any(), gomock.Any()).
2625+
Return(errors.New("api error"))
2626+
},
2627+
args: []clitest.CliArgument{
2628+
clitest.StringArgument(FlagDomain, "test-domain"),
2629+
clitest.StringArgument(FlagWorkflowID, "test-workflow-id"),
2630+
clitest.StringArgument(FlagRunID, "test-run-id"),
2631+
},
2632+
errContains: "Refresh workflow tasks failed",
2633+
},
2634+
}
2635+
2636+
for _, tt := range tests {
2637+
t.Run(tt.name, func(t *testing.T) {
2638+
mockCtrl := gomock.NewController(t)
2639+
serverFrontendClient := frontend.NewMockClient(mockCtrl)
2640+
app := NewCliApp(&clientFactoryMock{
2641+
serverFrontendClient: serverFrontendClient,
2642+
})
2643+
tt.setupMock(serverFrontendClient)
2644+
ctx := clitest.NewCLIContext(t, app, tt.args...)
2645+
err := RefreshWorkflowTasks(ctx)
2646+
if tt.errContains == "" {
2647+
assert.NoError(t, err)
2648+
} else {
2649+
assert.ErrorContains(t, err, tt.errContains)
2650+
}
2651+
})
2652+
}
2653+
}
2654+
25822655
func Test_ObserveHistory_MissingFlags(t *testing.T) {
25832656
app := NewCliApp(&clientFactoryMock{})
25842657
set := flag.NewFlagSet("test", 0)

0 commit comments

Comments
 (0)