Skip to content

Commit b042454

Browse files
berndverstBernd VerstCopilot
authored
DurableTask release 1.0.0b7 : Add experimental scheduler attach command (#9677)
* Allow attaching of schedulers * Update version to 1.0.0b7 * Refactor params * Update output * Update src/durabletask/azext_durabletask/_params.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/durabletask/azext_durabletask/_scheduler.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address copilot core review * lint * Update CODEOWNERS for durabletask --------- Co-authored-by: Bernd Verst <beverst@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 12f6102 commit b042454

10 files changed

Lines changed: 781 additions & 139 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@
322322

323323
/src/azext_gallery-service-artifact/ @rohitbhoopalam
324324

325-
/src/azext_durabletask/ @RyanLettieri
325+
/src/azext_durabletask/ @berndverst @torosent
326326

327327
/src/acat @qinqingxu @Sherylueen @yongxin-ms @wh-alice
328328

src/durabletask/HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Release History
44
===============
55

6+
1.0.0b7
7+
+++++
8+
* Add experimental `az durabletask scheduler attach` command to attach a Durable Task scheduler to a Function App or Container App.
9+
* Update table output to provide more relevant information for schedulers.
10+
611
1.0.0b6
712
+++++
813
* Update ARM API version to `2026-02-01`
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from collections import OrderedDict
7+
8+
9+
def _get_resource_group(result):
10+
"""Derive the resource group name from the resource id."""
11+
resource_id = result.get('id', '')
12+
if resource_id:
13+
from azure.mgmt.core.tools import parse_resource_id
14+
parsed = parse_resource_id(resource_id)
15+
return parsed.get('resource_group', '')
16+
return ''
17+
18+
19+
def scheduler_table_format(result):
20+
"""Format a single scheduler for table output."""
21+
return OrderedDict([
22+
('Name', result.get('name', '')),
23+
('ResourceGroup', _get_resource_group(result)),
24+
('Location', result.get('location', '')),
25+
('State', result.get('properties', {}).get('provisioningState', '')),
26+
('SKU', result.get('properties', {}).get('sku', {}).get('name', '')),
27+
('Capacity Units', result.get('properties', {}).get('sku', {}).get('capacity', '')),
28+
])
29+
30+
31+
def scheduler_list_table_format(results):
32+
"""Format a list of schedulers for table output."""
33+
return [scheduler_table_format(r) for r in results]
34+
35+
36+
def taskhub_table_format(result):
37+
"""Format a single task hub for table output."""
38+
return OrderedDict([
39+
('Name', result.get('name', '')),
40+
('ResourceGroup', _get_resource_group(result)),
41+
('State', result.get('properties', {}).get('provisioningState', '')),
42+
])
43+
44+
45+
def taskhub_list_table_format(results):
46+
"""Format a list of task hubs for table output."""
47+
return [taskhub_table_format(r) for r in results]

src/durabletask/azext_durabletask/_help.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,31 @@
99
# pylint: disable=too-many-lines
1010

1111
from knack.help_files import helps # pylint: disable=unused-import
12+
13+
helps['durabletask scheduler attach'] = """
14+
type: command
15+
short-summary: "[Experimental] Attach a Durable Task scheduler to a Function App or Container App."
16+
long-summary: |
17+
This command is experimental and may change in future releases.
18+
19+
Assigns a Durable Task role to the target resource's managed identity
20+
and configures the target's application settings or environment variables
21+
with the scheduler endpoint and task hub name.
22+
examples:
23+
- name: Attach a scheduler to a function app with the Worker role
24+
text: |
25+
az durabletask scheduler attach -g myResourceGroup -n myScheduler \\
26+
--task-hub-name myTaskHub --role-type worker \\
27+
--target /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Web/sites/myFunctionApp
28+
- name: Attach a scheduler to a container app with the Data Contributor role
29+
text: |
30+
az durabletask scheduler attach -g myResourceGroup -n myScheduler \\
31+
--task-hub-name myTaskHub --role-type contributor \\
32+
--target /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.App/containerApps/myContainerApp
33+
- name: Attach using a user-assigned managed identity
34+
text: |
35+
az durabletask scheduler attach -g myResourceGroup -n myScheduler \\
36+
--task-hub-name myTaskHub --role-type worker \\
37+
--target /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Web/sites/myFunctionApp \\
38+
--identity /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity
39+
"""

src/durabletask/azext_durabletask/_params.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,22 @@
1010

1111

1212
def load_arguments(self, _): # pylint: disable=unused-argument
13-
pass
13+
with self.argument_context('durabletask scheduler attach') as c:
14+
c.argument('resource_group_name', options_list=['--resource-group', '-g'],
15+
help='Name of the resource group containing the scheduler.',
16+
required=True)
17+
c.argument('scheduler_name', options_list=['--name', '-n'],
18+
help='Name of the Durable Task scheduler.',
19+
required=True)
20+
c.argument('task_hub_name', options_list=['--task-hub-name'],
21+
help='Name of the Durable Task task hub.',
22+
required=True)
23+
c.argument('target', options_list=['--target', '-t'],
24+
help='Resource ID of the target Function App or Container App.',
25+
required=True)
26+
c.argument('role_type', options_list=['--role-type'],
27+
help='The type of role to assign to the target managed identity.',
28+
choices=['worker', 'contributor', 'reader'], required=True)
29+
c.argument('identity', options_list=['--identity'],
30+
help='Resource ID of a user-assigned managed identity. '
31+
'If omitted, the system-assigned managed identity is used.')
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from .aaz.latest.durabletask.retention_policy import Create as _Create
7+
from azure.cli.core.aaz import AAZStrArg
8+
9+
10+
class CreatePolicy(_Create):
11+
"""Create a retention policy for a Durabletask scheduler."""
12+
13+
@classmethod
14+
def _build_arguments_schema(cls, *args, **kwargs):
15+
"""Define custom arguments for the command."""
16+
# Call the parent class method to initialize the argument schema
17+
cls.args_schema = super()._build_arguments_schema(*args, **kwargs)
18+
19+
# Remove the registration of the retention policies schema so it doesn't show up in the CLI help
20+
if hasattr(cls._args_schema.retention_policies, '_registered'):
21+
setattr(cls._args_schema.retention_policies, '_registered', False)
22+
_args_schema = cls._args_schema
23+
24+
# Define retention policy arguments with their CLI options and descriptions
25+
retention_args = {
26+
"default_days": ("--default-days", "-d", "The number of days to retain orchestrations."),
27+
"canceled_days": ("--canceled-days", "-x", "The number of days to retain canceled orchestrations."),
28+
"completed_days": ("--completed-days", "-c", "The number of days to retain completed orchestrations."),
29+
"failed_days": ("--failed-days", "-f", "The number of days to retain failed orchestrations."),
30+
"terminated_days": ("--terminated-days", "-t", "The number of days to retain terminated orchestrations."),
31+
}
32+
33+
# Add each retention argument to the schema
34+
for arg_name, (option, short_option, help_text) in retention_args.items():
35+
setattr(_args_schema, arg_name, AAZStrArg(
36+
arg_group="Properties", # Group these arguments under "Properties"
37+
options=[option, short_option], # CLI options for the argument
38+
help=help_text, # Description of the argument
39+
required=False, # These arguments are optional
40+
))
41+
42+
return _args_schema
43+
44+
def pre_operations(self):
45+
"""Prepare retention policies before executing the operation."""
46+
47+
if not any([
48+
self.ctx.args.default_days,
49+
self.ctx.args.canceled_days,
50+
self.ctx.args.completed_days,
51+
self.ctx.args.failed_days,
52+
self.ctx.args.terminated_days
53+
]):
54+
raise ValueError("At least one retention period (e.g., --default-days, --canceled-days) must be specified.")
55+
56+
# Build the retention policies based on the provided arguments
57+
self.ctx.args.retention_policies = _build_retention_policies({
58+
key: value for key, value in {
59+
'default_days': self.ctx.args.default_days,
60+
'canceled_days': self.ctx.args.canceled_days,
61+
'completed_days': self.ctx.args.completed_days,
62+
'failed_days': self.ctx.args.failed_days,
63+
'terminated_days': self.ctx.args.terminated_days
64+
}.items() if value is not None # Only include arguments that are not None
65+
})
66+
67+
68+
def _build_retention_policies(args_dict):
69+
"""Build a list of retention policies based on the provided arguments."""
70+
retention_policies = []
71+
72+
def to_int(value):
73+
"""Convert a value to an integer, handling serialization."""
74+
try:
75+
return int(value.to_serialized_data()) # Convert serialized data to an integer
76+
except (ValueError, TypeError):
77+
return None # Return None if conversion fails
78+
79+
# Add default retention policy if specified
80+
default_days = args_dict.get('default_days')
81+
if default_days is not None:
82+
days = to_int(default_days)
83+
if days is not None:
84+
# Add a default retention policy with the specified number of days
85+
retention_policies.append({"retentionPeriodInDays": days})
86+
87+
# Define a mapping of argument keys to orchestration states
88+
state_mapping = {
89+
'canceled_days': 'Canceled',
90+
'completed_days': 'Completed',
91+
'failed_days': 'Failed',
92+
'terminated_days': 'Terminated',
93+
}
94+
95+
# Add state-specific retention policies
96+
for arg_key, state in state_mapping.items():
97+
days_arg = args_dict.get(arg_key)
98+
if days_arg is not None:
99+
days = to_int(days_arg)
100+
if days is not None:
101+
# Add a retention policy for the specific orchestration state
102+
retention_policies.append({
103+
"retentionPeriodInDays": days,
104+
"orchestrationState": state
105+
})
106+
107+
return retention_policies

0 commit comments

Comments
 (0)