diff --git a/package-lock.json b/package-lock.json index 895a3ae96..d9a05e3ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,9 +23,9 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.4", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.4.7", + "@microsoft/vscode-azext-azureutils": "^3.4.8", "@microsoft/vscode-azext-utils": "^3.3.3", - "@microsoft/vscode-azureresources-api": "^2.0.4", + "@microsoft/vscode-azureresources-api": "^2.5.1", "@microsoft/vscode-container-client": "^0.1.2", "cross-fetch": "^4.0.0", "escape-string-regexp": "^4.0.0", @@ -1235,9 +1235,9 @@ } }, "node_modules/@microsoft/vscode-azext-azureutils": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.4.7.tgz", - "integrity": "sha512-HooK1gk+1Gu5hcCuGr+z3CHHs06g79adP9pPHzRhLeLlBtsfbeaiwTMKuS0xqlmc9eu7+VXvfRkMExnqMU2gqw==", + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.4.8.tgz", + "integrity": "sha512-QGtAO3r1KwSItF0Ep6dG5bJnDkIDGt6loo4Lpq/pmz/J4l0BQUcnsma6ghiWYNFOYyuV9xD5XzPu9vOpEt8c+A==", "license": "MIT", "dependencies": { "@azure/arm-authorization": "^9.0.0", @@ -1476,9 +1476,10 @@ } }, "node_modules/@microsoft/vscode-azureresources-api": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azureresources-api/-/vscode-azureresources-api-2.4.0.tgz", - "integrity": "sha512-rYA7EiZ/XDX6C2LgKXjy84zgjnWRGPHSoYcV+J7WAo42qEZJiwp9wT/N3qtOjzYkmdvzG0TU/UKC0Hi+O8qxkw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azureresources-api/-/vscode-azureresources-api-2.5.1.tgz", + "integrity": "sha512-CUlDVsau6RJA8F1IENnfA3N0XqyGQz/VJgh9QLnWjRkiBjW8/VSUpxNab6qflnN9SS38EHGv81v/92kEkoOQKQ==", + "license": "MIT", "peerDependencies": { "@azure/ms-rest-azure-env": "^2.0.0" } diff --git a/package.json b/package.json index afb509475..60792578e 100644 --- a/package.json +++ b/package.json @@ -1483,9 +1483,9 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.4", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.4.7", + "@microsoft/vscode-azext-azureutils": "^3.4.8", "@microsoft/vscode-azext-utils": "^3.3.3", - "@microsoft/vscode-azureresources-api": "^2.0.4", + "@microsoft/vscode-azureresources-api": "^2.5.1", "@microsoft/vscode-container-client": "^0.1.2", "cross-fetch": "^4.0.0", "escape-string-regexp": "^4.0.0", diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DTSStartingResourcesLogStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DTSStartingResourcesLogStep.ts index 8a8c207c7..4ce232a07 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DTSStartingResourcesLogStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DTSStartingResourcesLogStep.ts @@ -12,16 +12,12 @@ const startingResourcesContext: string = 'startingResourcesLogStepItem'; export class DTSStartingResourcesLogStep extends AzureWizardPromptStep { public hideStepCount: boolean = true; - protected hasLogged: boolean = false; public async configureBeforePrompt(context: T): Promise { - if (this.hasLogged) { - return; - } - if (context.resourceGroup) { prependOrInsertAfterLastInfoChild(context, new ActivityChildItem({ + stepId: this.id, contextValue: createContextValue([startingResourcesContext, activityInfoContext]), label: localize('useResourceGroup', 'Use resource group "{0}"', context.resourceGroup.name), activityType: ActivityChildType.Info, @@ -34,6 +30,7 @@ export class DTSStartingResourcesLogStep { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts index e00e9382a..bbf9ae10c 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { parseAzureResourceId } from '@microsoft/vscode-azext-azureutils'; -import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { CommonRoleDefinitions, createAuthorizationManagementClient, createRoleId, parseAzureResourceId, RoleAssignmentExecuteStep, uiUtils, type Role } from '@microsoft/vscode-azext-azureutils'; +import { ActivityChildItem, ActivityChildType, activitySuccessContext, activitySuccessIcon, AzureWizardPromptStep, createContextValue, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { localSettingsDescription } from '../../../../../constants-nls'; +import { ext } from '../../../../../extensionVariables'; import { localize } from '../../../../../localize'; import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; +import { FunctionAppUserAssignedIdentitiesListStep } from '../../../../identity/FunctionAppUserAssignedIdentitiesListStep'; import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext'; import { DurableTaskHubCreateStep } from './DurableTaskHubCreateStep'; import { DurableTaskHubNameStep } from './DurableTaskHubNameStep'; @@ -22,7 +24,7 @@ export class DurableTaskHubListStep public async prompt(context: T): Promise { context.dtsHub = (await context.ui.showQuickPick(await this.getPicks(context), { - placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'), + placeHolder: localize('selectTaskScheduler', 'Select a durable task hub'), })).data; if (context.dtsHub) { @@ -35,17 +37,6 @@ export class DurableTaskHubListStep return !context.dtsHub; } - public async getSubWizard(context: T): Promise | undefined> { - if (context.dtsHub) { - return undefined; - } - - return { - promptSteps: [new DurableTaskHubNameStep(this.schedulerClient)], - executeSteps: [new DurableTaskHubCreateStep(this.schedulerClient)], - }; - } - private async getPicks(context: T): Promise[]> { const taskHubs: DurableTaskHubResource[] = context.dts ? await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), parseAzureResourceId(context.dts.id).resourceGroup, context.dts.name) : []; @@ -66,4 +57,65 @@ export class DurableTaskHubListStep }), ]; } + + public async getSubWizard(context: T): Promise | undefined> { + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; + + if (!context.dtsHub) { + promptSteps.push(new DurableTaskHubNameStep(this.schedulerClient)); + executeSteps.push(new DurableTaskHubCreateStep(this.schedulerClient)); + } + + const dtsContributorRole: Role = { + scopeId: context.dtsHub?.id, + roleDefinitionId: createRoleId(context.subscriptionId, CommonRoleDefinitions.durableTaskDataContributor), + roleDefinitionName: CommonRoleDefinitions.durableTaskDataContributor.roleName, + }; + + promptSteps.push(new FunctionAppUserAssignedIdentitiesListStep(dtsContributorRole /** targetRole */, { identityAssignStepPriority: 180 })); + executeSteps.push(new RoleAssignmentExecuteStep(this.getDTSRoleAssignmentCallback(context, dtsContributorRole), { priority: 190 })); + + return { promptSteps, executeSteps }; + } + + private getDTSRoleAssignmentCallback(context: T, role: Role): () => Promise { + return async () => { + const roleAssignment: Role = { + ...role, + // This id may be missing when the role is initially passed in, + // but by the time we run the step, we should have the populated id ready. + scopeId: context.dtsHub?.id, + }; + + if (!roleAssignment.scopeId) { + return []; + } + + const amClient = await createAuthorizationManagementClient(context); + const roleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope( + roleAssignment.scopeId, + { + // $filter=principalId eq {id} + filter: `principalId eq '{${context.managedIdentity?.principalId}}'`, + } + )); + + const hasRoleAssignment = roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId)); + if (hasRoleAssignment) { + context.activityChildren?.push( + new ActivityChildItem({ + label: localize('verifyIdentityWithRoleLabel', 'Verify identity "{0}" has role "{1}"', context.managedIdentity?.name, role.roleDefinitionName), + description: '0s', + contextValue: createContextValue(['roleAssignmentExecuteStepItem', activitySuccessContext]), + activityType: ActivityChildType.Success, + iconPath: activitySuccessIcon, + }), + ); + ext.outputChannel.appendLog(localize('verifyIdentity', 'Successfully verified identity "{0}" has role "{1}".', context.managedIdentity?.name, role.roleDefinitionName)); + } + + return hasRoleAssignment ? [] : [roleAssignment]; + }; + } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index 93f8f186f..18c7f37bb 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -20,7 +20,7 @@ export class DurableTaskHubNameStep public async prompt(context: T): Promise { context.newDTSHubName = (await context.ui.showInputBox({ prompt: localize('taskSchedulerName', 'Enter a name for the durable task hub'), - value: context.suggestedDTSHubNameLocalSettings, + value: context.suggestedDTSHubNameLocalSettings ?? 'default', validateInput: this.validateInput, asyncValidationTask: (name: string) => this.validateNameAvailable(context, name), })).trim(); diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index 94cdf0406..7b061a205 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommonRoleDefinitions, createAuthorizationManagementClient, createRoleId, LocationListStep, parseAzureResourceId, RoleAssignmentExecuteStep, uiUtils, type ILocationWizardContext, type Role } from '@microsoft/vscode-azext-azureutils'; +import { LocationListStep, parseAzureResourceId, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { localSettingsDescription } from '../../../../../constants-nls'; import { localize } from '../../../../../localize'; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; -import { FunctionAppUserAssignedIdentitiesListStep } from '../../../../identity/FunctionAppUserAssignedIdentitiesListStep'; import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext'; import { DurableTaskHubListStep } from './DurableTaskHubListStep'; import { DurableTaskSchedulerCreateStep } from './DurableTaskSchedulerCreateStep'; @@ -52,43 +51,7 @@ export class DurableTaskSchedulerListStep Promise { - return async () => { - const roleAssignment: Role = { - ...role, - // This id may be missing when the role is initially passed in, - // but by the time we run the step, we should have the populated id ready. - scopeId: context.dts?.id, - }; - - if (!roleAssignment.scopeId) { - return []; - } - - const amClient = await createAuthorizationManagementClient(context); - const roleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope( - roleAssignment.scopeId, - { - // $filter=principalId eq {id} - filter: `principalId eq '{${context.managedIdentity?.principalId}}'`, - } - )); - - const hasRoleAssignment = roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId)); - return hasRoleAssignment ? [] : [roleAssignment]; - }; - } } private async getPicks(context: T): Promise[]> { diff --git a/src/commands/identity/FunctionAppUserAssignedIdentitiesListStep.ts b/src/commands/identity/FunctionAppUserAssignedIdentitiesListStep.ts index 045bc0bb4..8e06b8744 100644 --- a/src/commands/identity/FunctionAppUserAssignedIdentitiesListStep.ts +++ b/src/commands/identity/FunctionAppUserAssignedIdentitiesListStep.ts @@ -6,8 +6,7 @@ import { type ManagedServiceIdentityClient } from '@azure/arm-msi'; import { type ParsedSite } from '@microsoft/vscode-azext-azureappservice'; import { createAuthorizationManagementClient, createManagedServiceIdentityClient, parseAzureResourceId, uiUtils, UserAssignedIdentityListStep, type ParsedAzureResourceId, type Role } from '@microsoft/vscode-azext-azureutils'; -import { ActivityChildItem, ActivityChildType, activityInfoContext, activityInfoIcon, AzureWizardPromptStep, createContextValue, nonNullProp, prependOrInsertAfterLastInfoChild, type ActivityInfoChild, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; -import { ext } from '../../extensionVariables'; +import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { localize } from '../../localize'; import { type ManagedIdentityAssignContext } from './ManagedIdentityAssignContext'; import { ManagedIdentityAssignStep } from './ManagedIdentityAssignStep'; @@ -65,21 +64,6 @@ export class FunctionAppUserAssignedIdentitiesListStep {