Skip to content

Commit e828960

Browse files
authored
Miscellaneous improvements related to durable task hub (#4641)
1 parent 61d2882 commit e828960

9 files changed

+110
-101
lines changed

package-lock.json

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,9 +1483,9 @@
14831483
"@azure/storage-blob": "^12.5.0",
14841484
"@microsoft/vscode-azext-azureappservice": "^3.6.4",
14851485
"@microsoft/vscode-azext-azureappsettings": "^0.2.8",
1486-
"@microsoft/vscode-azext-azureutils": "^3.4.7",
1486+
"@microsoft/vscode-azext-azureutils": "^3.4.9",
14871487
"@microsoft/vscode-azext-utils": "^3.3.3",
1488-
"@microsoft/vscode-azureresources-api": "^2.0.4",
1488+
"@microsoft/vscode-azureresources-api": "^2.5.1",
14891489
"@microsoft/vscode-container-client": "^0.1.2",
14901490
"cross-fetch": "^4.0.0",
14911491
"escape-string-regexp": "^4.0.0",

src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { CodeAction, ConnectionKey, hostFileName } from '../../../../constants';
99
import { ext } from '../../../../extensionVariables';
1010
import { type IDTSTaskJson, type IHostJsonV2 } from '../../../../funcConfig/host';
1111
import { localize } from '../../../../localize';
12-
import { clientIdKey } from '../../../durableTaskScheduler/copySchedulerConnectionString';
1312
import { notifyFailedToConfigureHost } from '../notifyFailedToConfigureHost';
1413
import { setLocalSetting } from '../setConnectionSetting';
1514
import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
@@ -36,22 +35,15 @@ export class DTSConnectionSetSettingStep<T extends IDTSConnectionWizardContext |
3635
context.newDTSConnectionSettingKey = ConnectionKey.DTS;
3736
}
3837

39-
const newDTSConnectionSettingKey = nonNullProp(context, 'newDTSConnectionSettingKey');
40-
let newDTSConnectionSettingValue = nonNullProp(context, 'newDTSConnectionSettingValue');
41-
42-
// Todo: Move this to `DurableTaskSchedulerGetConnectionStep` when we upgrade the azure package for new identity logic
43-
if ((context as IDTSAzureConnectionWizardContext).managedIdentity) {
44-
newDTSConnectionSettingValue = newDTSConnectionSettingValue.replace(clientIdKey, (context as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? clientIdKey);
45-
}
46-
4738
if (context.action === CodeAction.Debug) {
48-
await setLocalSetting(context, newDTSConnectionSettingKey, newDTSConnectionSettingValue);
39+
await setLocalSetting(
40+
context,
41+
nonNullProp(context, 'newDTSConnectionSettingKey'),
42+
nonNullProp(context, 'newDTSConnectionSettingValue'),
43+
);
4944
} else {
5045
// No further action required
5146
}
52-
53-
context.newDTSConnectionSettingValue = newDTSConnectionSettingValue;
54-
context.valuesToMask.push(context.newDTSConnectionSettingValue);
5547
}
5648

5749
public shouldExecute(context: T): boolean {

src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DTSStartingResourcesLogStep.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,12 @@ const startingResourcesContext: string = 'startingResourcesLogStepItem';
1212

1313
export class DTSStartingResourcesLogStep<T extends IDTSAzureConnectionWizardContext> extends AzureWizardPromptStep<T> {
1414
public hideStepCount: boolean = true;
15-
protected hasLogged: boolean = false;
1615

1716
public async configureBeforePrompt(context: T): Promise<void> {
18-
if (this.hasLogged) {
19-
return;
20-
}
21-
2217
if (context.resourceGroup) {
2318
prependOrInsertAfterLastInfoChild(context,
2419
new ActivityChildItem({
20+
stepId: this.id,
2521
contextValue: createContextValue([startingResourcesContext, activityInfoContext]),
2622
label: localize('useResourceGroup', 'Use resource group "{0}"', context.resourceGroup.name),
2723
activityType: ActivityChildType.Info,
@@ -34,6 +30,7 @@ export class DTSStartingResourcesLogStep<T extends IDTSAzureConnectionWizardCont
3430
if (context.site) {
3531
prependOrInsertAfterLastInfoChild(context,
3632
new ActivityChildItem({
33+
stepId: this.id,
3734
label: localize('useFunctionApp', 'Use function app "{0}"', context.site.fullName),
3835
contextValue: createContextValue([startingResourcesContext, activityInfoContext]),
3936
activityType: ActivityChildType.Info,
@@ -46,6 +43,7 @@ export class DTSStartingResourcesLogStep<T extends IDTSAzureConnectionWizardCont
4643
if (context.dts) {
4744
prependOrInsertAfterLastInfoChild(context,
4845
new ActivityChildItem({
46+
stepId: this.id,
4947
label: localize('useDTS', 'Use durable task scheduler "{0}"', context.dts.name),
5048
contextValue: createContextValue([startingResourcesContext, activityInfoContext]),
5149
activityType: ActivityChildType.Info,
@@ -58,6 +56,7 @@ export class DTSStartingResourcesLogStep<T extends IDTSAzureConnectionWizardCont
5856
if (context.dtsHub) {
5957
prependOrInsertAfterLastInfoChild(context,
6058
new ActivityChildItem({
59+
stepId: this.id,
6160
label: localize('useDTSHub', 'Use durable task hub "{0}"', context.dtsHub.name),
6261
contextValue: createContextValue([startingResourcesContext, activityInfoContext]),
6362
activityType: ActivityChildType.Info,
@@ -74,8 +73,6 @@ export class DTSStartingResourcesLogStep<T extends IDTSAzureConnectionWizardCont
7473
if (context.newDTSHubConnectionSettingKey) {
7574
ext.outputChannel.appendLog(localize('dtsHubConnectionKey', 'Using DTS hub host connection key "{0}"', context.newDTSHubConnectionSettingKey));
7675
}
77-
78-
this.hasLogged = true;
7976
}
8077

8178
public async prompt(): Promise<void> {

src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { parseAzureResourceId } from '@microsoft/vscode-azext-azureutils';
7-
import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils';
6+
import { CommonRoleDefinitions, createAuthorizationManagementClient, createRoleId, parseAzureResourceId, RoleAssignmentExecuteStep, uiUtils, type Role } from '@microsoft/vscode-azext-azureutils';
7+
import { ActivityChildItem, ActivityChildType, activitySuccessContext, activitySuccessIcon, AzureWizardPromptStep, createContextValue, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils';
88
import { localSettingsDescription } from '../../../../../constants-nls';
9+
import { ext } from '../../../../../extensionVariables';
910
import { localize } from '../../../../../localize';
1011
import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient';
12+
import { FunctionAppUserAssignedIdentitiesListStep } from '../../../../identity/FunctionAppUserAssignedIdentitiesListStep';
1113
import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext';
1214
import { DurableTaskHubCreateStep } from './DurableTaskHubCreateStep';
1315
import { DurableTaskHubNameStep } from './DurableTaskHubNameStep';
@@ -22,7 +24,7 @@ export class DurableTaskHubListStep<T extends IDTSAzureConnectionWizardContext>
2224

2325
public async prompt(context: T): Promise<void> {
2426
context.dtsHub = (await context.ui.showQuickPick(await this.getPicks(context), {
25-
placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'),
27+
placeHolder: localize('selectTaskScheduler', 'Select a durable task hub'),
2628
})).data;
2729

2830
if (context.dtsHub) {
@@ -35,17 +37,6 @@ export class DurableTaskHubListStep<T extends IDTSAzureConnectionWizardContext>
3537
return !context.dtsHub;
3638
}
3739

38-
public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
39-
if (context.dtsHub) {
40-
return undefined;
41-
}
42-
43-
return {
44-
promptSteps: [new DurableTaskHubNameStep(this.schedulerClient)],
45-
executeSteps: [new DurableTaskHubCreateStep(this.schedulerClient)],
46-
};
47-
}
48-
4940
private async getPicks(context: T): Promise<IAzureQuickPickItem<DurableTaskHubResource | undefined>[]> {
5041
const taskHubs: DurableTaskHubResource[] = context.dts ?
5142
await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), parseAzureResourceId(context.dts.id).resourceGroup, context.dts.name) : [];
@@ -66,4 +57,80 @@ export class DurableTaskHubListStep<T extends IDTSAzureConnectionWizardContext>
6657
}),
6758
];
6859
}
60+
61+
public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
62+
const promptSteps: AzureWizardPromptStep<T>[] = [];
63+
const executeSteps: AzureWizardExecuteStep<T>[] = [];
64+
65+
if (!context.dtsHub) {
66+
promptSteps.push(new DurableTaskHubNameStep(this.schedulerClient));
67+
executeSteps.push(new DurableTaskHubCreateStep(this.schedulerClient));
68+
}
69+
70+
const dtsContributorRole: Role = {
71+
scopeId: context.dtsHub?.id,
72+
roleDefinitionId: createRoleId(context.subscriptionId, CommonRoleDefinitions.durableTaskDataContributor),
73+
roleDefinitionName: CommonRoleDefinitions.durableTaskDataContributor.roleName,
74+
};
75+
76+
promptSteps.push(new FunctionAppUserAssignedIdentitiesListStep(dtsContributorRole /** targetRole */, { identityAssignStepPriority: 180 }));
77+
executeSteps.push(new RoleAssignmentExecuteStep(this.getDTSRoleAssignmentCallback(context, dtsContributorRole), { priority: 190 }));
78+
79+
return { promptSteps, executeSteps };
80+
}
81+
82+
private getDTSRoleAssignmentCallback(context: T, role: Role): () => Promise<Role[]> {
83+
return async () => {
84+
const roleAssignment: Role = {
85+
...role,
86+
// This id may be missing when the role is initially passed in,
87+
// but by the time we run the step, we should have the populated id ready.
88+
scopeId: context.dtsHub?.id,
89+
};
90+
91+
if (!roleAssignment.scopeId) {
92+
return [];
93+
}
94+
95+
const amClient = await createAuthorizationManagementClient(context);
96+
97+
let hasRoleAssignment: boolean = false;
98+
if (context.dtsHub) {
99+
const taskHubRoleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope(
100+
context.dtsHub.id,
101+
{
102+
// $filter=principalId eq {id}
103+
filter: `principalId eq '{${context.managedIdentity?.principalId}}'`,
104+
}
105+
));
106+
hasRoleAssignment = taskHubRoleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId));
107+
}
108+
109+
if (!hasRoleAssignment && context.dts) {
110+
const taskSchedulerRoleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope(
111+
context.dts.id,
112+
{
113+
// $filter=principalId eq {id}
114+
filter: `principalId eq '{${context.managedIdentity?.principalId}}'`,
115+
}
116+
));
117+
hasRoleAssignment = taskSchedulerRoleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId));
118+
}
119+
120+
if (hasRoleAssignment) {
121+
context.activityChildren?.push(
122+
new ActivityChildItem({
123+
label: localize('verifyIdentityWithRoleLabel', 'Verify identity "{0}" has role "{1}"', context.managedIdentity?.name, role.roleDefinitionName),
124+
description: '0s',
125+
contextValue: createContextValue(['roleAssignmentExecuteStepItem', activitySuccessContext]),
126+
activityType: ActivityChildType.Success,
127+
iconPath: activitySuccessIcon,
128+
}),
129+
);
130+
ext.outputChannel.appendLog(localize('verifyIdentity', 'Successfully verified identity "{0}" has role "{1}".', context.managedIdentity?.name, role.roleDefinitionName));
131+
}
132+
133+
return hasRoleAssignment ? [] : [roleAssignment];
134+
};
135+
}
69136
}

src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class DurableTaskHubNameStep<T extends IDTSAzureConnectionWizardContext>
2020
public async prompt(context: T): Promise<void> {
2121
context.newDTSHubName = (await context.ui.showInputBox({
2222
prompt: localize('taskSchedulerName', 'Enter a name for the durable task hub'),
23-
value: context.suggestedDTSHubNameLocalSettings,
23+
value: context.suggestedDTSHubNameLocalSettings ?? 'default',
2424
validateInput: this.validateInput,
2525
asyncValidationTask: (name: string) => this.validateNameAvailable(context, name),
2626
})).trim();

src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerGetConnectionStep.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
7-
import { getSchedulerConnectionString, SchedulerAuthenticationType } from '../../../../durableTaskScheduler/copySchedulerConnectionString';
7+
import { clientIdKey, getSchedulerConnectionString, SchedulerAuthenticationType } from '../../../../durableTaskScheduler/copySchedulerConnectionString';
88
import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext';
99

1010
export class DurableTaskSchedulerGetConnectionStep<T extends IDTSAzureConnectionWizardContext> extends AzureWizardExecuteStep<T> {
1111
public priority: number = 200;
1212

1313
public async execute(context: T): Promise<void> {
1414
context.newDTSConnectionSettingValue = getSchedulerConnectionString(context.dts?.properties.endpoint ?? '', SchedulerAuthenticationType.UserAssignedIdentity);
15+
16+
if (context.managedIdentity) {
17+
context.newDTSConnectionSettingValue = context.newDTSConnectionSettingValue.replace(clientIdKey, context.managedIdentity?.clientId ?? clientIdKey);
18+
}
19+
1520
context.valuesToMask.push(context.newDTSConnectionSettingValue);
1621
}
1722

src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { CommonRoleDefinitions, createAuthorizationManagementClient, createRoleId, LocationListStep, parseAzureResourceId, RoleAssignmentExecuteStep, uiUtils, type ILocationWizardContext, type Role } from '@microsoft/vscode-azext-azureutils';
6+
import { LocationListStep, parseAzureResourceId, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils';
77
import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils';
88
import { localSettingsDescription } from '../../../../../constants-nls';
99
import { localize } from '../../../../../localize';
1010
import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient';
11-
import { FunctionAppUserAssignedIdentitiesListStep } from '../../../../identity/FunctionAppUserAssignedIdentitiesListStep';
1211
import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext';
1312
import { DurableTaskHubListStep } from './DurableTaskHubListStep';
1413
import { DurableTaskSchedulerCreateStep } from './DurableTaskSchedulerCreateStep';
@@ -52,43 +51,7 @@ export class DurableTaskSchedulerListStep<T extends IDTSAzureConnectionWizardCon
5251
promptSteps.push(new DurableTaskHubListStep(this._schedulerClient));
5352
}
5453

55-
const dtsContributorRole: Role = {
56-
scopeId: context.dts?.id,
57-
roleDefinitionId: createRoleId(context.subscriptionId, CommonRoleDefinitions.durableTaskDataContributor),
58-
roleDefinitionName: CommonRoleDefinitions.durableTaskDataContributor.roleName,
59-
};
60-
61-
promptSteps.push(new FunctionAppUserAssignedIdentitiesListStep(dtsContributorRole /** targetRole */, { identityAssignStepPriority: 180 }));
62-
executeSteps.push(new RoleAssignmentExecuteStep(getDTSRoleAssignmentCallback(context, dtsContributorRole), { priority: 190 }));
63-
6454
return { promptSteps, executeSteps };
65-
66-
function getDTSRoleAssignmentCallback(context: T, role: Role): () => Promise<Role[]> {
67-
return async () => {
68-
const roleAssignment: Role = {
69-
...role,
70-
// This id may be missing when the role is initially passed in,
71-
// but by the time we run the step, we should have the populated id ready.
72-
scopeId: context.dts?.id,
73-
};
74-
75-
if (!roleAssignment.scopeId) {
76-
return [];
77-
}
78-
79-
const amClient = await createAuthorizationManagementClient(context);
80-
const roleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope(
81-
roleAssignment.scopeId,
82-
{
83-
// $filter=principalId eq {id}
84-
filter: `principalId eq '{${context.managedIdentity?.principalId}}'`,
85-
}
86-
));
87-
88-
const hasRoleAssignment = roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId));
89-
return hasRoleAssignment ? [] : [roleAssignment];
90-
};
91-
}
9255
}
9356

9457
private async getPicks(context: T): Promise<IAzureQuickPickItem<DurableTaskSchedulerResource | undefined>[]> {

0 commit comments

Comments
 (0)