From f858697998ab3845209c4cbfd7b6a4d07dc4a7d9 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 27 May 2025 15:38:20 -0700 Subject: [PATCH 01/52] Add new storage type pick and host/local settings logic --- .../createFunction/IFunctionWizardContext.ts | 4 ++-- .../durableSteps/DurableProjectConfigureStep.ts | 7 +++++++ .../DurableStorageTypePromptStep.ts | 17 ++++++----------- src/constants.ts | 5 ++++- src/funcConfig/host.ts | 11 ++++++++++- src/utils/durableUtils.ts | 16 +++++++++++++--- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/commands/createFunction/IFunctionWizardContext.ts b/src/commands/createFunction/IFunctionWizardContext.ts index d49051e4e..1ced9585f 100644 --- a/src/commands/createFunction/IFunctionWizardContext.ts +++ b/src/commands/createFunction/IFunctionWizardContext.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type ExecuteActivityContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { type DurableBackendValues } from "../../constants"; +import { type DurableBackend } from "../../constants"; import { type BindingSettingValue } from "../../funcConfig/function"; import { type IBindingSetting } from "../../templates/IBindingTemplate"; import { type FunctionTemplateBase } from "../../templates/IFunctionTemplate"; @@ -17,7 +17,7 @@ export interface IFunctionWizardContext extends Partial, I // Durable Functions hasDurableStorage?: boolean; - newDurableStorageType?: DurableBackendValues; + newDurableStorageType?: DurableBackend; useStorageEmulator?: boolean; } diff --git a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts index 6de420874..52f5edf16 100644 --- a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts +++ b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts @@ -57,6 +57,8 @@ export class DurableProjectConfigureStep exten } }); + // Todo: We should probably throw a non-blocking error here with custom fail state logic + return; } @@ -72,6 +74,11 @@ export class DurableProjectConfigureStep exten hostJson.extensions.durableTask = durableUtils.getDefaultNetheriteTaskConfig(); await setLocalAppSetting(context, context.projectPath, ConnectionKey.EventHubs, '', MismatchBehavior.Overwrite); break; + case DurableBackend.DTS: + hostJson.extensions.durableTask = durableUtils.getDefaultDTSTaskConfig(); + await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTS, '', MismatchBehavior.Overwrite); + await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTSHub, 'default', MismatchBehavior.Overwrite); + break; case DurableBackend.SQL: hostJson.extensions.durableTask = durableUtils.getDefaultSqlTaskConfig(); await setLocalAppSetting(context, context.projectPath, ConnectionKey.SQL, '', MismatchBehavior.Overwrite); diff --git a/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts b/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts index 2402abb3b..162425195 100644 --- a/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts +++ b/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts @@ -5,7 +5,7 @@ import { AzureWizardPromptStep, openUrl, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils"; import { DurableBackend, type DurableBackendValues } from "../../../constants"; -import { defaultDescription } from "../../../constants-nls"; +import { defaultDescription, previewDescription } from "../../../constants-nls"; import { localize } from "../../../localize"; import { FunctionSubWizard } from "../FunctionSubWizard"; import { type IFunctionWizardContext } from "../IFunctionWizardContext"; @@ -19,24 +19,19 @@ export class DurableStorageTypePromptStep exte } public async prompt(context: T): Promise { - const durableStorageLabels: string[] = [ - 'Azure Storage', - 'Netherite', - 'MSSQL' - ]; const durableStorageInfo: string = localize('durableStorageInfo', '$(link-external) Learn more about the tradeoffs between storage providers'); const placeHolder: string = localize('chooseDurableStorageType', 'Choose a durable storage type.'); const picks: IAzureQuickPickItem[] = [ - { label: durableStorageLabels[0], description: defaultDescription, data: DurableBackend.Storage, suppressPersistence: true }, - { label: durableStorageLabels[1], data: DurableBackend.Netherite, suppressPersistence: true }, - { label: durableStorageLabels[2], data: DurableBackend.SQL, suppressPersistence: true }, - { label: durableStorageInfo, data: undefined, suppressPersistence: true } + { label: 'Azure Storage', description: defaultDescription, data: DurableBackend.Storage }, + { label: 'Durable Task Scheduler', description: previewDescription, data: DurableBackend.DTS }, + { label: 'MSSQL', data: DurableBackend.SQL }, + { label: durableStorageInfo, data: undefined } ]; let pick: DurableBackendValues | undefined; while (!pick) { - pick = (await context.ui.showQuickPick(picks, { placeHolder })).data; + pick = (await context.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true })).data; if (!pick) { await openUrl('https://aka.ms/durable-storage-providers'); } diff --git a/src/constants.ts b/src/constants.ts index 2b0d2a86e..83298e31f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -99,6 +99,8 @@ export enum ConnectionKey { Storage = 'AzureWebJobsStorage', StorageIdentity = 'AzureWebJobsStorage__accountName', EventHubs = 'EventHubsConnection', + DTS = 'DURABLE_TASK_SCHEDULER_CONNECTION_STRING', + DTSHub = 'TASKHUB_NAME', SQL = 'SQLDB_Connection' } @@ -120,7 +122,8 @@ export enum ConnectionType { export enum DurableBackend { Storage = 'AzureStorage', Netherite = 'Netherite', - SQL = "mssql" + DTS = 'azureManaged', + SQL = 'mssql', } export type ConnectionTypeValues = typeof ConnectionType[keyof typeof ConnectionType]; diff --git a/src/funcConfig/host.ts b/src/funcConfig/host.ts index 980b65ea2..83da77b99 100644 --- a/src/funcConfig/host.ts +++ b/src/funcConfig/host.ts @@ -34,7 +34,7 @@ export interface IHostJsonV2 { }; } -export type IDurableTaskJson = IStorageTaskJson | INetheriteTaskJson | ISqlTaskJson; +export type IDurableTaskJson = IStorageTaskJson | INetheriteTaskJson | IDTSTaskJson | ISqlTaskJson; export interface IStorageTaskJson { storageProvider?: { @@ -53,6 +53,15 @@ export interface INetheriteTaskJson { } } +export interface IDTSTaskJson { + hubName?: string; + storageProvider?: { + type?: DurableBackend.DTS; + connectionStringName?: string; + StorageConnectionName?: string; + } +} + export interface ISqlTaskJson { storageProvider?: { type?: DurableBackend.SQL; diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index 644a1813e..5579026b1 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -10,7 +10,7 @@ import * as xml2js from "xml2js"; import { type IFunctionWizardContext } from "../commands/createFunction/IFunctionWizardContext"; import { ConnectionKey, DurableBackend, ProjectLanguage, hostFileName, requirementsFileName, type DurableBackendValues } from "../constants"; import { ext } from "../extensionVariables"; -import { type IHostJsonV2, type INetheriteTaskJson, type ISqlTaskJson, type IStorageTaskJson } from "../funcConfig/host"; +import { type IDTSTaskJson, type IHostJsonV2, type INetheriteTaskJson, type ISqlTaskJson, type IStorageTaskJson } from "../funcConfig/host"; import { localize } from "../localize"; import { cpUtils } from "./cpUtils"; import { dotnetUtils } from "./dotnetUtils"; @@ -215,9 +215,9 @@ export namespace durableUtils { }; } - export function getDefaultNetheriteTaskConfig(hubName?: string): INetheriteTaskJson { + export function getDefaultNetheriteTaskConfig(hubName: string = ''): INetheriteTaskJson { return { - hubName: hubName || '', + hubName, useGracefulShutdown: true, storageProvider: { type: DurableBackend.Netherite, @@ -228,6 +228,16 @@ export namespace durableUtils { }; } + export function getDefaultDTSTaskConfig(hubName: string = '%TASKHUB_NAME%'): IDTSTaskJson { + return { + hubName, + storageProvider: { + type: DurableBackend.DTS, + connectionStringName: ConnectionKey.DTS, + } + }; + } + export function getDefaultSqlTaskConfig(): ISqlTaskJson { return { storageProvider: { From c84b288e8eb1f098090f17f6212eb0074c271f64 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 27 May 2025 16:07:53 -0700 Subject: [PATCH 02/52] Update host.json preview dep --- .../durableSteps/DurableProjectConfigureStep.ts | 9 ++++++++- src/utils/durableUtils.ts | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts index 52f5edf16..b362393c3 100644 --- a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts +++ b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts @@ -6,7 +6,7 @@ import { AzExtFsExtra, AzureWizardExecuteStepWithActivityOutput } from '@microsoft/vscode-azext-utils'; import * as path from "path"; import { type Progress } from 'vscode'; -import { ConnectionKey, DurableBackend, hostFileName } from '../../../constants'; +import { ConnectionKey, DurableBackend, hostFileName, ProjectLanguage } from '../../../constants'; import { viewOutput } from '../../../constants-nls'; import { ext } from '../../../extensionVariables'; import { type IHostJsonV2 } from '../../../funcConfig/host'; @@ -76,6 +76,13 @@ export class DurableProjectConfigureStep exten break; case DurableBackend.DTS: hostJson.extensions.durableTask = durableUtils.getDefaultDTSTaskConfig(); + // Non- .NET projects require a special preview extension bundle to work properly + // Todo: Remove once this functionality is out of preview + if (context.language !== ProjectLanguage.CSharp) { + hostJson.extensionBundle ??= {}; + hostJson.extensionBundle.id = 'Microsoft.Azure.Functions.ExtensionBundle.Preview'; + hostJson.extensionBundle.version = '[4.29.0, 5.0.0)'; + } await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTS, '', MismatchBehavior.Overwrite); await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTSHub, 'default', MismatchBehavior.Overwrite); break; diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index 5579026b1..2b420ebcc 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -228,9 +228,9 @@ export namespace durableUtils { }; } - export function getDefaultDTSTaskConfig(hubName: string = '%TASKHUB_NAME%'): IDTSTaskJson { + export function getDefaultDTSTaskConfig(): IDTSTaskJson { return { - hubName, + hubName: '%TASKHUB_NAME%', storageProvider: { type: DurableBackend.DTS, connectionStringName: ConnectionKey.DTS, From eb005a970a3c2c3d38cc4a7d91a78161924fabc8 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 27 May 2025 16:23:37 -0700 Subject: [PATCH 03/52] Install deps --- .../durableSteps/DurableProjectConfigureStep.ts | 2 +- src/utils/durableUtils.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts index b362393c3..78dd5bfdd 100644 --- a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts +++ b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts @@ -78,7 +78,7 @@ export class DurableProjectConfigureStep exten hostJson.extensions.durableTask = durableUtils.getDefaultDTSTaskConfig(); // Non- .NET projects require a special preview extension bundle to work properly // Todo: Remove once this functionality is out of preview - if (context.language !== ProjectLanguage.CSharp) { + if (context.language !== ProjectLanguage.CSharp && context.language !== ProjectLanguage.FSharp) { hostJson.extensionBundle ??= {}; hostJson.extensionBundle.id = 'Microsoft.Azure.Functions.ExtensionBundle.Preview'; hostJson.extensionBundle.version = '[4.29.0, 5.0.0)'; diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index 2b420ebcc..eb013f943 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -24,6 +24,8 @@ export namespace durableUtils { export const dotnetIsolatedDfSqlPackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer'; export const dotnetInProcDfNetheritePackage: string = 'Microsoft.Azure.DurableTask.Netherite.AzureFunctions'; export const dotnetIsolatedDfNetheritePackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite'; + export const dotnetInProcDTSPackage: string = 'Microsoft.Azure.DurableTask.DurableTask.AzureFunctions'; + export const dotnetIsolatedDTSPackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged'; export const dotnetInProcDfBasePackage: string = 'Microsoft.Azure.WebJobs.Extensions.DurableTask'; export const nodeDfPackage: string = 'durable-functions'; export const pythonDfPackage: string = 'azure-functions-durable'; @@ -159,6 +161,11 @@ export namespace durableUtils { packageNames.push(dotnetIsolatedDfNetheritePackage) : packageNames.push(dotnetInProcDfNetheritePackage); break; + case DurableBackend.DTS: + isDotnetIsolated ? + packageNames.push(dotnetIsolatedDTSPackage) : + packageNames.push(dotnetInProcDTSPackage); + break; case DurableBackend.SQL: isDotnetIsolated ? packageNames.push(dotnetIsolatedDfSqlPackage) : From cbe17070d1bd2ee1f3ca48ecb26025bf05c062ba Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 27 May 2025 16:25:32 -0700 Subject: [PATCH 04/52] Update extension bundle --- .../durableSteps/DurableProjectConfigureStep.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts index 78dd5bfdd..bb5769eab 100644 --- a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts +++ b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts @@ -79,9 +79,10 @@ export class DurableProjectConfigureStep exten // Non- .NET projects require a special preview extension bundle to work properly // Todo: Remove once this functionality is out of preview if (context.language !== ProjectLanguage.CSharp && context.language !== ProjectLanguage.FSharp) { - hostJson.extensionBundle ??= {}; - hostJson.extensionBundle.id = 'Microsoft.Azure.Functions.ExtensionBundle.Preview'; - hostJson.extensionBundle.version = '[4.29.0, 5.0.0)'; + hostJson.extensionBundle = { + id: 'Microsoft.Azure.Functions.ExtensionBundle.Preview', + version: '[4.29.0, 5.0.0)', + }; } await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTS, '', MismatchBehavior.Overwrite); await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTSHub, 'default', MismatchBehavior.Overwrite); From 4cb51ba2b875a69815ef26556ca683f28080a4e5 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 27 May 2025 16:55:52 -0700 Subject: [PATCH 05/52] Update durable detection logic --- src/utils/durableUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index eb013f943..bdeb22fe9 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -64,6 +64,8 @@ export namespace durableUtils { switch (hostStorageType) { case DurableBackend.Netherite: return DurableBackend.Netherite; + case DurableBackend.DTS: + return DurableBackend.DTS; case DurableBackend.SQL: return DurableBackend.SQL; case DurableBackend.Storage: From 5d5e2dd0141acb247af8d39b7b9e9569f618cb37 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 28 May 2025 18:35:30 -0700 Subject: [PATCH 06/52] Remove extra prop --- src/funcConfig/host.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/funcConfig/host.ts b/src/funcConfig/host.ts index 83da77b99..650601dc1 100644 --- a/src/funcConfig/host.ts +++ b/src/funcConfig/host.ts @@ -58,7 +58,6 @@ export interface IDTSTaskJson { storageProvider?: { type?: DurableBackend.DTS; connectionStringName?: string; - StorageConnectionName?: string; } } From 8cb081c645d3c62bcab07a31c81e10e3878cfae0 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 30 May 2025 11:27:56 -0700 Subject: [PATCH 07/52] Add .NET deps --- src/utils/durableUtils.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index bdeb22fe9..125d61d55 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -24,9 +24,9 @@ export namespace durableUtils { export const dotnetIsolatedDfSqlPackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer'; export const dotnetInProcDfNetheritePackage: string = 'Microsoft.Azure.DurableTask.Netherite.AzureFunctions'; export const dotnetIsolatedDfNetheritePackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite'; - export const dotnetInProcDTSPackage: string = 'Microsoft.Azure.DurableTask.DurableTask.AzureFunctions'; + export const dotnetInProcDTSPackage: string = 'Microsoft.Azure.WebJobs.Extensions.DurableTask.AzureManaged'; export const dotnetIsolatedDTSPackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged'; - export const dotnetInProcDfBasePackage: string = 'Microsoft.Azure.WebJobs.Extensions.DurableTask'; + export const dotnetInProcDfBasePackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask'; export const nodeDfPackage: string = 'durable-functions'; export const pythonDfPackage: string = 'azure-functions-durable'; @@ -154,44 +154,45 @@ export namespace durableUtils { } async function installDotnetDependencies(context: IFunctionWizardContext): Promise { - const packageNames: string[] = []; + const packages: { name: string; prerelease?: boolean }[] = []; const isDotnetIsolated: boolean = /Isolated/i.test(context.functionTemplate?.id ?? ''); switch (context.newDurableStorageType) { case DurableBackend.Netherite: isDotnetIsolated ? - packageNames.push(dotnetIsolatedDfNetheritePackage) : - packageNames.push(dotnetInProcDfNetheritePackage); + packages.push({ name: dotnetIsolatedDfNetheritePackage }) : + packages.push({ name: dotnetInProcDfNetheritePackage }); break; case DurableBackend.DTS: isDotnetIsolated ? - packageNames.push(dotnetIsolatedDTSPackage) : - packageNames.push(dotnetInProcDTSPackage); + packages.push({ name: dotnetIsolatedDTSPackage, prerelease: true }) : + packages.push({ name: dotnetInProcDTSPackage, prerelease: true }); break; case DurableBackend.SQL: isDotnetIsolated ? - packageNames.push(dotnetIsolatedDfSqlPackage) : - packageNames.push(dotnetInProcDfSqlPackage); + packages.push({ name: dotnetIsolatedDfSqlPackage }) : + packages.push({ name: dotnetInProcDfSqlPackage }); break; case DurableBackend.Storage: default: } - /* - * https://github.com/microsoft/vscode-azurefunctions/issues/3599 - * Seems that the package arrives out-dated and needs to be updated to at least 2.9.1; - * otherwise, error appears when running with sql backend - */ + // Although the templates should incorporate this package already, it is often included with an out-dated version + // which can cause issues right out of the gate if the package isn't updated if (!isDotnetIsolated) { - packageNames.push(dotnetInProcDfBasePackage); + packages.push({ name: dotnetInProcDfBasePackage }); } const failedPackages: string[] = []; - for (const packageName of packageNames) { + for (const p of packages) { try { - await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'dotnet', 'add', 'package', packageName); + const packageArgs: string[] = [p.name]; + if (p.prerelease) { + packageArgs.push('--prerelease'); + } + await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'dotnet', 'add', 'package', ...packageArgs); } catch { - failedPackages.push(packageName); + failedPackages.push(p.name); } } From ffb7b43c0955ffead42a01848119cdcc4c792dc2 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 30 May 2025 11:29:55 -0700 Subject: [PATCH 08/52] Fix accidental change --- src/utils/durableUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index 125d61d55..f0c6f0370 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -26,7 +26,7 @@ export namespace durableUtils { export const dotnetIsolatedDfNetheritePackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite'; export const dotnetInProcDTSPackage: string = 'Microsoft.Azure.WebJobs.Extensions.DurableTask.AzureManaged'; export const dotnetIsolatedDTSPackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged'; - export const dotnetInProcDfBasePackage: string = 'Microsoft.Azure.Functions.Worker.Extensions.DurableTask'; + export const dotnetInProcDfBasePackage: string = 'Microsoft.Azure.WebJobs.Extensions.DurableTask'; export const nodeDfPackage: string = 'durable-functions'; export const pythonDfPackage: string = 'azure-functions-durable'; From bd1c81586dd45efe2ecdb2e21fc3f0aa588ec7f2 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 30 May 2025 13:22:01 -0700 Subject: [PATCH 09/52] Revert todo --- .../createFunction/durableSteps/DurableProjectConfigureStep.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts index bb5769eab..0882ff765 100644 --- a/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts +++ b/src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts @@ -57,8 +57,6 @@ export class DurableProjectConfigureStep exten } }); - // Todo: We should probably throw a non-blocking error here with custom fail state logic - return; } From d89c079690b721c8c6e13af0a2239a2a95400571 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 30 May 2025 14:41:33 -0700 Subject: [PATCH 10/52] Add todo --- src/utils/durableUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index f0c6f0370..8ca3f9cec 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -164,6 +164,7 @@ export namespace durableUtils { packages.push({ name: dotnetInProcDfNetheritePackage }); break; case DurableBackend.DTS: + // Todo: Remove prerelease flag once this functionality is out of preview isDotnetIsolated ? packages.push({ name: dotnetIsolatedDTSPackage, prerelease: true }) : packages.push({ name: dotnetInProcDTSPackage, prerelease: true }); From e201fe5a18cdb891ccd502fc39670d6dd2fdfad2 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 30 May 2025 16:29:38 -0700 Subject: [PATCH 11/52] Update comment --- src/utils/durableUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index 8ca3f9cec..a9080d146 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -179,7 +179,7 @@ export namespace durableUtils { } // Although the templates should incorporate this package already, it is often included with an out-dated version - // which can cause issues right out of the gate if the package isn't updated + // which can lead to errors on first run. if (!isDotnetIsolated) { packages.push({ name: dotnetInProcDfBasePackage }); } From dece3e9a0fa46b2ce278e59b304016d57001d5e7 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:35:33 -0700 Subject: [PATCH 12/52] Update comment --- src/utils/durableUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index a9080d146..d25ba1865 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -179,7 +179,7 @@ export namespace durableUtils { } // Although the templates should incorporate this package already, it is often included with an out-dated version - // which can lead to errors on first run. + // which can lead to errors on first run. To improve this experience for our users, ensure that the latest version is used. if (!isDotnetIsolated) { packages.push({ name: dotnetInProcDfBasePackage }); } From 0e94b73cdd5d7a4fa80279c6411fc3540e8e5742 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:35:39 -0700 Subject: [PATCH 13/52] Fully working implementation --- .../AzureWebJobsStoragePromptStep.ts | 4 +- .../DTSConnectionCustomPromptStep.ts | 30 ++++++++ .../DTSConnectionSetSettingStep.ts | 22 ++++++ .../DTSConnectionTypeListStep.ts | 77 +++++++++++++++++++ .../DTSEmulatorStartStep.ts | 39 ++++++++++ .../DTSHubNameCustomPromptStep.ts | 39 ++++++++++ .../DTSHubNameSetSettingStep.ts | 22 ++++++ .../IDTSConnectionWizardContext.ts | 19 +++++ .../EventHubsConnectionPromptStep.ts | 4 +- .../copyEmulatorConnectionString.ts | 10 +-- .../durableTaskScheduler/createScheduler.ts | 4 +- .../durableTaskScheduler/startEmulator.ts | 4 +- src/commands/registerCommands.ts | 9 ++- .../durable/validateDTSConnectionPreDebug.ts | 60 +++++++++++++++ src/debug/validatePreDebug.ts | 10 ++- 15 files changed, 335 insertions(+), 18 deletions(-) create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionCustomPromptStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts create mode 100644 src/debug/durable/validateDTSConnectionPreDebug.ts diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts index 0752f3d3a..99904244e 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts @@ -36,7 +36,7 @@ export class AzureWebJobsStoragePromptStep { + public async configureBeforePrompt(context: T & { dtsConnectionType?: ConnectionType, eventHubsConnectionType?: EventHubsConnectionTypeValues, sqlDbConnectionType?: SqlDbConnectionTypeValues }): Promise { if (this.options?.preselectedConnectionType === ConnectionType.Azure || this.options?.preselectedConnectionType === ConnectionType.Emulator) { context.azureWebJobsStorageType = this.options.preselectedConnectionType; } else if (!!context.storageAccount || !!context.newStorageAccountName) { @@ -47,6 +47,8 @@ export class AzureWebJobsStoragePromptStep extends AzureWizardPromptStep { + public async prompt(context: T): Promise { + context.newDTSConnection = (await context.ui.showInputBox({ + prompt: localize('customDTSConnectionPrompt', 'Provide a custom DTS connection string.'), + validateInput: (value: string | undefined) => this.validateInput(value) + })).trim(); + } + + public shouldPrompt(context: T): boolean { + return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Custom; + } + + private validateInput(name: string | undefined): string | undefined { + name = name ? name.trim() : ''; + if (!validationUtils.hasValidCharLength(name)) { + return validationUtils.getInvalidCharLengthMessage(); + } + return undefined; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts new file mode 100644 index 000000000..61889dcb1 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { nonNullProp } from '@microsoft/vscode-azext-utils'; +import { ConnectionKey } from '../../../../constants'; +import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; +import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; + +export class DTSConnectionSetSettingStep extends SetConnectionSettingStepBase { + public priority: number = 240; + public debugDeploySetting: ConnectionKey = ConnectionKey.DTS; + + public async execute(context: T): Promise { + await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnection')); + } + + public shouldExecute(context: T): boolean { + return !!context.newDTSConnection; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts new file mode 100644 index 000000000..4a4a1cbb6 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, type AzureWizardExecuteStep, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { type MessageItem } from 'vscode'; +import { ConnectionType } from '../../../../constants'; +import { useEmulator } from '../../../../constants-nls'; +import { localize } from '../../../../localize'; +import { DTSConnectionCustomPromptStep } from './DTSConnectionCustomPromptStep'; +import { DTSConnectionSetSettingStep } from './DTSConnectionSetSettingStep'; +import { DTSEmulatorStartStep } from './DTSEmulatorStartStep'; +import { DTSHubNameCustomPromptStep } from './DTSHubNameCustomPromptStep'; +import { DTSHubNameSetSettingStep } from './DTSHubNameSetSettingStep'; +import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; + +export class DTSConnectionTypeListStep extends AzureWizardPromptStep { + constructor(readonly connectionTypes: Set) { + super(); + } + + public async prompt(context: T): Promise { + const connectAzureButton = { title: localize('connectAzureTaskScheduler', 'Connect Azure Task Scheduler'), data: ConnectionType.Azure }; + const connectEmulatorButton = { title: useEmulator, data: ConnectionType.Emulator }; + const connectCustomDTSButton = { title: localize('connectCustomTaskScheduler', 'Connect Custom Task Scheduler'), data: ConnectionType.Custom }; + + const buttons: MessageItem[] = []; + if (this.connectionTypes.has(ConnectionType.Azure)) { + buttons.push(connectAzureButton); + } + if (this.connectionTypes.has(ConnectionType.Emulator)) { + buttons.push(connectEmulatorButton); + } + if (this.connectionTypes.has(ConnectionType.Custom)) { + buttons.push(connectCustomDTSButton); + } + + const message: string = localize('selectDTSConnection', 'In order to proceed, you must connect a Durable Task Scheduler for internal use by the Azure Functions runtime.'); + context.dtsConnectionType = (await context.ui.showWarningMessage(message, { modal: true }, ...buttons) as { + title: string; + data: ConnectionType; + }).data; + + context.telemetry.properties.dtsConnectionType = context.dtsConnectionType; + } + + public shouldPrompt(context: T): boolean { + return !context.dtsConnectionType; + } + + public async getSubWizard(context: T): Promise | undefined> { + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; + + switch (context.dtsConnectionType) { + case ConnectionType.Azure: + throw new Error('Needs implementation.'); + case ConnectionType.Emulator: + executeSteps.push(new DTSEmulatorStartStep()); + break; + case ConnectionType.Custom: + promptSteps.push( + new DTSConnectionCustomPromptStep(), + new DTSHubNameCustomPromptStep(), + ); + break; + } + + executeSteps.push( + new DTSConnectionSetSettingStep(), + new DTSHubNameSetSettingStep(), + ); + + return { promptSteps, executeSteps }; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts new file mode 100644 index 000000000..a94a68600 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardExecuteStep, nonNullValue } from '@microsoft/vscode-azext-utils'; +import { commands } from 'vscode'; +import { ConnectionType } from '../../../../constants'; +import { localize } from '../../../../localize'; +import { type DurableTaskSchedulerEmulator } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; +import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; + +export class DTSEmulatorStartStep extends AzureWizardExecuteStep { + public priority: number = 200; + + public async execute(context: T): Promise { + const emulatorId: string = nonNullValue( + await commands.executeCommand('azureFunctions.durableTaskScheduler.startEmulator'), + localize('failedToStartEmulator', 'Internal error: Failed to start DTS emulator.'), + ); + + const emulators: DurableTaskSchedulerEmulator[] = nonNullValue( + await commands.executeCommand('azureFunctions.durableTaskScheduler.getEmulators'), + localize('failedToGetEmulators', 'Internal error: Failed to retrieve the list of DTS emulators.'), + ); + + const emulator: DurableTaskSchedulerEmulator = nonNullValue( + emulators.find(e => e.id === emulatorId), + localize('couldNotFindEmulator', 'Internal error: Failed to retrieve info on the started DTS emulator.'), + ); + + context.newDTSConnection = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`; + context.newDTSHubName = 'default'; + } + + public shouldExecute(context: T): boolean { + return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Emulator; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts new file mode 100644 index 000000000..376b892c8 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, validationUtils, type IActionContext } from '@microsoft/vscode-azext-utils'; +import * as path from 'path'; +import { ConnectionKey, ConnectionType, localSettingsFileName } from '../../../../constants'; +import { getLocalSettingsJson } from '../../../../funcConfig/local.settings'; +import { localize } from '../../../../localize'; +import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; + +export class DTSHubNameCustomPromptStep extends AzureWizardPromptStep { + public async prompt(context: T): Promise { + context.newDTSHubName = (await context.ui.showInputBox({ + prompt: localize('customDTSConnectionPrompt', 'Provide the custom DTS hub name.'), + value: await getDTSHubName(context, context.projectPath), + validateInput: (value: string) => this.validateInput(value) + })).trim(); + } + + public shouldPrompt(context: T): boolean { + return !context.newDTSHubName && context.dtsConnectionType === ConnectionType.Custom; + } + + private validateInput(name: string): string | undefined { + name = name.trim(); + + if (!validationUtils.hasValidCharLength(name)) { + return validationUtils.getInvalidCharLengthMessage(); + } + return undefined; + } +} + +async function getDTSHubName(context: IActionContext, projectPath: string): Promise { + const localSettingsJson = await getLocalSettingsJson(context, path.join(projectPath, localSettingsFileName)); + return localSettingsJson.Values?.[ConnectionKey.DTSHub]; +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts new file mode 100644 index 000000000..1a06ed03e --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { nonNullProp } from '@microsoft/vscode-azext-utils'; +import { ConnectionKey } from '../../../../constants'; +import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; +import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; + +export class DTSHubNameSetSettingStep extends SetConnectionSettingStepBase { + public priority: number = 241; + public debugDeploySetting: ConnectionKey = ConnectionKey.DTSHub; + + public async execute(context: T): Promise { + await this.setConnectionSetting(context, nonNullProp(context, 'newDTSHubName')); + } + + public shouldExecute(context: T): boolean { + return !!context.newDTSHubName; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts new file mode 100644 index 000000000..dd4b78dd3 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ResourceGroup } from "@azure/arm-resources"; +import { type ConnectionType, type StorageConnectionTypeValues } from "../../../../constants"; +import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; + +export interface IDTSConnectionWizardContext extends ISetConnectionSettingContext { + resourceGroup?: ResourceGroup; + + // Connection Types + azureWebJobsStorageType?: StorageConnectionTypeValues; + dtsConnectionType?: ConnectionType; + + newDTSConnection?: string; + newDTSHubName?: string; +} diff --git a/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionPromptStep.ts b/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionPromptStep.ts index 6ef8e9518..ef929d3e4 100644 --- a/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionPromptStep.ts +++ b/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionPromptStep.ts @@ -39,7 +39,7 @@ export class EventHubsConnectionPromptStep { @@ -51,7 +51,7 @@ export class EventHubsConnectionPromptStep 0) { const noTaskHubItem: QuickPickItem = { - detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), - label: localize('noTaskHubLabel', 'None') - } + detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), + label: localize('noTaskHubLabel', 'None') + } const taskHubItems: QuickPickItem[] = taskHubs.map(taskHub => ({ label: taskHub })); diff --git a/src/commands/durableTaskScheduler/createScheduler.ts b/src/commands/durableTaskScheduler/createScheduler.ts index b236e2a0d..491505487 100644 --- a/src/commands/durableTaskScheduler/createScheduler.ts +++ b/src/commands/durableTaskScheduler/createScheduler.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { type ResourceManagementClient } from '@azure/arm-resources'; import { type AzExtClientContext, createAzureClient, type ILocationWizardContext, type IResourceGroupWizardContext, LocationListStep, parseClientContext, ResourceGroupCreateStep, ResourceGroupListStep, VerifyProvidersStep } from "@microsoft/vscode-azext-azureutils"; import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, createSubscriptionContext, type ExecuteActivityContext, type IActionContext, type ISubscriptionActionContext, subscriptionExperience } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; +import { type Progress, workspace } from "vscode"; import { DurableTaskProvider, DurableTaskSchedulersResourceType } from "../../constants"; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; @@ -13,8 +15,6 @@ import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler import { type DurableTaskSchedulerDataBranchProvider } from "../../tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider"; import { createActivityContext } from "../../utils/activityUtils"; import { withCancellation } from "../../utils/cancellation"; -import { workspace, type Progress } from "vscode"; -import { type ResourceManagementClient } from '@azure/arm-resources'; interface ICreateSchedulerContext extends ISubscriptionActionContext, ILocationWizardContext, IResourceGroupWizardContext, ExecuteActivityContext { subscription?: AzureSubscription; diff --git a/src/commands/durableTaskScheduler/startEmulator.ts b/src/commands/durableTaskScheduler/startEmulator.ts index 31fb87bb9..e1528ea3d 100644 --- a/src/commands/durableTaskScheduler/startEmulator.ts +++ b/src/commands/durableTaskScheduler/startEmulator.ts @@ -7,7 +7,7 @@ import { type IActionContext } from "@microsoft/vscode-azext-utils"; import { type DurableTaskSchedulerEmulatorClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient"; export function startEmulatorCommandFactory(emulatorClient: DurableTaskSchedulerEmulatorClient) { - return async (_: IActionContext) => { - await emulatorClient.startEmulator(); + return async (_: IActionContext): Promise => { + return await emulatorClient.startEmulator(); }; } diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index d68c9c0b2..1c0839e8b 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -21,6 +21,7 @@ import { installOrUpdateFuncCoreTools } from '../funcCoreTools/installOrUpdateFu import { uninstallFuncCoreTools } from '../funcCoreTools/uninstallFuncCoreTools'; import { type DurableTaskSchedulerClient } from '../tree/durableTaskScheduler/DurableTaskSchedulerClient'; import { type DurableTaskSchedulerDataBranchProvider } from '../tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider'; +import { type DurableTaskSchedulerEmulator, type DurableTaskSchedulerEmulatorClient } from '../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; import { ResolvedFunctionAppResource } from '../tree/ResolvedFunctionAppResource'; import { addBinding } from './addBinding/addBinding'; import { addLocalMIConnections } from './addMIConnections/addLocalMIConnections'; @@ -50,6 +51,7 @@ import { disconnectRepo } from './deployments/disconnectRepo'; import { redeployDeployment } from './deployments/redeployDeployment'; import { viewCommitInGitHub } from './deployments/viewCommitInGitHub'; import { viewDeploymentLogs } from './deployments/viewDeploymentLogs'; +import { copyEmulatorConnectionStringCommandFactory } from './durableTaskScheduler/copyEmulatorConnectionString'; import { copySchedulerConnectionStringCommandFactory } from './durableTaskScheduler/copySchedulerConnectionString'; import { copySchedulerEndpointCommandFactory } from './durableTaskScheduler/copySchedulerEndpoint'; import { createSchedulerCommandFactory } from './durableTaskScheduler/createScheduler'; @@ -57,6 +59,8 @@ import { createTaskHubCommandFactory } from './durableTaskScheduler/createTaskHu import { deleteSchedulerCommandFactory } from './durableTaskScheduler/deleteScheduler'; import { deleteTaskHubCommandFactory } from './durableTaskScheduler/deleteTaskHub'; import { openTaskHubDashboard } from './durableTaskScheduler/openTaskHubDashboard'; +import { startEmulatorCommandFactory } from './durableTaskScheduler/startEmulator'; +import { stopEmulatorCommandFactory } from './durableTaskScheduler/stopEmulator'; import { editAppSetting } from './editAppSetting'; import { EventGridCodeLensProvider } from './executeFunction/eventGrid/EventGridCodeLensProvider'; import { sendEventGridRequest } from './executeFunction/eventGrid/sendEventGridRequest'; @@ -79,10 +83,6 @@ import { stopFunctionApp } from './stopFunctionApp'; import { swapSlot } from './swapSlot'; import { disableFunction, enableFunction } from './updateDisabledState'; import { viewProperties } from './viewProperties'; -import { startEmulatorCommandFactory } from './durableTaskScheduler/startEmulator'; -import { stopEmulatorCommandFactory } from './durableTaskScheduler/stopEmulator'; -import { type DurableTaskSchedulerEmulatorClient } from '../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; -import { copyEmulatorConnectionStringCommandFactory } from './durableTaskScheduler/copyEmulatorConnectionString'; export function registerCommands( services: { @@ -204,4 +204,5 @@ export function registerCommands( registerCommandWithTreeNodeUnwrapping('azureFunctions.durableTaskScheduler.openTaskHubDashboard', openTaskHubDashboard); registerCommandWithTreeNodeUnwrapping('azureFunctions.durableTaskScheduler.startEmulator', startEmulatorCommandFactory(services.dts.emulatorClient)); registerCommandWithTreeNodeUnwrapping('azureFunctions.durableTaskScheduler.stopEmulator', stopEmulatorCommandFactory(services.dts.emulatorClient)); + registerCommandWithTreeNodeUnwrapping('azureFunctions.durableTaskScheduler.getEmulators', async (_: IActionContext): Promise => await services.dts.emulatorClient.getEmulators()); } diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts new file mode 100644 index 000000000..e44a9e9ba --- /dev/null +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizard, type IActionContext } from "@microsoft/vscode-azext-utils"; +import { DTSConnectionTypeListStep } from "../../commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep"; +import { type IDTSConnectionWizardContext } from "../../commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext"; +import { CodeAction, ConnectionKey, ConnectionType } from "../../constants"; +import { getLocalSettingsConnectionString } from "../../funcConfig/local.settings"; +import { localize } from "../../localize"; +import { requestUtils } from "../../utils/requestUtils"; + +// If the user previously chose to debug using the emulator, save that preference for the VS Code session +let useDTSEmulator: boolean; + +export async function validateDTSConnectionPreDebug(context: IActionContext, projectPath: string): Promise { + const dtsConnection: string | undefined = await getLocalSettingsConnectionString(context, ConnectionKey.DTS, projectPath); + if (dtsConnection && await isAliveConnection(context, dtsConnection)) { + return; + } + + const availableDebugConnectionTypes = new Set([ConnectionType.Emulator, ConnectionType.Custom]); + + const wizardContext: IDTSConnectionWizardContext = Object.assign(context, { + projectPath, + action: CodeAction.Debug, + dtsConnectionType: useDTSEmulator ? ConnectionType.Emulator : undefined, + }); + const wizard: AzureWizard = new AzureWizard(wizardContext, { + title: localize('acquireDTSConnection', 'Acquire DTS connection'), + promptSteps: [new DTSConnectionTypeListStep(availableDebugConnectionTypes)], + }); + + await wizard.prompt(); + await wizard.execute(); + + useDTSEmulator = wizardContext.dtsConnectionType === ConnectionType.Emulator; +} + +/** + * Checks whether a given DTS connection is still alive (i.e. has not gone stale) + */ +export async function isAliveConnection(context: IActionContext, dtsConnection: string): Promise { + // We need to extract the endpoint from a string like: Endpoint=http://localhost:55053/;Authentication=None + const endpointMatch = dtsConnection.match(/Endpoint=([^;]+)/); + if (!endpointMatch) { + return false; + } + + try { + const url: string = endpointMatch[1]; + await requestUtils.sendRequestWithExtTimeout(context, { url, method: 'GET' }); + return true; + } catch (e) { + // Even if we get back an error, if we can read a status code, the connection provided a response and is still alive + const statusCode = (e as { statusCode?: number })?.statusCode; + return statusCode ? true : false; + } +} diff --git a/src/debug/validatePreDebug.ts b/src/debug/validatePreDebug.ts index 381fd61f9..5cdfa095e 100644 --- a/src/debug/validatePreDebug.ts +++ b/src/debug/validatePreDebug.ts @@ -13,7 +13,7 @@ import { validateStorageConnection } from '../commands/appSettings/connectionSet import { validateEventHubsConnection } from '../commands/appSettings/connectionSettings/eventHubs/validateEventHubsConnection'; import { validateSqlDbConnection } from '../commands/appSettings/connectionSettings/sqlDatabase/validateSqlDbConnection'; import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject'; -import { CodeAction, ConnectionKey, DurableBackend, ProjectLanguage, functionJsonFileName, localSettingsFileName, localStorageEmulatorConnectionString, projectLanguageModelSetting, projectLanguageSetting, workerRuntimeKey, type DurableBackendValues } from "../constants"; +import { CodeAction, ConnectionKey, DurableBackend, ProjectLanguage, functionJsonFileName, localSettingsFileName, localStorageEmulatorConnectionString, projectLanguageModelSetting, projectLanguageSetting, workerRuntimeKey } from "../constants"; import { ParsedFunctionJson } from "../funcConfig/function"; import { MismatchBehavior, getLocalSettingsConnectionString, setLocalAppSetting } from "../funcConfig/local.settings"; import { getLocalFuncCoreToolsVersion } from '../funcCoreTools/getLocalFuncCoreToolsVersion'; @@ -24,6 +24,7 @@ import { durableUtils } from '../utils/durableUtils'; import { isNodeV4Plus, isPythonV2Plus } from '../utils/programmingModelUtils'; import { getDebugConfigs, isDebugConfigEqual } from '../vsCodeConfig/launch'; import { getWorkspaceSetting, tryGetFunctionsWorkerRuntimeForProject } from "../vsCodeConfig/settings"; +import { validateDTSConnectionPreDebug } from './durable/validateDTSConnectionPreDebug'; export interface IPreDebugValidateResult { workspace: vscode.WorkspaceFolder; @@ -52,10 +53,11 @@ export async function preDebugValidate(actionContext: IActionContext, debugConfi if (context.projectPath) { const projectLanguage: string | undefined = getWorkspaceSetting(projectLanguageSetting, context.projectPath); const projectLanguageModel: number | undefined = getWorkspaceSetting(projectLanguageModelSetting, context.projectPath); - const durableStorageType: DurableBackendValues | undefined = await durableUtils.getStorageTypeFromWorkspace(projectLanguage, context.projectPath); + const durableStorageType: DurableBackend | undefined = await durableUtils.getStorageTypeFromWorkspace(projectLanguage, context.projectPath); context.telemetry.properties.projectLanguage = projectLanguage; context.telemetry.properties.projectLanguageModel = projectLanguageModel?.toString(); + context.telemetry.properties.durableStorageType = durableStorageType; context.telemetry.properties.lastValidateStep = 'functionVersion'; shouldContinue = await validateFunctionVersion(context, projectLanguage, projectLanguageModel, workspace.uri.fsPath); @@ -64,6 +66,10 @@ export async function preDebugValidate(actionContext: IActionContext, debugConfi await validateWorkerRuntime(context, projectLanguage, context.projectPath); switch (durableStorageType) { + case DurableBackend.DTS: + context.telemetry.properties.lastValidateStep = 'dtsConnection'; + await validateDTSConnectionPreDebug(context, context.projectPath); + break; case DurableBackend.Netherite: context.telemetry.properties.lastValidateStep = 'eventHubsConnection'; await validateEventHubsConnection(context, context.projectPath); From e43c2aa456a7b2f14022f37d740671ada1304499 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:57:33 -0700 Subject: [PATCH 14/52] Update comment --- src/debug/durable/validateDTSConnectionPreDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts index e44a9e9ba..6e8a82fad 100644 --- a/src/debug/durable/validateDTSConnectionPreDebug.ts +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -11,7 +11,7 @@ import { getLocalSettingsConnectionString } from "../../funcConfig/local.setting import { localize } from "../../localize"; import { requestUtils } from "../../utils/requestUtils"; -// If the user previously chose to debug using the emulator, save that preference for the VS Code session +// If the user previously chose to debug using the emulator, save that preference for the ongoing VS Code session let useDTSEmulator: boolean; export async function validateDTSConnectionPreDebug(context: IActionContext, projectPath: string): Promise { From e96ceb96b940ef2d02273aea6d3a08d0991be5a8 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:01:11 -0700 Subject: [PATCH 15/52] Update comment again --- src/debug/durable/validateDTSConnectionPreDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts index 6e8a82fad..58192b3f9 100644 --- a/src/debug/durable/validateDTSConnectionPreDebug.ts +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -11,7 +11,7 @@ import { getLocalSettingsConnectionString } from "../../funcConfig/local.setting import { localize } from "../../localize"; import { requestUtils } from "../../utils/requestUtils"; -// If the user previously chose to debug using the emulator, save that preference for the ongoing VS Code session +// If the user previously chose to debug using the emulator, leverage that preference for the remaining VS Code session let useDTSEmulator: boolean; export async function validateDTSConnectionPreDebug(context: IActionContext, projectPath: string): Promise { From 0529729e8884e60f16894f899b61a643cfd2d98c Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:14:20 -0700 Subject: [PATCH 16/52] Formatting --- src/debug/durable/validateDTSConnectionPreDebug.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts index 58192b3f9..1b0103627 100644 --- a/src/debug/durable/validateDTSConnectionPreDebug.ts +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -27,6 +27,7 @@ export async function validateDTSConnectionPreDebug(context: IActionContext, pro action: CodeAction.Debug, dtsConnectionType: useDTSEmulator ? ConnectionType.Emulator : undefined, }); + const wizard: AzureWizard = new AzureWizard(wizardContext, { title: localize('acquireDTSConnection', 'Acquire DTS connection'), promptSteps: [new DTSConnectionTypeListStep(availableDebugConnectionTypes)], From 96eae40b027463130946921d394b64fb9133ba78 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:59:45 -0700 Subject: [PATCH 17/52] Make storage connection type reuse logic more simple --- .../IConnectionPromptOptions.ts | 4 +-- .../AzureWebJobsStoragePromptStep.ts | 28 +++++++++++++------ .../IAzureWebJobsStorageWizardContext.ts | 4 +-- .../IDTSConnectionWizardContext.ts | 4 +-- .../IEventHubsConnectionWizardContext.ts | 4 +-- .../ISqlDatabaseConnectionWizardContext.ts | 4 +-- src/constants.ts | 2 +- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts b/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts index 0db3f971f..8ea36aa81 100644 --- a/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts +++ b/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionTypeValues } from "../../../constants"; +import { type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionType } from "../../../constants"; export interface IConnectionPromptOptions { - preselectedConnectionType?: StorageConnectionTypeValues | EventHubsConnectionTypeValues | SqlDbConnectionTypeValues; + preselectedConnectionType?: StorageConnectionType | EventHubsConnectionTypeValues | SqlDbConnectionTypeValues; } diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts index 99904244e..854ed924e 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts @@ -6,7 +6,7 @@ import { StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, type ISubscriptionActionContext, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { type MessageItem } from 'vscode'; -import { ConnectionType, type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues } from '../../../../constants'; +import { ConnectionType, type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionType } from '../../../../constants'; import { useEmulator } from '../../../../constants-nls'; import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; @@ -37,18 +37,15 @@ export class AzureWebJobsStoragePromptStep { + const reuseableConnectionType: ConnectionType | undefined = tryReuseExistingConnectionType([context.dtsConnectionType, context.eventHubsConnectionType, context.sqlDbConnectionType]); + if (this.options?.preselectedConnectionType === ConnectionType.Azure || this.options?.preselectedConnectionType === ConnectionType.Emulator) { context.azureWebJobsStorageType = this.options.preselectedConnectionType; } else if (!!context.storageAccount || !!context.newStorageAccountName) { // Only should prompt if no storage account was selected context.azureWebJobsStorageType = ConnectionType.Azure; - } else if (context.eventHubsConnectionType) { - context.azureWebJobsStorageType = context.eventHubsConnectionType; - } else if (context.sqlDbConnectionType === ConnectionType.Azure) { - // No official support for an `Emulator` scenario yet - context.azureWebJobsStorageType = context.sqlDbConnectionType; - } else if (context.dtsConnectionType === ConnectionType.Emulator) { - context.azureWebJobsStorageType = context.dtsConnectionType; + } else if (reuseableConnectionType) { + context.azureWebJobsStorageType = reuseableConnectionType; } // Even if we end up skipping the prompt, we should still record the flow in telemetry @@ -90,3 +87,18 @@ export class AzureWebJobsStoragePromptStep = new Set([ConnectionType.Azure, ConnectionType.Emulator]); + +function tryReuseExistingConnectionType(connections: (ConnectionType | undefined)[]): StorageConnectionType | undefined { + for (const c of connections) { + if (!c) { + continue; + } + + if (availableStorageConnections.has(c)) { + return c as StorageConnectionType; + } + } + return undefined; +} diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts index 3b05dab38..7a4ecb2c0 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts @@ -5,12 +5,12 @@ import { type StorageAccount } from "@azure/arm-storage"; import { type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { type StorageConnectionTypeValues } from "../../../../constants"; +import { type StorageConnectionType } from "../../../../constants"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; export interface IAzureWebJobsStorageWizardContext extends ISetConnectionSettingContext, Partial { storageAccount?: StorageAccount; newStorageAccountName?: string; - azureWebJobsStorageType?: StorageConnectionTypeValues; + azureWebJobsStorageType?: StorageConnectionType; } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index dd4b78dd3..60fb9ec86 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { type ResourceGroup } from "@azure/arm-resources"; -import { type ConnectionType, type StorageConnectionTypeValues } from "../../../../constants"; +import { type ConnectionType, type StorageConnectionType } from "../../../../constants"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; export interface IDTSConnectionWizardContext extends ISetConnectionSettingContext { resourceGroup?: ResourceGroup; // Connection Types - azureWebJobsStorageType?: StorageConnectionTypeValues; + azureWebJobsStorageType?: StorageConnectionType; dtsConnectionType?: ConnectionType; newDTSConnection?: string; diff --git a/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts index 582332df2..90258d8db 100644 --- a/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type ResourceGroup } from "@azure/arm-resources"; -import { type EventHubsConnectionTypeValues, type StorageConnectionTypeValues } from "../../../../constants"; +import { type EventHubsConnectionTypeValues, type StorageConnectionType } from "../../../../constants"; import { type IEventHubWizardContext } from "../../../addBinding/settingSteps/eventHub/IEventHubWizardContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; @@ -12,7 +12,7 @@ export interface IEventHubsConnectionWizardContext extends IEventHubWizardContex resourceGroup?: ResourceGroup; // Connection Types - azureWebJobsStorageType?: StorageConnectionTypeValues; + azureWebJobsStorageType?: StorageConnectionType; eventHubsConnectionType?: EventHubsConnectionTypeValues; // Netherite uses all of the eventhub namespace settings in IEventHubWizardContext diff --git a/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts index fdbb2787e..3b708adbb 100644 --- a/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts @@ -6,14 +6,14 @@ import { type ResourceGroup } from '@azure/arm-resources'; import { type Database, type Server } from '@azure/arm-sql'; import { type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { type SqlDbConnectionTypeValues, type StorageConnectionTypeValues } from "../../../../constants"; +import { type SqlDbConnectionTypeValues, type StorageConnectionType } from "../../../../constants"; import { type ISetConnectionSettingContext } from '../ISetConnectionSettingContext'; export interface ISqlDatabaseConnectionWizardContext extends ISetConnectionSettingContext, Partial { resourceGroup?: ResourceGroup; // Connection Types - azureWebJobsStorageType?: StorageConnectionTypeValues; + azureWebJobsStorageType?: StorageConnectionType; sqlDbConnectionType?: SqlDbConnectionTypeValues; // SQL diff --git a/src/constants.ts b/src/constants.ts index 83298e31f..afa2877fc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -127,7 +127,7 @@ export enum DurableBackend { } export type ConnectionTypeValues = typeof ConnectionType[keyof typeof ConnectionType]; -export type StorageConnectionTypeValues = Exclude; +export type StorageConnectionType = ConnectionType.Azure | ConnectionType.Emulator; export type EventHubsConnectionTypeValues = Exclude; export type SqlDbConnectionTypeValues = Exclude; From 612ec9c0ba2c52d208f375e429eb8788f615ddda Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:30:05 -0700 Subject: [PATCH 18/52] Update connection types --- .../IConnectionPromptOptions.ts | 5 +++-- .../IConnectionTypesContext.ts | 19 +++++++++++++++++++ .../ISetConnectionSettingContext.ts | 3 ++- .../AzureWebJobsStoragePromptStep.ts | 13 +++++++------ .../IAzureWebJobsStorageWizardContext.ts | 2 +- .../IDTSConnectionWizardContext.ts | 3 ++- .../IEventHubsConnectionWizardContext.ts | 4 ++-- .../ISqlDatabaseConnectionWizardContext.ts | 4 ++-- src/constants.ts | 5 ----- 9 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 src/commands/appSettings/connectionSettings/IConnectionTypesContext.ts diff --git a/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts b/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts index 8ea36aa81..baae2bf8b 100644 --- a/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts +++ b/src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionType } from "../../../constants"; +import { type EventHubsConnectionType, type SqlDbConnectionType, type StorageConnectionType } from "./IConnectionTypesContext"; + export interface IConnectionPromptOptions { - preselectedConnectionType?: StorageConnectionType | EventHubsConnectionTypeValues | SqlDbConnectionTypeValues; + preselectedConnectionType?: StorageConnectionType | EventHubsConnectionType | SqlDbConnectionType; } diff --git a/src/commands/appSettings/connectionSettings/IConnectionTypesContext.ts b/src/commands/appSettings/connectionSettings/IConnectionTypesContext.ts new file mode 100644 index 000000000..9e26863af --- /dev/null +++ b/src/commands/appSettings/connectionSettings/IConnectionTypesContext.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ConnectionType } from "../../../constants"; + +export type StorageConnectionType = ConnectionType.Azure | ConnectionType.Emulator; +export type DTSConnectionType = ConnectionType; +export type EventHubsConnectionType = ConnectionType.Azure | ConnectionType.Emulator; +export type SqlDbConnectionType = ConnectionType.Azure | ConnectionType.Custom; + +export interface IConnectionTypesContext { + azureWebJobsStorageType?: StorageConnectionType; + dtsConnectionType?: DTSConnectionType; + eventHubsConnectionType?: EventHubsConnectionType; + sqlDbConnectionType?: SqlDbConnectionType; +} + diff --git a/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts b/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts index a318a622b..99b0127ea 100644 --- a/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts +++ b/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts @@ -5,8 +5,9 @@ import { type IActionContext } from "@microsoft/vscode-azext-utils"; import { type CodeActionValues, type ConnectionKey } from "../../../constants"; +import { type IConnectionTypesContext } from "./IConnectionTypesContext"; -export interface ISetConnectionSettingContext extends IActionContext { +export interface ISetConnectionSettingContext extends IActionContext, IConnectionTypesContext { action: CodeActionValues; projectPath: string; diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts index 854ed924e..6c1b4a97a 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts @@ -6,11 +6,12 @@ import { StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, type ISubscriptionActionContext, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { type MessageItem } from 'vscode'; -import { ConnectionType, type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionType } from '../../../../constants'; +import { ConnectionType } from '../../../../constants'; import { useEmulator } from '../../../../constants-nls'; import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; import { type IConnectionPromptOptions } from '../IConnectionPromptOptions'; +import { type StorageConnectionType } from '../IConnectionTypesContext'; import { type IAzureWebJobsStorageWizardContext } from './IAzureWebJobsStorageWizardContext'; export class AzureWebJobsStoragePromptStep extends AzureWizardPromptStep { @@ -36,16 +37,16 @@ export class AzureWebJobsStoragePromptStep { - const reuseableConnectionType: ConnectionType | undefined = tryReuseExistingConnectionType([context.dtsConnectionType, context.eventHubsConnectionType, context.sqlDbConnectionType]); + public async configureBeforePrompt(context: T): Promise { + const matchingConnectionType: StorageConnectionType | undefined = tryFindMatchingConnectionType([context.dtsConnectionType, context.eventHubsConnectionType, context.sqlDbConnectionType]); if (this.options?.preselectedConnectionType === ConnectionType.Azure || this.options?.preselectedConnectionType === ConnectionType.Emulator) { context.azureWebJobsStorageType = this.options.preselectedConnectionType; } else if (!!context.storageAccount || !!context.newStorageAccountName) { // Only should prompt if no storage account was selected context.azureWebJobsStorageType = ConnectionType.Azure; - } else if (reuseableConnectionType) { - context.azureWebJobsStorageType = reuseableConnectionType; + } else if (matchingConnectionType) { + context.azureWebJobsStorageType = matchingConnectionType; } // Even if we end up skipping the prompt, we should still record the flow in telemetry @@ -90,7 +91,7 @@ export class AzureWebJobsStoragePromptStep = new Set([ConnectionType.Azure, ConnectionType.Emulator]); -function tryReuseExistingConnectionType(connections: (ConnectionType | undefined)[]): StorageConnectionType | undefined { +function tryFindMatchingConnectionType(connections: (ConnectionType | undefined)[]): StorageConnectionType | undefined { for (const c of connections) { if (!c) { continue; diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts index 7a4ecb2c0..75f9ff73f 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts @@ -5,7 +5,7 @@ import { type StorageAccount } from "@azure/arm-storage"; import { type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { type StorageConnectionType } from "../../../../constants"; +import { type StorageConnectionType } from "../IConnectionTypesContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; export interface IAzureWebJobsStorageWizardContext extends ISetConnectionSettingContext, Partial { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index 60fb9ec86..47a6da469 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { type ResourceGroup } from "@azure/arm-resources"; -import { type ConnectionType, type StorageConnectionType } from "../../../../constants"; +import { type ConnectionType } from "../../../../constants"; +import { type StorageConnectionType } from "../IConnectionTypesContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; export interface IDTSConnectionWizardContext extends ISetConnectionSettingContext { diff --git a/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts index 90258d8db..4f2da0299 100644 --- a/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/eventHubs/IEventHubsConnectionWizardContext.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { type ResourceGroup } from "@azure/arm-resources"; -import { type EventHubsConnectionTypeValues, type StorageConnectionType } from "../../../../constants"; import { type IEventHubWizardContext } from "../../../addBinding/settingSteps/eventHub/IEventHubWizardContext"; +import { type EventHubsConnectionType, type StorageConnectionType } from "../IConnectionTypesContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; export interface IEventHubsConnectionWizardContext extends IEventHubWizardContext, ISetConnectionSettingContext { @@ -13,7 +13,7 @@ export interface IEventHubsConnectionWizardContext extends IEventHubWizardContex // Connection Types azureWebJobsStorageType?: StorageConnectionType; - eventHubsConnectionType?: EventHubsConnectionTypeValues; + eventHubsConnectionType?: EventHubsConnectionType; // Netherite uses all of the eventhub namespace settings in IEventHubWizardContext } diff --git a/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts index 3b708adbb..61013ce14 100644 --- a/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/sqlDatabase/ISqlDatabaseConnectionWizardContext.ts @@ -6,7 +6,7 @@ import { type ResourceGroup } from '@azure/arm-resources'; import { type Database, type Server } from '@azure/arm-sql'; import { type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { type SqlDbConnectionTypeValues, type StorageConnectionType } from "../../../../constants"; +import { type SqlDbConnectionType, type StorageConnectionType } from '../IConnectionTypesContext'; import { type ISetConnectionSettingContext } from '../ISetConnectionSettingContext'; export interface ISqlDatabaseConnectionWizardContext extends ISetConnectionSettingContext, Partial { @@ -14,7 +14,7 @@ export interface ISqlDatabaseConnectionWizardContext extends ISetConnectionSetti // Connection Types azureWebJobsStorageType?: StorageConnectionType; - sqlDbConnectionType?: SqlDbConnectionTypeValues; + sqlDbConnectionType?: SqlDbConnectionType; // SQL newSqlServerName?: string; diff --git a/src/constants.ts b/src/constants.ts index a992437de..d168a72dc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -126,11 +126,6 @@ export enum DurableBackend { SQL = 'mssql', } -export type ConnectionTypeValues = typeof ConnectionType[keyof typeof ConnectionType]; -export type StorageConnectionType = ConnectionType.Azure | ConnectionType.Emulator; -export type EventHubsConnectionTypeValues = Exclude; -export type SqlDbConnectionTypeValues = Exclude; - export type CodeActionValues = typeof CodeAction[keyof typeof CodeAction]; export type ConnectionKeyValues = typeof ConnectionKey[keyof typeof ConnectionKey]; export type DurableBackendValues = typeof DurableBackend[keyof typeof DurableBackend]; From c676ce9a64a868282106842012a7426f8cc76848 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:37:15 -0700 Subject: [PATCH 19/52] Revert autoformatting change --- .../durableTaskScheduler/copyEmulatorConnectionString.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts index b2302be16..5c10e7621 100644 --- a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts +++ b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts @@ -5,8 +5,8 @@ import { type IActionContext } from "@microsoft/vscode-azext-utils"; import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; -import { ext } from "../../extensionVariables"; import { localize } from "../../localize"; +import { ext } from "../../extensionVariables"; import { type DurableTaskSchedulerEmulatorWorkspaceResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorWorkspaceResourceModel"; export function copyEmulatorConnectionStringCommandFactory() { @@ -24,9 +24,9 @@ export function copyEmulatorConnectionStringCommandFactory() { if (taskHubs.length > 0) { const noTaskHubItem: QuickPickItem = { - detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), - label: localize('noTaskHubLabel', 'None') - } + detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), + label: localize('noTaskHubLabel', 'None') + } const taskHubItems: QuickPickItem[] = taskHubs.map(taskHub => ({ label: taskHub })); From 2537cc2337df0bf017bda7e05d1c19d8a8ea4873 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:46:19 -0700 Subject: [PATCH 20/52] Revert more autoformatting --- .../durableTaskScheduler/copyEmulatorConnectionString.ts | 2 +- src/commands/durableTaskScheduler/createScheduler.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts index 5c10e7621..ba94b0d2e 100644 --- a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts +++ b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { type IActionContext } from "@microsoft/vscode-azext-utils"; -import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; import { localize } from "../../localize"; import { ext } from "../../extensionVariables"; +import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; import { type DurableTaskSchedulerEmulatorWorkspaceResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorWorkspaceResourceModel"; export function copyEmulatorConnectionStringCommandFactory() { diff --git a/src/commands/durableTaskScheduler/createScheduler.ts b/src/commands/durableTaskScheduler/createScheduler.ts index 491505487..b236e2a0d 100644 --- a/src/commands/durableTaskScheduler/createScheduler.ts +++ b/src/commands/durableTaskScheduler/createScheduler.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type ResourceManagementClient } from '@azure/arm-resources'; import { type AzExtClientContext, createAzureClient, type ILocationWizardContext, type IResourceGroupWizardContext, LocationListStep, parseClientContext, ResourceGroupCreateStep, ResourceGroupListStep, VerifyProvidersStep } from "@microsoft/vscode-azext-azureutils"; import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, createSubscriptionContext, type ExecuteActivityContext, type IActionContext, type ISubscriptionActionContext, subscriptionExperience } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -import { type Progress, workspace } from "vscode"; import { DurableTaskProvider, DurableTaskSchedulersResourceType } from "../../constants"; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; @@ -15,6 +13,8 @@ import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler import { type DurableTaskSchedulerDataBranchProvider } from "../../tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider"; import { createActivityContext } from "../../utils/activityUtils"; import { withCancellation } from "../../utils/cancellation"; +import { workspace, type Progress } from "vscode"; +import { type ResourceManagementClient } from '@azure/arm-resources'; interface ICreateSchedulerContext extends ISubscriptionActionContext, ILocationWizardContext, IResourceGroupWizardContext, ExecuteActivityContext { subscription?: AzureSubscription; From 0da96c170ca88d2f5c862f9265b7794e8cbc487c Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:53:41 -0700 Subject: [PATCH 21/52] Add dts connection validation for azure deployment --- .../DTSConnectionSetSettingStep.ts | 4 +- .../DTSConnectionTypeListStep.ts | 26 ++++-- .../DTSHubNameSetSettingStep.ts | 4 +- .../IDTSConnectionWizardContext.ts | 23 +++++- .../azure/DurableTaskHubCreateStep.ts | 35 ++++++++ .../azure/DurableTaskHubListStep.ts | 64 +++++++++++++++ .../azure/DurableTaskHubNameStep.ts | 32 ++++++++ .../azure/DurableTaskSchedulerCreateStep.ts | 36 +++++++++ .../azure/DurableTaskSchedulerListStep.ts | 68 ++++++++++++++++ .../azure/DurableTaskSchedulerNameStep.ts | 31 +++++++ .../DTSConnectionCustomPromptStep.ts | 10 +-- .../DTSHubNameCustomPromptStep.ts | 12 +-- .../{ => emulator}/DTSEmulatorStartStep.ts | 14 ++-- .../validateDTSConnection.ts | 80 +++++++++++++++++++ src/commands/deploy/deploy.ts | 23 ++++-- .../deploy/shouldValidateConnection.ts | 12 ++- src/constants-nls.ts | 1 + src/tree/SubscriptionTreeItem.ts | 2 + .../DurableTaskSchedulerClient.ts | 24 ++++-- 19 files changed, 450 insertions(+), 51 deletions(-) create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts rename src/commands/appSettings/connectionSettings/durableTaskScheduler/{ => custom}/DTSConnectionCustomPromptStep.ts (75%) rename src/commands/appSettings/connectionSettings/durableTaskScheduler/{ => custom}/DTSHubNameCustomPromptStep.ts (78%) rename src/commands/appSettings/connectionSettings/durableTaskScheduler/{ => emulator}/DTSEmulatorStartStep.ts (72%) create mode 100644 src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts index 61889dcb1..74b8a55f4 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts @@ -13,10 +13,10 @@ export class DTSConnectionSetSettingStep public debugDeploySetting: ConnectionKey = ConnectionKey.DTS; public async execute(context: T): Promise { - await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnection')); + await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnectionSetting')); } public shouldExecute(context: T): boolean { - return !!context.newDTSConnection; + return !!context.newDTSConnectionSetting; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts index 4a4a1cbb6..3fd6450ea 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -3,17 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VerifyProvidersStep } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, type AzureWizardExecuteStep, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { type MessageItem } from 'vscode'; -import { ConnectionType } from '../../../../constants'; +import { ConnectionType, DurableTaskProvider } from '../../../../constants'; import { useEmulator } from '../../../../constants-nls'; import { localize } from '../../../../localize'; -import { DTSConnectionCustomPromptStep } from './DTSConnectionCustomPromptStep'; +import { HttpDurableTaskSchedulerClient } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; +import { DurableTaskHubListStep } from './azure/DurableTaskHubListStep'; +import { DurableTaskSchedulerListStep } from './azure/DurableTaskSchedulerListStep'; +import { DTSConnectionCustomPromptStep } from './custom/DTSConnectionCustomPromptStep'; +import { DTSHubNameCustomPromptStep } from './custom/DTSHubNameCustomPromptStep'; import { DTSConnectionSetSettingStep } from './DTSConnectionSetSettingStep'; -import { DTSEmulatorStartStep } from './DTSEmulatorStartStep'; -import { DTSHubNameCustomPromptStep } from './DTSHubNameCustomPromptStep'; import { DTSHubNameSetSettingStep } from './DTSHubNameSetSettingStep'; -import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; +import { DTSEmulatorStartStep } from './emulator/DTSEmulatorStartStep'; +import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; export class DTSConnectionTypeListStep extends AzureWizardPromptStep { constructor(readonly connectionTypes: Set) { @@ -50,12 +54,18 @@ export class DTSConnectionTypeListStep ex } public async getSubWizard(context: T): Promise | undefined> { - const promptSteps: AzureWizardPromptStep[] = []; - const executeSteps: AzureWizardExecuteStep[] = []; + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; switch (context.dtsConnectionType) { case ConnectionType.Azure: - throw new Error('Needs implementation.'); + const client = new HttpDurableTaskSchedulerClient(); + promptSteps.push( + new DurableTaskSchedulerListStep(client), + new DurableTaskHubListStep(client) + ); + executeSteps.push(new VerifyProvidersStep([DurableTaskProvider])); + break; case ConnectionType.Emulator: executeSteps.push(new DTSEmulatorStartStep()); break; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts index 1a06ed03e..46d2ead71 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameSetSettingStep.ts @@ -13,10 +13,10 @@ export class DTSHubNameSetSettingStep ext public debugDeploySetting: ConnectionKey = ConnectionKey.DTSHub; public async execute(context: T): Promise { - await this.setConnectionSetting(context, nonNullProp(context, 'newDTSHubName')); + await this.setConnectionSetting(context, nonNullProp(context, 'newDTSHubNameConnectionSetting')); } public shouldExecute(context: T): boolean { - return !!context.newDTSHubName; + return !!context.newDTSHubNameConnectionSetting; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index 47a6da469..69e59f7cc 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -4,17 +4,32 @@ *--------------------------------------------------------------------------------------------*/ import { type ResourceGroup } from "@azure/arm-resources"; +import { type IActionContext, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { type ConnectionType } from "../../../../constants"; +import { type DurableTaskHubResource, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type StorageConnectionType } from "../IConnectionTypesContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; -export interface IDTSConnectionWizardContext extends ISetConnectionSettingContext { - resourceGroup?: ResourceGroup; - +export interface IDTSConnectionWizardContext extends IActionContext, ISetConnectionSettingContext { // Connection Types azureWebJobsStorageType?: StorageConnectionType; dtsConnectionType?: ConnectionType; - newDTSConnection?: string; + newDTSConnectionSetting?: string; + newDTSHubNameConnectionSetting?: string; +} + +export interface IDTSAzureConnectionWizardContext extends ISubscriptionActionContext, IDTSConnectionWizardContext { + subscription?: AzureSubscription; + resourceGroup?: ResourceGroup; + + suggestedDTSEndpointLocalSettings?: string; + suggestedDTSHub?: string; + + newDTSName?: string; + dts?: DurableTaskSchedulerResource; + newDTSHubName?: string; + dtsHub?: DurableTaskHubResource; } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts new file mode 100644 index 000000000..0aead6e77 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +import { type Progress } from "vscode"; +import { localize } from "../../../../../localize"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; + +export class DurableTaskHubCreateStep extends AzureWizardExecuteStep { + priority: number = 160; + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + public async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { + progress.report({ message: localize('createTaskHub', 'Creating durable task hub...') }); + + context.dtsHub = await this.schedulerClient.createTaskHub( + nonNullProp(context, 'subscription'), + nonNullValueAndProp(context.resourceGroup, 'name'), + nonNullValueAndProp(context.dts, 'name'), + nonNullProp(context, 'newDTSHubName'), + ); + } + + public shouldExecute(context: T): boolean { + return !context.dtsHub; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts new file mode 100644 index 000000000..25ad44a90 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { localSettingsDescription } from '../../../../../constants-nls'; +import { localize } from '../../../../../localize'; +import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; +import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext'; +import { DurableTaskHubCreateStep } from './DurableTaskHubCreateStep'; +import { DurableTaskHubNameStep } from './DurableTaskHubNameStep'; + +export class DurableTaskHubListStep extends AzureWizardPromptStep { + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + public async prompt(context: T): Promise { + context.dtsHub = (await context.ui.showQuickPick(await this.getPicks(context), { + placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'), + })).data; + } + + public shouldPrompt(context: T): boolean { + 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 resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); + const taskHubs: DurableTaskHubResource[] = context.dts ? + await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), resourceGroupName, context.dts.name) : []; + + const createPick = { + label: localize('createTaskHub', '$(plus) Create new Durable Task Hub'), + data: undefined, + }; + + return [ + createPick, + ...taskHubs.map(h => { + return { + label: h.name, + description: h.name === context.suggestedDTSHub ? localSettingsDescription : undefined, + data: h, + }; + }), + ]; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts new file mode 100644 index 000000000..7befa2f84 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +import { localize } from "../../../../../localize"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; + +export class DurableTaskHubNameStep extends AzureWizardPromptStep { + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + 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.suggestedDTSHub, + // Todo: validation + }); + + context.valuesToMask.push(context.newDTSHubName); + } + + public shouldPrompt(context: T): boolean { + return !context.newDTSHubName; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts new file mode 100644 index 000000000..093cf4379 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LocationListStep } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +import { type Progress } from "vscode"; +import { localize } from "../../../../../localize"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; + +export class DurableTaskSchedulerCreateStep extends AzureWizardExecuteStep { + priority: number = 150; + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { + progress.report({ message: localize('createTaskScheduler', 'Creating durable task scheduler...') }); + + context.dts = (await this.schedulerClient.createScheduler( + nonNullProp(context, 'subscription'), + nonNullValueAndProp(context.resourceGroup, 'name'), + (await LocationListStep.getLocation(context)).name, + nonNullProp(context, 'newDTSName'), + )).scheduler; + } + + shouldExecute(context: T): boolean { + return !context.dts; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts new file mode 100644 index 000000000..1b84938ef --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, 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 { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext'; +import { DurableTaskHubListStep } from './DurableTaskHubListStep'; +import { DurableTaskSchedulerCreateStep } from './DurableTaskSchedulerCreateStep'; +import { DurableTaskSchedulerNameStep } from './DurableTaskSchedulerNameStep'; + +export class DurableTaskSchedulerListStep extends AzureWizardPromptStep { + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + public async prompt(context: T): Promise { + context.dts = (await context.ui.showQuickPick(await this.getPicks(context), { + placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'), + })).data; + + context.telemetry.properties.usedLocalDTSConnectionSettings = context.suggestedDTSEndpointLocalSettings ? String(context.dts?.properties.endpoint === context.suggestedDTSEndpointLocalSettings) : undefined; + } + + public shouldPrompt(context: T): boolean { + return !context.dts; + } + + public async getSubWizard(context: T): Promise | undefined> { + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; + + if (!context.dts) { + promptSteps.push(new DurableTaskSchedulerNameStep(this.schedulerClient)); + executeSteps.push(new DurableTaskSchedulerCreateStep(this.schedulerClient)); + } + + promptSteps.push(new DurableTaskHubListStep(this.schedulerClient)); + return { promptSteps, executeSteps }; + } + + private async getPicks(context: T): Promise[]> { + const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); + const schedulers: DurableTaskSchedulerResource[] = await this.schedulerClient.getSchedulers(nonNullProp(context, 'subscription'), resourceGroupName) ?? []; + + const createPick = { + label: localize('createTaskScheduler', '$(plus) Create new Durable Task Scheduler'), + data: undefined, + }; + + return [ + createPick, + ...schedulers.map(s => { + return { + label: s.name, + description: s.properties.endpoint === context.suggestedDTSEndpointLocalSettings ? localSettingsDescription : undefined, + data: s, + }; + }), + ]; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts new file mode 100644 index 000000000..9cfe8cdfd --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +import { localize } from "../../../../../localize"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; + +export class DurableTaskSchedulerNameStep extends AzureWizardPromptStep { + private readonly schedulerClient: DurableTaskSchedulerClient; + + constructor(schedulerClient?: DurableTaskSchedulerClient) { + super(); + this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + } + + public async prompt(context: T): Promise { + context.newDTSName = await context.ui.showInputBox({ + prompt: localize('taskSchedulerName', 'Enter a name for the Durable Task Scheduler'), + // Todo: validation + }); + + context.valuesToMask.push(context.newDTSName); + } + + public shouldPrompt(context: T): boolean { + return !context.newDTSName; + } +} diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionCustomPromptStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSConnectionCustomPromptStep.ts similarity index 75% rename from src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionCustomPromptStep.ts rename to src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSConnectionCustomPromptStep.ts index a4982df79..830191e9b 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionCustomPromptStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSConnectionCustomPromptStep.ts @@ -4,20 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizardPromptStep, validationUtils } from '@microsoft/vscode-azext-utils'; -import { ConnectionType } from '../../../../constants'; -import { localize } from '../../../../localize'; -import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; +import { ConnectionType } from '../../../../../constants'; +import { localize } from '../../../../../localize'; +import { type IDTSConnectionWizardContext } from '../IDTSConnectionWizardContext'; export class DTSConnectionCustomPromptStep extends AzureWizardPromptStep { public async prompt(context: T): Promise { - context.newDTSConnection = (await context.ui.showInputBox({ + context.newDTSConnectionSetting = (await context.ui.showInputBox({ prompt: localize('customDTSConnectionPrompt', 'Provide a custom DTS connection string.'), validateInput: (value: string | undefined) => this.validateInput(value) })).trim(); } public shouldPrompt(context: T): boolean { - return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Custom; + return !context.newDTSConnectionSetting && context.dtsConnectionType === ConnectionType.Custom; } private validateInput(name: string | undefined): string | undefined { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSHubNameCustomPromptStep.ts similarity index 78% rename from src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts rename to src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSHubNameCustomPromptStep.ts index 376b892c8..367a111b5 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSHubNameCustomPromptStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/custom/DTSHubNameCustomPromptStep.ts @@ -5,14 +5,14 @@ import { AzureWizardPromptStep, validationUtils, type IActionContext } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; -import { ConnectionKey, ConnectionType, localSettingsFileName } from '../../../../constants'; -import { getLocalSettingsJson } from '../../../../funcConfig/local.settings'; -import { localize } from '../../../../localize'; -import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; +import { ConnectionKey, ConnectionType, localSettingsFileName } from '../../../../../constants'; +import { getLocalSettingsJson } from '../../../../../funcConfig/local.settings'; +import { localize } from '../../../../../localize'; +import { type IDTSConnectionWizardContext } from '../IDTSConnectionWizardContext'; export class DTSHubNameCustomPromptStep extends AzureWizardPromptStep { public async prompt(context: T): Promise { - context.newDTSHubName = (await context.ui.showInputBox({ + context.newDTSHubNameConnectionSetting = (await context.ui.showInputBox({ prompt: localize('customDTSConnectionPrompt', 'Provide the custom DTS hub name.'), value: await getDTSHubName(context, context.projectPath), validateInput: (value: string) => this.validateInput(value) @@ -20,7 +20,7 @@ export class DTSHubNameCustomPromptStep e } public shouldPrompt(context: T): boolean { - return !context.newDTSHubName && context.dtsConnectionType === ConnectionType.Custom; + return !context.newDTSHubNameConnectionSetting && context.dtsConnectionType === ConnectionType.Custom; } private validateInput(name: string): string | undefined { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/emulator/DTSEmulatorStartStep.ts similarity index 72% rename from src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts rename to src/commands/appSettings/connectionSettings/durableTaskScheduler/emulator/DTSEmulatorStartStep.ts index a94a68600..f6d8a6bab 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/emulator/DTSEmulatorStartStep.ts @@ -5,10 +5,10 @@ import { AzureWizardExecuteStep, nonNullValue } from '@microsoft/vscode-azext-utils'; import { commands } from 'vscode'; -import { ConnectionType } from '../../../../constants'; -import { localize } from '../../../../localize'; -import { type DurableTaskSchedulerEmulator } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; -import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; +import { ConnectionType } from '../../../../../constants'; +import { localize } from '../../../../../localize'; +import { type DurableTaskSchedulerEmulator } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; +import { type IDTSConnectionWizardContext } from '../IDTSConnectionWizardContext'; export class DTSEmulatorStartStep extends AzureWizardExecuteStep { public priority: number = 200; @@ -29,11 +29,11 @@ export class DTSEmulatorStartStep extends localize('couldNotFindEmulator', 'Internal error: Failed to retrieve info on the started DTS emulator.'), ); - context.newDTSConnection = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`; - context.newDTSHubName = 'default'; + context.newDTSConnectionSetting = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`; + context.newDTSHubNameConnectionSetting = 'default'; } public shouldExecute(context: T): boolean { - return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Emulator; + return !context.newDTSConnectionSetting && context.dtsConnectionType === ConnectionType.Emulator; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts new file mode 100644 index 000000000..533178195 --- /dev/null +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type StringDictionary } from "@azure/arm-appservice"; +import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; +import { AzureWizard, nonNullValue, nonNullValueAndProp, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; +import { CodeAction, ConnectionKey, ConnectionType } from "../../../../constants"; +import { ext } from "../../../../extensionVariables"; +import { getLocalSettingsConnectionString } from "../../../../funcConfig/local.settings"; +import { localize } from "../../../../localize"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IFuncDeployContext } from "../../../deploy/deploy"; +import { DTSConnectionTypeListStep } from "./DTSConnectionTypeListStep"; +import { type IDTSAzureConnectionWizardContext } from "./IDTSConnectionWizardContext"; + +type ValidateDTSConnectionContext = IFuncDeployContext & ISubscriptionActionContext & { subscription: AzureSubscription }; + +export async function validateDTSConnection(context: ValidateDTSConnectionContext, client: SiteClient, projectPath: string): Promise { + const app: StringDictionary = await client.listApplicationSettings(); + + const remoteDTSConnection: string | undefined = app?.properties?.[ConnectionKey.DTS]; + const remoteDTSHubName: string | undefined = app?.properties?.[ConnectionKey.DTSHub]; + + if (remoteDTSConnection && remoteDTSHubName) { + return; + } + + const localDTSConnection: string | undefined = await getLocalSettingsConnectionString(context, ConnectionKey.DTS, projectPath); + const localDTSHubName: string | undefined = await getLocalSettingsConnectionString(context, ConnectionKey.DTSHub, projectPath); + const availableDeployConnectionTypes = new Set([ConnectionType.Azure]); + + const wizardContext: IDTSAzureConnectionWizardContext = { + ...context, + subscription: context.subscription, + projectPath, + action: CodeAction.Deploy, + dtsConnectionType: ConnectionType.Azure, + dts: remoteDTSConnection ? await getDTSResource(context, nonNullValue(tryGetDTSEndpoint(remoteDTSConnection))) : undefined, + suggestedDTSEndpointLocalSettings: tryGetDTSEndpoint(localDTSConnection), + suggestedDTSHub: localDTSHubName, + }; + + const wizard: AzureWizard = new AzureWizard(wizardContext, { + promptSteps: [new DTSConnectionTypeListStep(availableDeployConnectionTypes)], + }); + + await wizard.prompt(); + await wizard.execute(); +} + +async function getDTSResource(context: ValidateDTSConnectionContext, dtsEndpoint: string): Promise { + try { + const client = new HttpDurableTaskSchedulerClient(); + const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulers(context.subscription, nonNullValueAndProp(context.resourceGroup, 'name')) ?? []; + return schedulers.find(s => s.properties.endpoint === dtsEndpoint); + } catch (e) { + const pe: IParsedError = parseError(e); + ext.outputChannel.appendLog(localize('failedToFetchDTS', 'Failed to fetch remote DTS resource with endpoint: "{0}"', dtsEndpoint)); + ext.outputChannel.appendLog(pe.message); + return undefined; + } +} + +// Example connection: "Endpoint=https://mwf-dts1-cghxbwa9drf6bzh.westus2.durabletask.io;Authentication=ManagedIdentity;ClientID=" +// Example endpoint: "https://mwf-dts1-cghxbwa9drf6bzh.westus2.durabletask.io" +function tryGetDTSEndpoint(dtsConnection: string | undefined): string | undefined { + if (!dtsConnection) { + return undefined; + } + + const endpointMatch = dtsConnection.match(/Endpoint=(^;)+/); + if (!endpointMatch) { + return undefined; + } + + return endpointMatch[1]; +} diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 56cc1b825..2dac800ec 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -5,7 +5,7 @@ import { type Site, type SiteConfigResource, type StringDictionary } from '@azure/arm-appservice'; import { getDeployFsPath, getDeployNode, deploy as innerDeploy, showDeployConfirmation, type IDeployContext, type IDeployPaths } from '@microsoft/vscode-azext-azureappservice'; -import { DialogResponses, type ExecuteActivityContext, type IActionContext, type ISubscriptionActionContext } from '@microsoft/vscode-azext-utils'; +import { DialogResponses, subscriptionExperience, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import type * as vscode from 'vscode'; import { CodeAction, ConnectionType, deploySubpathSetting, DurableBackend, hostFileName, ProjectLanguage, remoteBuildSetting, ScmType, stackUpgradeLearnMoreLink, type DurableBackendValues } from '../../constants'; @@ -23,6 +23,7 @@ import { treeUtils } from '../../utils/treeUtils'; import { getWorkspaceSetting } from '../../vsCodeConfig/settings'; import { verifyInitForVSCode } from '../../vsCodeConfig/verifyInitForVSCode'; import { type ISetConnectionSettingContext } from '../appSettings/connectionSettings/ISetConnectionSettingContext'; +import { validateDTSConnection } from '../appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection'; import { validateEventHubsConnection } from '../appSettings/connectionSettings/eventHubs/validateEventHubsConnection'; import { validateSqlDbConnection } from '../appSettings/connectionSettings/sqlDatabase/validateSqlDbConnection'; import { getEolWarningMessages } from '../createFunctionApp/stacks/getStackPicks'; @@ -83,6 +84,15 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | return await getOrCreateFunctionApp(context) }); + const subscriptionContext: ISubscriptionContext & { subscription: AzureSubscription } = { + ...node.subscription, + subscription: await subscriptionExperience(context, ext.rgApiV2.resources.azureResourceTreeDataProvider, { + selectBySubscriptionId: node.subscription.subscriptionId, + }), + }; + + Object.assign(context, { ...subscriptionContext, resourceGroup: node.site.resourceGroup }); + if (node.contextValue.includes('container')) { const learnMoreLink: string = 'https://aka.ms/deployContainerApps' await context.ui.showWarningMessage(localize('containerFunctionAppError', 'Deploy is not currently supported for containerized function apps within the Azure Functions extension. Please read here to learn how to deploy your project.'), { learnMoreLink }); @@ -131,11 +141,14 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | } const durableStorageType: DurableBackendValues | undefined = await durableUtils.getStorageTypeFromWorkspace(language, context.projectPath); - context.telemetry.properties.projectDurableStorageType = durableStorageType; + context.telemetry.properties.durableStorageType = durableStorageType; const { shouldValidateEventHubs, shouldValidateSqlDb } = await shouldValidateConnections(durableStorageType, client, context.projectPath); // Preliminary local validation done to ensure all required resources have been created and are available. Final deploy writes are made in 'verifyAppSettings' + if (durableStorageType === DurableBackend.DTS) { + await validateDTSConnection(Object.assign(context, subscriptionContext), client, context.projectPath); + } if (shouldValidateEventHubs) { await validateEventHubsConnection(context, context.projectPath, { preselectedConnectionType: ConnectionType.Azure }); } @@ -156,11 +169,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | deploymentWarningMessages.push(connectionStringWarningMessage); } - const subContext = { - ...context - } as unknown as ISubscriptionActionContext; - - const eolWarningMessage = await getEolWarningMessages(subContext, { + const eolWarningMessage = await getEolWarningMessages({ ...context, ...subscriptionContext }, { site: node.site.rawSite, isLinux: client.isLinux, isFlex: isFlexConsumption, diff --git a/src/commands/deploy/shouldValidateConnection.ts b/src/commands/deploy/shouldValidateConnection.ts index 2925def83..7925768c8 100644 --- a/src/commands/deploy/shouldValidateConnection.ts +++ b/src/commands/deploy/shouldValidateConnection.ts @@ -3,18 +3,22 @@ import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; import { ConnectionKey, DurableBackend, type DurableBackendValues } from "../../constants"; import { getEventHubName } from "../appSettings/connectionSettings/eventHubs/validateEventHubsConnection"; -export interface IShouldValidateConnection { +type ShouldValidateConnections = { shouldValidateEventHubs: boolean; shouldValidateSqlDb: boolean; } -export async function shouldValidateConnections(durableStorageType: DurableBackendValues | undefined, client: SiteClient, projectPath: string): Promise { +export async function shouldValidateConnections(durableStorageType: DurableBackendValues | undefined, client: SiteClient, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); + + // Event Hubs const remoteEventHubsConnection: string | undefined = app?.properties?.[ConnectionKey.EventHubs]; - const remoteSqlDbConnection: string | undefined = app?.properties?.[ConnectionKey.SQL]; const eventHubName: string | undefined = await getEventHubName(projectPath); - const shouldValidateEventHubs: boolean = durableStorageType === DurableBackend.Netherite && (!eventHubName || !remoteEventHubsConnection); + + // SQL + const remoteSqlDbConnection: string | undefined = app?.properties?.[ConnectionKey.SQL]; const shouldValidateSqlDb: boolean = durableStorageType === DurableBackend.SQL && !remoteSqlDbConnection; + return { shouldValidateEventHubs, shouldValidateSqlDb }; } diff --git a/src/constants-nls.ts b/src/constants-nls.ts index 0e52a994a..299509992 100644 --- a/src/constants-nls.ts +++ b/src/constants-nls.ts @@ -4,6 +4,7 @@ export const viewOutput: string = localize('viewOutput', 'View Output'); export const defaultDescription: string = localize('default', '(Default)'); export const recommendedDescription: string = localize('recommended', '(Recommended)'); export const previewDescription: string = localize('preview', '(Preview)'); +export const localSettingsDescription: string = localize('localSettings', '(Local Settings)'); export const pythonNewModelPreview: string = localize('pythonNewModelPreview', 'Python (Programming Model V2)'); export const useEmulator: string = localize('useEmulator', 'Use Local Emulator'); diff --git a/src/tree/SubscriptionTreeItem.ts b/src/tree/SubscriptionTreeItem.ts index fdc4ccb5c..bde696645 100644 --- a/src/tree/SubscriptionTreeItem.ts +++ b/src/tree/SubscriptionTreeItem.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type Site, type WebSiteManagementClient } from '@azure/arm-appservice'; +import { type ResourceGroup } from '@azure/arm-resources'; import { SubscriptionTreeItemBase, uiUtils } from '@microsoft/vscode-azext-azureutils'; import { AzureWizard, parseError, type AzExtTreeItem, type IActionContext, type ICreateChildImplContext } from '@microsoft/vscode-azext-utils'; import { type WorkspaceFolder } from 'vscode'; @@ -24,6 +25,7 @@ import { ResolvedContainerizedFunctionAppResource } from './containerizedFunctio import { isProjectCV, isRemoteProjectCV } from './projectContextValues'; export interface ICreateFunctionAppContext extends ICreateChildImplContext { + resourceGroup?: ResourceGroup; newResourceGroupName?: string; workspaceFolder?: WorkspaceFolder; dockerfilePath?: string; diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts index a3d54f178..9f012edda 100644 --- a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts +++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { type AzureAuthentication, type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -import { localize } from '../../localize'; import { type CancellationToken } from "vscode"; +import { localize } from '../../localize'; import { delay } from "../../utils/delay"; interface FetchOptions { @@ -73,6 +73,7 @@ export interface DurableTaskSchedulerClient { deleteTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getScheduler(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; + getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise; getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; @@ -100,7 +101,7 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien request, subscription.authentication); - return { + return { scheduler: response.value, status: this.createStatus(response.asyncOperation, subscription.authentication) }; @@ -146,6 +147,14 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien return scheduler; } + async getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise { + const schedulerUrl = HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName); + + const schedulers = await this.getAsJson(schedulerUrl, subscription.authentication); + + return schedulers; + } + async getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise { const taskHubUrl = HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName, schedulerName, `/taskHubs/${taskHubName}`); @@ -162,11 +171,15 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien return response?.value ?? []; } - private static getBaseUrl(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, relativeUrl?: string | undefined) { + private static getBaseUrl(subscription: AzureSubscription, resourceGroupName: string, schedulerName?: string, relativeUrl?: string | undefined) { const provider = 'Microsoft.DurableTask'; const apiVersion = '2024-10-01-preview'; - let url = `${subscription.environment.resourceManagerEndpointUrl}subscriptions/${subscription.subscriptionId}/resourceGroups/${resourceGroupName}/providers/${provider}/schedulers/${schedulerName}`; + let url = `${subscription.environment.resourceManagerEndpointUrl}subscriptions/${subscription.subscriptionId}/resourceGroups/${resourceGroupName}/providers/${provider}/schedulers`; + + if (schedulerName) { + url += `/${schedulerName}`; + } if (relativeUrl) { url += relativeUrl; @@ -285,8 +298,7 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien const status = await get(); - if (status === true) - { + if (status === true) { return true; } From 616f84b70b2c3f08e0dca376f3980f041dcd5972 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:56:33 -0700 Subject: [PATCH 22/52] Remove extra step --- .../durableTaskScheduler/DTSConnectionTypeListStep.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts index 3fd6450ea..f20edb70a 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -10,7 +10,6 @@ import { ConnectionType, DurableTaskProvider } from '../../../../constants'; import { useEmulator } from '../../../../constants-nls'; import { localize } from '../../../../localize'; import { HttpDurableTaskSchedulerClient } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; -import { DurableTaskHubListStep } from './azure/DurableTaskHubListStep'; import { DurableTaskSchedulerListStep } from './azure/DurableTaskSchedulerListStep'; import { DTSConnectionCustomPromptStep } from './custom/DTSConnectionCustomPromptStep'; import { DTSHubNameCustomPromptStep } from './custom/DTSHubNameCustomPromptStep'; @@ -60,10 +59,7 @@ export class DTSConnectionTypeListStep ex switch (context.dtsConnectionType) { case ConnectionType.Azure: const client = new HttpDurableTaskSchedulerClient(); - promptSteps.push( - new DurableTaskSchedulerListStep(client), - new DurableTaskHubListStep(client) - ); + promptSteps.push(new DurableTaskSchedulerListStep(client)); executeSteps.push(new VerifyProvidersStep([DurableTaskProvider])); break; case ConnectionType.Emulator: From a0f194bc87a6e9753f9fe198ee87e65368f6a9fd Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:08:27 -0700 Subject: [PATCH 23/52] Don't need to update this file --- src/commands/deploy/shouldValidateConnection.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/commands/deploy/shouldValidateConnection.ts b/src/commands/deploy/shouldValidateConnection.ts index 7925768c8..2925def83 100644 --- a/src/commands/deploy/shouldValidateConnection.ts +++ b/src/commands/deploy/shouldValidateConnection.ts @@ -3,22 +3,18 @@ import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; import { ConnectionKey, DurableBackend, type DurableBackendValues } from "../../constants"; import { getEventHubName } from "../appSettings/connectionSettings/eventHubs/validateEventHubsConnection"; -type ShouldValidateConnections = { +export interface IShouldValidateConnection { shouldValidateEventHubs: boolean; shouldValidateSqlDb: boolean; } -export async function shouldValidateConnections(durableStorageType: DurableBackendValues | undefined, client: SiteClient, projectPath: string): Promise { +export async function shouldValidateConnections(durableStorageType: DurableBackendValues | undefined, client: SiteClient, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); - - // Event Hubs const remoteEventHubsConnection: string | undefined = app?.properties?.[ConnectionKey.EventHubs]; + const remoteSqlDbConnection: string | undefined = app?.properties?.[ConnectionKey.SQL]; const eventHubName: string | undefined = await getEventHubName(projectPath); - const shouldValidateEventHubs: boolean = durableStorageType === DurableBackend.Netherite && (!eventHubName || !remoteEventHubsConnection); - // SQL - const remoteSqlDbConnection: string | undefined = app?.properties?.[ConnectionKey.SQL]; + const shouldValidateEventHubs: boolean = durableStorageType === DurableBackend.Netherite && (!eventHubName || !remoteEventHubsConnection); const shouldValidateSqlDb: boolean = durableStorageType === DurableBackend.SQL && !remoteSqlDbConnection; - return { shouldValidateEventHubs, shouldValidateSqlDb }; } From 5dfb8694859442fdab8a8bc26e41a7bd5758c2d4 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:12:33 -0700 Subject: [PATCH 24/52] Standardize name casing --- .../durableTaskScheduler/azure/DurableTaskHubListStep.ts | 2 +- .../durableTaskScheduler/azure/DurableTaskHubNameStep.ts | 2 +- .../durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts | 2 +- .../durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts index 25ad44a90..56d4d1524 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts @@ -46,7 +46,7 @@ export class DurableTaskHubListStep await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), resourceGroupName, context.dts.name) : []; const createPick = { - label: localize('createTaskHub', '$(plus) Create new Durable Task Hub'), + label: localize('createTaskHub', '$(plus) Create new durable task hub'), data: undefined, }; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index 7befa2f84..1955d3aef 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -18,7 +18,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'), + prompt: localize('taskSchedulerName', 'Enter a name for the durable task hub'), value: context.suggestedDTSHub, // Todo: validation }); diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index 1b84938ef..0e4b26626 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -50,7 +50,7 @@ export class DurableTaskSchedulerListStep { context.newDTSName = await context.ui.showInputBox({ - prompt: localize('taskSchedulerName', 'Enter a name for the Durable Task Scheduler'), + prompt: localize('taskSchedulerName', 'Enter a name for the durable task scheduler'), // Todo: validation }); From be7032e472997d275baf9518d51c4af6f53da0cf Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:07:39 -0700 Subject: [PATCH 25/52] Unexpected code path --- .../durableTaskScheduler/DTSConnectionTypeListStep.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts index f20edb70a..29af9b8d2 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -71,6 +71,8 @@ export class DTSConnectionTypeListStep ex new DTSHubNameCustomPromptStep(), ); break; + default: + throw new Error(localize('unexpectedConnectionType', 'Internal error: Unexpected DTS connection type encountered: "{0}".', context.dtsConnectionType)); } executeSteps.push( From 4284d2323980cc91196cd8bf93bf40e9a1db03b0 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:24:32 -0700 Subject: [PATCH 26/52] Misc --- .../durableTaskScheduler/DTSConnectionTypeListStep.ts | 4 +--- .../durableTaskScheduler/IDTSConnectionWizardContext.ts | 2 +- .../durableTaskScheduler/azure/DurableTaskHubListStep.ts | 2 +- .../durableTaskScheduler/azure/DurableTaskHubNameStep.ts | 2 +- .../durableTaskScheduler/validateDTSConnection.ts | 3 ++- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts index 29af9b8d2..a5b390b30 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -9,7 +9,6 @@ import { type MessageItem } from 'vscode'; import { ConnectionType, DurableTaskProvider } from '../../../../constants'; import { useEmulator } from '../../../../constants-nls'; import { localize } from '../../../../localize'; -import { HttpDurableTaskSchedulerClient } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; import { DurableTaskSchedulerListStep } from './azure/DurableTaskSchedulerListStep'; import { DTSConnectionCustomPromptStep } from './custom/DTSConnectionCustomPromptStep'; import { DTSHubNameCustomPromptStep } from './custom/DTSHubNameCustomPromptStep'; @@ -58,8 +57,7 @@ export class DTSConnectionTypeListStep ex switch (context.dtsConnectionType) { case ConnectionType.Azure: - const client = new HttpDurableTaskSchedulerClient(); - promptSteps.push(new DurableTaskSchedulerListStep(client)); + promptSteps.push(new DurableTaskSchedulerListStep()); executeSteps.push(new VerifyProvidersStep([DurableTaskProvider])); break; case ConnectionType.Emulator: diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index 69e59f7cc..817f6aec8 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -25,7 +25,7 @@ export interface IDTSAzureConnectionWizardContext extends ISubscriptionActionCon resourceGroup?: ResourceGroup; suggestedDTSEndpointLocalSettings?: string; - suggestedDTSHub?: string; + suggestedDTSHubNameLocalSettings?: string; newDTSName?: string; dts?: DurableTaskSchedulerResource; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts index 56d4d1524..e600434a9 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts @@ -55,7 +55,7 @@ export class DurableTaskHubListStep ...taskHubs.map(h => { return { label: h.name, - description: h.name === context.suggestedDTSHub ? localSettingsDescription : undefined, + description: h.name === context.suggestedDTSHubNameLocalSettings ? localSettingsDescription : undefined, data: h, }; }), diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index 1955d3aef..f3079b266 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -19,7 +19,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.suggestedDTSHub, + value: context.suggestedDTSHubNameLocalSettings, // Todo: validation }); diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index 533178195..aa4c846df 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -39,8 +39,9 @@ export async function validateDTSConnection(context: ValidateDTSConnectionContex action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, dts: remoteDTSConnection ? await getDTSResource(context, nonNullValue(tryGetDTSEndpoint(remoteDTSConnection))) : undefined, + // If the local settings are using the emulator (i.e. localhost), it's fine because it won't match up with any remote resources and the suggestion will end up hidden from the user suggestedDTSEndpointLocalSettings: tryGetDTSEndpoint(localDTSConnection), - suggestedDTSHub: localDTSHubName, + suggestedDTSHubNameLocalSettings: localDTSHubName, }; const wizard: AzureWizard = new AzureWizard(wizardContext, { From 6d19b79498d386875c189f522bda4b4acce22ed6 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:10:55 -0700 Subject: [PATCH 27/52] Fix some stuff, add location + todo --- .../IDTSConnectionWizardContext.ts | 7 +++---- .../azure/DurableTaskSchedulerListStep.ts | 4 ++-- .../validateDTSConnection.ts | 18 ++++++++++++++---- src/commands/deploy/deploy.ts | 7 +++++-- .../DurableTaskSchedulerClient.ts | 8 ++++---- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index 817f6aec8..122a5e88f 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type ResourceGroup } from "@azure/arm-resources"; -import { type IActionContext, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { type IResourceGroupWizardContext } from "@microsoft/vscode-azext-azureutils"; +import { type IActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { type ConnectionType } from "../../../../constants"; import { type DurableTaskHubResource, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; @@ -20,9 +20,8 @@ export interface IDTSConnectionWizardContext extends IActionContext, ISetConnect newDTSHubNameConnectionSetting?: string; } -export interface IDTSAzureConnectionWizardContext extends ISubscriptionActionContext, IDTSConnectionWizardContext { +export interface IDTSAzureConnectionWizardContext extends IResourceGroupWizardContext, IDTSConnectionWizardContext { subscription?: AzureSubscription; - resourceGroup?: ResourceGroup; suggestedDTSEndpointLocalSettings?: string; suggestedDTSHubNameLocalSettings?: string; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index 0e4b26626..f578baf36 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -22,7 +22,7 @@ export class DurableTaskSchedulerListStep { context.dts = (await context.ui.showQuickPick(await this.getPicks(context), { - placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'), + placeHolder: localize('selectTaskScheduler', 'Select a durable task scheduler'), })).data; context.telemetry.properties.usedLocalDTSConnectionSettings = context.suggestedDTSEndpointLocalSettings ? String(context.dts?.properties.endpoint === context.suggestedDTSEndpointLocalSettings) : undefined; @@ -47,7 +47,7 @@ export class DurableTaskSchedulerListStep[]> { const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); - const schedulers: DurableTaskSchedulerResource[] = await this.schedulerClient.getSchedulers(nonNullProp(context, 'subscription'), resourceGroupName) ?? []; + const schedulers: DurableTaskSchedulerResource[] = await this.schedulerClient.getSchedulers(nonNullProp(context, 'subscription'), resourceGroupName); const createPick = { label: localize('createTaskScheduler', '$(plus) Create new durable task scheduler'), diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index aa4c846df..769df9e60 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -5,13 +5,15 @@ import { type StringDictionary } from "@azure/arm-appservice"; import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; -import { AzureWizard, nonNullValue, nonNullValueAndProp, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { LocationListStep } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizard, nonNullValue, nonNullValueAndProp, parseError, type AzureWizardPromptStep, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -import { CodeAction, ConnectionKey, ConnectionType } from "../../../../constants"; +import { CodeAction, ConnectionKey, ConnectionType, DurableTaskProvider, DurableTaskSchedulersResourceType } from "../../../../constants"; import { ext } from "../../../../extensionVariables"; import { getLocalSettingsConnectionString } from "../../../../funcConfig/local.settings"; import { localize } from "../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { createActivityContext } from "../../../../utils/activityUtils"; import { type IFuncDeployContext } from "../../../deploy/deploy"; import { DTSConnectionTypeListStep } from "./DTSConnectionTypeListStep"; import { type IDTSAzureConnectionWizardContext } from "./IDTSConnectionWizardContext"; @@ -34,6 +36,7 @@ export async function validateDTSConnection(context: ValidateDTSConnectionContex const wizardContext: IDTSAzureConnectionWizardContext = { ...context, + ...await createActivityContext(), subscription: context.subscription, projectPath, action: CodeAction.Deploy, @@ -44,8 +47,15 @@ export async function validateDTSConnection(context: ValidateDTSConnectionContex suggestedDTSHubNameLocalSettings: localDTSHubName, }; + const promptSteps: AzureWizardPromptStep[] = [new DTSConnectionTypeListStep(availableDeployConnectionTypes)]; + // Should we prompt or should we set for all resources based on node.site.location? + LocationListStep.addProviderForFiltering(wizardContext, DurableTaskProvider, DurableTaskSchedulersResourceType); + LocationListStep.addStep(wizardContext, promptSteps); + const wizard: AzureWizard = new AzureWizard(wizardContext, { - promptSteps: [new DTSConnectionTypeListStep(availableDeployConnectionTypes)], + title: localize('createAndAssignDTS', 'Create and assign DTS resources'), + promptSteps, + showLoadingPrompt: true, }); await wizard.prompt(); @@ -72,7 +82,7 @@ function tryGetDTSEndpoint(dtsConnection: string | undefined): string | undefine return undefined; } - const endpointMatch = dtsConnection.match(/Endpoint=(^;)+/); + const endpointMatch = dtsConnection.match(/Endpoint=([^;]+)/); if (!endpointMatch) { return undefined; } diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 2dac800ec..5b69bd656 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -5,6 +5,7 @@ import { type Site, type SiteConfigResource, type StringDictionary } from '@azure/arm-appservice'; import { getDeployFsPath, getDeployNode, deploy as innerDeploy, showDeployConfirmation, type IDeployContext, type IDeployPaths } from '@microsoft/vscode-azext-azureappservice'; +import { ResourceGroupListStep } from '@microsoft/vscode-azext-azureutils'; import { DialogResponses, subscriptionExperience, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import type * as vscode from 'vscode'; @@ -91,7 +92,9 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | }), }; - Object.assign(context, { ...subscriptionContext, resourceGroup: node.site.resourceGroup }); + Object.assign(context, { + resourceGroup: (await ResourceGroupListStep.getResourceGroups(Object.assign(context, subscriptionContext))).find(rg => rg.name === node.site.resourceGroup), + }); if (node.contextValue.includes('container')) { const learnMoreLink: string = 'https://aka.ms/deployContainerApps' @@ -140,7 +143,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | context.deployMethod = 'flexconsumption'; } - const durableStorageType: DurableBackendValues | undefined = await durableUtils.getStorageTypeFromWorkspace(language, context.projectPath); + const durableStorageType: DurableBackend | undefined = await durableUtils.getStorageTypeFromWorkspace(language, context.projectPath); context.telemetry.properties.durableStorageType = durableStorageType; const { shouldValidateEventHubs, shouldValidateSqlDb } = await shouldValidateConnections(durableStorageType, client, context.projectPath); diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts index 9f012edda..62a3837dd 100644 --- a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts +++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts @@ -73,7 +73,7 @@ export interface DurableTaskSchedulerClient { deleteTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getScheduler(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; - getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise; + getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise; getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; @@ -147,12 +147,12 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien return scheduler; } - async getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise { + async getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise { const schedulerUrl = HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName); - const schedulers = await this.getAsJson(schedulerUrl, subscription.authentication); + const schedulers = await this.getAsJson<{ value: DurableTaskSchedulerResource[] }>(schedulerUrl, subscription.authentication); - return schedulers; + return schedulers?.value ?? []; } async getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise { From 34258ac9a94bba47b5d0dc3add499ddde196a1ba Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:11:26 -0700 Subject: [PATCH 28/52] Misc --- .../ISetConnectionSettingContext.ts | 6 ++- .../SetConnectionSettingStepBase.ts | 4 +- .../AzureWebJobsStorageExecuteStep.ts | 4 +- .../azure/DurableTaskHubCreateStep.ts | 1 + .../azure/DurableTaskHubListStep.ts | 5 ++ .../azure/DurableTaskHubNameStep.ts | 7 ++- .../azure/DurableTaskSchedulerCreateStep.ts | 20 ++++++-- .../azure/DurableTaskSchedulerListStep.ts | 15 +++++- .../azure/DurableTaskSchedulerNameStep.ts | 1 - .../validateDTSConnection.ts | 46 +++++++++++++------ .../EventHubsConnectionExecuteStep.ts | 4 +- .../SqlDatabaseConnectionExecuteStep.ts | 4 +- .../DurableStorageTypePromptStep.ts | 6 +-- .../IFunctionAppWizardContext.ts | 4 +- src/commands/deploy/deploy.ts | 8 ++-- .../deploy/shouldValidateConnection.ts | 4 +- src/commands/deploy/verifyAppSettings.ts | 15 ++++-- .../durableTaskScheduler/createTaskHub.ts | 20 ++++---- src/constants.ts | 4 -- src/funcConfig/local.settings.ts | 4 +- src/utils/durableUtils.ts | 6 +-- 21 files changed, 121 insertions(+), 67 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts b/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts index 99b0127ea..c684a0ab0 100644 --- a/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts +++ b/src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts @@ -4,15 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { type IActionContext } from "@microsoft/vscode-azext-utils"; -import { type CodeActionValues, type ConnectionKey } from "../../../constants"; +import { type CodeAction, type ConnectionKey } from "../../../constants"; import { type IConnectionTypesContext } from "./IConnectionTypesContext"; export interface ISetConnectionSettingContext extends IActionContext, IConnectionTypesContext { - action: CodeActionValues; + action: CodeAction; projectPath: string; // Remote connections for deploy [ConnectionKey.Storage]?: string; [ConnectionKey.EventHubs]?: string; + [ConnectionKey.DTS]?: string; + [ConnectionKey.DTSHub]?: string; [ConnectionKey.SQL]?: string; } diff --git a/src/commands/appSettings/connectionSettings/SetConnectionSettingStepBase.ts b/src/commands/appSettings/connectionSettings/SetConnectionSettingStepBase.ts index 8306c7f78..7b4e2900e 100644 --- a/src/commands/appSettings/connectionSettings/SetConnectionSettingStepBase.ts +++ b/src/commands/appSettings/connectionSettings/SetConnectionSettingStepBase.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; -import { CodeAction, type ConnectionKeyValues } from "../../../constants"; +import { CodeAction, type ConnectionKey } from "../../../constants"; import { MismatchBehavior, setLocalAppSetting } from "../../../funcConfig/local.settings"; import { type ISetConnectionSettingContext } from "./ISetConnectionSettingContext"; export abstract class SetConnectionSettingStepBase extends AzureWizardExecuteStep { - public abstract readonly debugDeploySetting: ConnectionKeyValues; + public abstract readonly debugDeploySetting: ConnectionKey; protected async setConnectionSetting(context: T, value: string): Promise { if (context.action === CodeAction.Deploy) { diff --git a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStorageExecuteStep.ts b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStorageExecuteStep.ts index bf34e5fe8..f302ef5f1 100644 --- a/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStorageExecuteStep.ts +++ b/src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStorageExecuteStep.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { type IStorageAccountWizardContext } from '@microsoft/vscode-azext-azureutils'; -import { ConnectionKey, ConnectionType, localStorageEmulatorConnectionString, type ConnectionKeyValues } from '../../../../constants'; +import { ConnectionKey, ConnectionType, localStorageEmulatorConnectionString } from '../../../../constants'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; import { getStorageConnectionString } from '../getLocalConnectionSetting'; import { type IAzureWebJobsStorageWizardContext } from './IAzureWebJobsStorageWizardContext'; export class AzureWebJobsStorageExecuteStep extends SetConnectionSettingStepBase { public priority: number = 230; - public debugDeploySetting: ConnectionKeyValues = ConnectionKey.Storage; + public debugDeploySetting: ConnectionKey = ConnectionKey.Storage; public async execute(context: T): Promise { let value: string; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts index 0aead6e77..d9699c6bc 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubCreateStep.ts @@ -27,6 +27,7 @@ export class DurableTaskHubCreateStep context.dtsHub = (await context.ui.showQuickPick(await this.getPicks(context), { placeHolder: localize('selectTaskScheduler', 'Select a Durable Task Scheduler'), })).data; + + if (context.dtsHub) { + context.newDTSHubNameConnectionSetting = context.dtsHub.name; + context.valuesToMask.push(context.dtsHub.name); + } } public shouldPrompt(context: T): boolean { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index f3079b266..f70770bd7 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -23,7 +23,12 @@ export class DurableTaskHubNameStep // Todo: validation }); - context.valuesToMask.push(context.newDTSHubName); + context.telemetry.properties.usedDTSDefaultHub = 'true'; + + if (context.newDTSHubName && context.newDTSHubName !== 'default') { + context.telemetry.properties.usedDTSDefaultHub = 'false'; + context.valuesToMask.push(context.newDTSHubName); + } } public shouldPrompt(context: T): boolean { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts index 093cf4379..8fa4f0d10 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts @@ -8,29 +8,39 @@ import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@micro import { type Progress } from "vscode"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { withCancellation } from "../../../../../utils/cancellation"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; export class DurableTaskSchedulerCreateStep extends AzureWizardExecuteStep { priority: number = 150; private readonly schedulerClient: DurableTaskSchedulerClient; - constructor(schedulerClient?: DurableTaskSchedulerClient) { + public constructor(schedulerClient?: DurableTaskSchedulerClient) { super(); this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); } - async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { + public async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { progress.report({ message: localize('createTaskScheduler', 'Creating durable task scheduler...') }); - context.dts = (await this.schedulerClient.createScheduler( + const response = (await this.schedulerClient.createScheduler( nonNullProp(context, 'subscription'), nonNullValueAndProp(context.resourceGroup, 'name'), (await LocationListStep.getLocation(context)).name, nonNullProp(context, 'newDTSName'), - )).scheduler; + )); + + const status = await withCancellation(token => response.status.waitForCompletion(token), 1000 * 60 * 30); + + if (status !== true) { + throw new Error(localize('schedulerCreationFailed', 'The scheduler could not be created.')); + } + + context.dts = response.scheduler; + context.newDTSConnectionSetting = context.dts.properties.endpoint; } - shouldExecute(context: T): boolean { + public shouldExecute(context: T): boolean { return !context.dts; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index f578baf36..47aea75ed 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { LocationListStep, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { DurableTaskProvider, DurableTaskSchedulersResourceType } from '../../../../../constants'; import { localSettingsDescription } from '../../../../../constants-nls'; import { localize } from '../../../../../localize'; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; @@ -25,7 +27,13 @@ export class DurableTaskSchedulerListStep[] = []; if (!context.dts) { + // Note: The location offering for this provider isn't 1:1 with what's available for the function app + // Todo: We should probably update the behavior of LocationListStep so that we re-verify the provider locations even if the location is already set + LocationListStep.addProviderForFiltering(context as unknown as ILocationWizardContext, DurableTaskProvider, DurableTaskSchedulersResourceType); + LocationListStep.addStep(context, promptSteps as AzureWizardPromptStep[]); + promptSteps.push(new DurableTaskSchedulerNameStep(this.schedulerClient)); executeSteps.push(new DurableTaskSchedulerCreateStep(this.schedulerClient)); } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts index 6a1cc8b97..7c3a8b927 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -21,7 +21,6 @@ export class DurableTaskSchedulerNameStep { +export async function validateDTSConnection(context: ValidateDTSConnectionContext, client: SiteClient, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); const remoteDTSConnection: string | undefined = app?.properties?.[ConnectionKey.DTS]; const remoteDTSHubName: string | undefined = app?.properties?.[ConnectionKey.DTSHub]; if (remoteDTSConnection && remoteDTSHubName) { - return; + return undefined; } const localDTSConnection: string | undefined = await getLocalSettingsConnectionString(context, ConnectionKey.DTS, projectPath); const localDTSHubName: string | undefined = await getLocalSettingsConnectionString(context, ConnectionKey.DTSHub, projectPath); + const localDTSEndpoint: string | undefined = tryGetDTSEndpoint(localDTSConnection); + + const remoteDTSEndpoint: string | undefined = tryGetDTSEndpoint(remoteDTSConnection); const availableDeployConnectionTypes = new Set([ConnectionType.Azure]); + // Spread the properties onto a new wizardContext so that we can initiate a separate activity log entry const wizardContext: IDTSAzureConnectionWizardContext = { ...context, ...await createActivityContext(), - subscription: context.subscription, projectPath, action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, - dts: remoteDTSConnection ? await getDTSResource(context, nonNullValue(tryGetDTSEndpoint(remoteDTSConnection))) : undefined, + dts: remoteDTSEndpoint ? await getDTSResource(context, remoteDTSEndpoint) : undefined, // If the local settings are using the emulator (i.e. localhost), it's fine because it won't match up with any remote resources and the suggestion will end up hidden from the user - suggestedDTSEndpointLocalSettings: tryGetDTSEndpoint(localDTSConnection), + suggestedDTSEndpointLocalSettings: localDTSEndpoint ? tryGetDTSEndpoint(localDTSConnection) : undefined, suggestedDTSHubNameLocalSettings: localDTSHubName, }; - const promptSteps: AzureWizardPromptStep[] = [new DTSConnectionTypeListStep(availableDeployConnectionTypes)]; - // Should we prompt or should we set for all resources based on node.site.location? - LocationListStep.addProviderForFiltering(wizardContext, DurableTaskProvider, DurableTaskSchedulersResourceType); - LocationListStep.addStep(wizardContext, promptSteps); - const wizard: AzureWizard = new AzureWizard(wizardContext, { - title: localize('createAndAssignDTS', 'Create and assign DTS resources'), - promptSteps, + title: localize('getDTSResources', 'Get Durable Task Scheduler resources'), + promptSteps: [ + new ResourceGroupListStep(), + new DTSConnectionTypeListStep(availableDeployConnectionTypes), + ], showLoadingPrompt: true, }); await wizard.prompt(); await wizard.execute(); + + return { + [ConnectionKey.DTS]: wizardContext[ConnectionKey.DTS], + [ConnectionKey.DTSHub]: wizardContext[ConnectionKey.DTSHub], + }; } +// Check function app for user assigned identity, +// 1. if exists, assign it to context +// 2. If it doesn't exist we need to either select or create a new user assigned identity. Do we need to prompt for managed identity vs. secret - Lily said only MI connection supported +// For this bottom choice we can probably follow whatever the create function app logic is doing + +// After creating the new DTS, add a step to assign the Durable Task Scheduler Contributor role to the user assigned identity +// If not creating a new DTS, you can skip this +// After adding the role, we need to set the new connection for DTS which should include the client ID for the connection string + async function getDTSResource(context: ValidateDTSConnectionContext, dtsEndpoint: string): Promise { try { const client = new HttpDurableTaskSchedulerClient(); diff --git a/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionExecuteStep.ts b/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionExecuteStep.ts index 41cc067e1..510834a6e 100644 --- a/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionExecuteStep.ts +++ b/src/commands/appSettings/connectionSettings/eventHubs/EventHubsConnectionExecuteStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; -import { ConnectionKey, ConnectionType, localEventHubsEmulatorConnectionRegExp, localEventHubsEmulatorConnectionString, type ConnectionKeyValues } from '../../../../constants'; +import { ConnectionKey, ConnectionType, localEventHubsEmulatorConnectionRegExp, localEventHubsEmulatorConnectionString } from '../../../../constants'; import { getLocalSettingsConnectionString } from '../../../../funcConfig/local.settings'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; import { getEventHubsConnectionString } from '../getLocalConnectionSetting'; @@ -12,7 +12,7 @@ import { type IEventHubsConnectionWizardContext } from './IEventHubsConnectionWi export class EventHubsConnectionExecuteStep extends SetConnectionSettingStepBase { public priority: number = 240; - public debugDeploySetting: ConnectionKeyValues = ConnectionKey.EventHubs; + public debugDeploySetting: ConnectionKey = ConnectionKey.EventHubs; public async execute(context: T): Promise { let value: string; diff --git a/src/commands/appSettings/connectionSettings/sqlDatabase/SqlDatabaseConnectionExecuteStep.ts b/src/commands/appSettings/connectionSettings/sqlDatabase/SqlDatabaseConnectionExecuteStep.ts index f6f3a3c96..a2a21beab 100644 --- a/src/commands/appSettings/connectionSettings/sqlDatabase/SqlDatabaseConnectionExecuteStep.ts +++ b/src/commands/appSettings/connectionSettings/sqlDatabase/SqlDatabaseConnectionExecuteStep.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { nonNullProp } from '@microsoft/vscode-azext-utils'; -import { ConnectionKey, ConnectionType, type ConnectionKeyValues } from '../../../../constants'; +import { ConnectionKey, ConnectionType } from '../../../../constants'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; import { getSqlDatabaseConnectionString } from '../getLocalConnectionSetting'; import { type ISqlDatabaseConnectionWizardContext } from './ISqlDatabaseConnectionWizardContext'; export class SqlDatabaseConnectionExecuteStep extends SetConnectionSettingStepBase { public priority: number = 250; - public debugDeploySetting: ConnectionKeyValues = ConnectionKey.SQL; + public debugDeploySetting: ConnectionKey = ConnectionKey.SQL; public async execute(context: T): Promise { let value: string; diff --git a/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts b/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts index 162425195..516312bcf 100644 --- a/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts +++ b/src/commands/createFunction/durableSteps/DurableStorageTypePromptStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizardPromptStep, openUrl, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils"; -import { DurableBackend, type DurableBackendValues } from "../../../constants"; +import { DurableBackend } from "../../../constants"; import { defaultDescription, previewDescription } from "../../../constants-nls"; import { localize } from "../../../localize"; import { FunctionSubWizard } from "../FunctionSubWizard"; @@ -22,14 +22,14 @@ export class DurableStorageTypePromptStep exte const durableStorageInfo: string = localize('durableStorageInfo', '$(link-external) Learn more about the tradeoffs between storage providers'); const placeHolder: string = localize('chooseDurableStorageType', 'Choose a durable storage type.'); - const picks: IAzureQuickPickItem[] = [ + const picks: IAzureQuickPickItem[] = [ { label: 'Azure Storage', description: defaultDescription, data: DurableBackend.Storage }, { label: 'Durable Task Scheduler', description: previewDescription, data: DurableBackend.DTS }, { label: 'MSSQL', data: DurableBackend.SQL }, { label: durableStorageInfo, data: undefined } ]; - let pick: DurableBackendValues | undefined; + let pick: DurableBackend | undefined; while (!pick) { pick = (await context.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true })).data; if (!pick) { diff --git a/src/commands/createFunctionApp/IFunctionAppWizardContext.ts b/src/commands/createFunctionApp/IFunctionAppWizardContext.ts index 2bbcc84d7..54df35c97 100644 --- a/src/commands/createFunctionApp/IFunctionAppWizardContext.ts +++ b/src/commands/createFunctionApp/IFunctionAppWizardContext.ts @@ -5,8 +5,8 @@ import { type IAppServiceWizardContext } from '@microsoft/vscode-azext-azureappservice'; import { type ExecuteActivityContext, type IAzureAgentInput, type ICreateChildImplContext } from '@microsoft/vscode-azext-utils'; +import { type DurableBackend } from '../../constants'; import { type FuncVersion } from '../../FuncVersion'; -import { type DurableBackendValues } from '../../constants'; import { type ICreateFunctionAppContext } from '../../tree/SubscriptionTreeItem'; import { type AppStackMajorVersion, type AppStackMinorVersion } from './stacks/models/AppStackModel'; import { type Sku } from './stacks/models/FlexSkuModel'; @@ -23,7 +23,7 @@ export interface IFunctionAppWizardContext extends IAppServiceWizardContext, ICr language: string | undefined; stackFilter?: string; newSiteStack?: FullFunctionAppStack; - durableStorageType?: DurableBackendValues; + durableStorageType?: DurableBackend; useFlexConsumptionPlan?: boolean; useManagedIdentity?: boolean; diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 5b69bd656..bb17d3541 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -9,7 +9,7 @@ import { ResourceGroupListStep } from '@microsoft/vscode-azext-azureutils'; import { DialogResponses, subscriptionExperience, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import type * as vscode from 'vscode'; -import { CodeAction, ConnectionType, deploySubpathSetting, DurableBackend, hostFileName, ProjectLanguage, remoteBuildSetting, ScmType, stackUpgradeLearnMoreLink, type DurableBackendValues } from '../../constants'; +import { CodeAction, ConnectionKey, ConnectionType, deploySubpathSetting, DurableBackend, hostFileName, ProjectLanguage, remoteBuildSetting, ScmType, stackUpgradeLearnMoreLink } from '../../constants'; import { ext } from '../../extensionVariables'; import { addLocalFuncTelemetry } from '../../funcCoreTools/getLocalFuncCoreToolsVersion'; import { localize } from '../../localize'; @@ -150,7 +150,9 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | // Preliminary local validation done to ensure all required resources have been created and are available. Final deploy writes are made in 'verifyAppSettings' if (durableStorageType === DurableBackend.DTS) { - await validateDTSConnection(Object.assign(context, subscriptionContext), client, context.projectPath); + const dtsConnections = await validateDTSConnection(Object.assign(context, subscriptionContext), client, context.projectPath); + context[ConnectionKey.DTS] = dtsConnections?.[ConnectionKey.DTS]; + context[ConnectionKey.DTSHub] = dtsConnections?.[ConnectionKey.DTSHub]; } if (shouldValidateEventHubs) { await validateEventHubsConnection(context, context.projectPath, { preselectedConnectionType: ConnectionType.Azure }); @@ -241,7 +243,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | await notifyDeployComplete(context, node, context.workspaceFolder, isFlexConsumption); } -async function updateWorkerProcessTo64BitIfRequired(context: IDeployContext, siteConfig: SiteConfigResource, node: SlotTreeItem, language: ProjectLanguage, durableStorageType: DurableBackendValues | undefined): Promise { +async function updateWorkerProcessTo64BitIfRequired(context: IDeployContext, siteConfig: SiteConfigResource, node: SlotTreeItem, language: ProjectLanguage, durableStorageType: DurableBackend | undefined): Promise { const client = await node.site.createClient(context); const config: SiteConfigResource = { use32BitWorkerProcess: false diff --git a/src/commands/deploy/shouldValidateConnection.ts b/src/commands/deploy/shouldValidateConnection.ts index 2925def83..a9617bac9 100644 --- a/src/commands/deploy/shouldValidateConnection.ts +++ b/src/commands/deploy/shouldValidateConnection.ts @@ -1,6 +1,6 @@ import { type StringDictionary } from "@azure/arm-appservice"; import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; -import { ConnectionKey, DurableBackend, type DurableBackendValues } from "../../constants"; +import { ConnectionKey, DurableBackend } from "../../constants"; import { getEventHubName } from "../appSettings/connectionSettings/eventHubs/validateEventHubsConnection"; export interface IShouldValidateConnection { @@ -8,7 +8,7 @@ export interface IShouldValidateConnection { shouldValidateSqlDb: boolean; } -export async function shouldValidateConnections(durableStorageType: DurableBackendValues | undefined, client: SiteClient, projectPath: string): Promise { +export async function shouldValidateConnections(durableStorageType: DurableBackend | undefined, client: SiteClient, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); const remoteEventHubsConnection: string | undefined = app?.properties?.[ConnectionKey.EventHubs]; const remoteSqlDbConnection: string | undefined = app?.properties?.[ConnectionKey.SQL]; diff --git a/src/commands/deploy/verifyAppSettings.ts b/src/commands/deploy/verifyAppSettings.ts index 0c85534c7..b9ba1b197 100644 --- a/src/commands/deploy/verifyAppSettings.ts +++ b/src/commands/deploy/verifyAppSettings.ts @@ -9,7 +9,7 @@ import { type IActionContext } from '@microsoft/vscode-azext-utils'; import * as retry from 'p-retry'; import type * as vscode from 'vscode'; import { FuncVersion, tryParseFuncVersion } from '../../FuncVersion'; -import { ConnectionKey, DurableBackend, extensionVersionKey, runFromPackageKey, workerRuntimeKey, type ConnectionKeyValues, type DurableBackendValues, type ProjectLanguage } from '../../constants'; +import { ConnectionKey, DurableBackend, extensionVersionKey, runFromPackageKey, workerRuntimeKey, type ProjectLanguage } from '../../constants'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { type SlotTreeItem } from '../../tree/SlotTreeItem'; @@ -29,7 +29,7 @@ export async function verifyAppSettings(options: { language: ProjectLanguage, languageModel: number | undefined, bools: VerifyAppSettingBooleans, - durableStorageType: DurableBackendValues | undefined, + durableStorageType: DurableBackend | undefined, appSettings: StringDictionary, }): Promise { @@ -66,13 +66,18 @@ export async function verifyAppSettings(options: { } } -export async function verifyAndUpdateAppConnectionStrings(context: IActionContext & Partial, durableStorageType: DurableBackendValues | undefined, remoteProperties: { [propertyName: string]: string }): Promise { +export async function verifyAndUpdateAppConnectionStrings(context: IActionContext & Partial, durableStorageType: DurableBackend | undefined, remoteProperties: { [propertyName: string]: string }): Promise { let didUpdate: boolean = false; switch (durableStorageType) { case DurableBackend.Netherite: const updatedNetheriteConnection: boolean = updateConnectionStringIfNeeded(context, remoteProperties, ConnectionKey.EventHubs, context[ConnectionKey.EventHubs]); didUpdate ||= updatedNetheriteConnection; break; + case DurableBackend.DTS: + const updatedDTSConnection: boolean = updateConnectionStringIfNeeded(context, remoteProperties, ConnectionKey.DTS, context[ConnectionKey.DTS]); + const updatedDTSHub: boolean = updateConnectionStringIfNeeded(context, remoteProperties, ConnectionKey.DTSHub, context[ConnectionKey.DTSHub]); + didUpdate ||= updatedDTSConnection || updatedDTSHub; + break; case DurableBackend.SQL: const updatedSqlDbConnection: boolean = updateConnectionStringIfNeeded(context, remoteProperties, ConnectionKey.SQL, context[ConnectionKey.SQL]); didUpdate ||= updatedSqlDbConnection; @@ -87,8 +92,8 @@ export async function verifyAndUpdateAppConnectionStrings(context: IActionContex return didUpdate; } -export function updateConnectionStringIfNeeded(context: IActionContext & Partial, remoteProperties: { [propertyName: string]: string }, propertyName: ConnectionKeyValues, newValue: string | undefined): boolean { - if (newValue) { +export function updateConnectionStringIfNeeded(context: IActionContext & Partial, remoteProperties: { [propertyName: string]: string }, propertyName: ConnectionKey, newValue: string | undefined): boolean { + if (newValue && newValue !== remoteProperties[propertyName]) { remoteProperties[propertyName] = newValue; context.telemetry.properties[`update${propertyName}`] = 'true'; return true; diff --git a/src/commands/durableTaskScheduler/createTaskHub.ts b/src/commands/durableTaskScheduler/createTaskHub.ts index 6df8c4aa2..76ee462d9 100644 --- a/src/commands/durableTaskScheduler/createTaskHub.ts +++ b/src/commands/durableTaskScheduler/createTaskHub.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, type ExecuteActivityContext, type IActionContext } from "@microsoft/vscode-azext-utils"; -import { localize } from '../../localize'; -import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel"; -import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { type Progress } from "vscode"; +import { localize } from '../../localize'; +import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel"; import { createActivityContext } from "../../utils/activityUtils"; interface ICreateTaskHubContext extends IActionContext, ExecuteActivityContext { @@ -58,13 +58,13 @@ export function createTaskHubCommandFactory(schedulerClient: DurableTaskSchedule } const wizardContext: ICreateTaskHubContext = - { - subscription: scheduler.subscription, - resourceGroup: scheduler.resourceGroup, - schedulerName: scheduler.name, - ...actionContext, - ...await createActivityContext() - }; + { + subscription: scheduler.subscription, + resourceGroup: scheduler.resourceGroup, + schedulerName: scheduler.name, + ...actionContext, + ...await createActivityContext() + }; const wizard = new AzureWizard( wizardContext, diff --git a/src/constants.ts b/src/constants.ts index d168a72dc..baf93380b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -126,10 +126,6 @@ export enum DurableBackend { SQL = 'mssql', } -export type CodeActionValues = typeof CodeAction[keyof typeof CodeAction]; -export type ConnectionKeyValues = typeof ConnectionKey[keyof typeof ConnectionKey]; -export type DurableBackendValues = typeof DurableBackend[keyof typeof DurableBackend]; - export const func: string = 'func'; export const extInstallCommand: string = 'extensions install'; export const extInstallTaskName: string = `${func}: ${extInstallCommand}`; diff --git a/src/funcConfig/local.settings.ts b/src/funcConfig/local.settings.ts index d61496199..fd72a4583 100644 --- a/src/funcConfig/local.settings.ts +++ b/src/funcConfig/local.settings.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { decryptLocalSettings } from '../commands/appSettings/localSettings/decryptLocalSettings'; import { encryptLocalSettings } from '../commands/appSettings/localSettings/encryptLocalSettings'; -import { localSettingsFileName, type ConnectionKeyValues } from '../constants'; +import { localSettingsFileName, type ConnectionKey } from '../constants'; import { localize } from '../localize'; import { parseJson } from '../utils/parseJson'; @@ -19,7 +19,7 @@ export interface ILocalSettingsJson { ConnectionStrings?: { [key: string]: string }; } -export async function getLocalSettingsConnectionString(context: IActionContext, connectionKey: ConnectionKeyValues, projectPath: string): Promise { +export async function getLocalSettingsConnectionString(context: IActionContext, connectionKey: ConnectionKey, projectPath: string): Promise { // func cli uses environment variable if it's defined on the machine, so no need to prompt if (process.env[connectionKey]) { return process.env[connectionKey]; diff --git a/src/utils/durableUtils.ts b/src/utils/durableUtils.ts index cfba9b0f4..25f6ebdc0 100644 --- a/src/utils/durableUtils.ts +++ b/src/utils/durableUtils.ts @@ -8,7 +8,7 @@ import * as path from "path"; import { type Uri } from "vscode"; import * as xml2js from "xml2js"; import { type IFunctionWizardContext } from "../commands/createFunction/IFunctionWizardContext"; -import { ConnectionKey, DurableBackend, ProjectLanguage, hostFileName, requirementsFileName, type DurableBackendValues } from "../constants"; +import { ConnectionKey, DurableBackend, ProjectLanguage, hostFileName, requirementsFileName } from "../constants"; import { ext } from "../extensionVariables"; import { type IDTSTaskJson, type IHostJsonV2, type INetheriteTaskJson, type ISqlTaskJson, type IStorageTaskJson } from "../funcConfig/host"; import { localize } from "../localize"; @@ -47,7 +47,7 @@ export namespace durableUtils { return durableOrchestrator.test(templateId) || durableEntity.test(templateId); } - export async function getStorageTypeFromWorkspace(language: string | undefined, projectPath: string): Promise { + export async function getStorageTypeFromWorkspace(language: string | undefined, projectPath: string): Promise { const hasDurableStorage: boolean = await verifyHasDurableStorage(language, projectPath); if (!hasDurableStorage) { return undefined; @@ -59,7 +59,7 @@ export namespace durableUtils { } const hostJson: IHostJsonV2 = await AzExtFsExtra.readJSON(hostJsonPath); - const hostStorageType: DurableBackendValues | undefined = hostJson.extensions?.durableTask?.storageProvider?.type; + const hostStorageType: DurableBackend | undefined = hostJson.extensions?.durableTask?.storageProvider?.type; switch (hostStorageType) { case DurableBackend.Netherite: From 7ba0ef6188308e0d2a4b6330e12d05f7ee43849f Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:54:12 -0700 Subject: [PATCH 29/52] WIP --- package-lock.json | 7 ++- package.json | 2 +- .../azure/DurableTaskSchedulerListStep.ts | 8 ++- .../validateDTSConnection.ts | 8 +-- .../FunctionAppRoleVerifyStep.ts | 28 +++++++++ ...unctionAppUserAssignedIdentitiesContext.ts | 11 ++++ ...nctionAppUserAssignedIdentitiesListStep.ts | 61 +++++++++++++++++++ 7 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts create mode 100644 src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts create mode 100644 src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts diff --git a/package-lock.json b/package-lock.json index c88820087..555bebecd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.3.3", + "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", @@ -1235,8 +1235,9 @@ }, "node_modules/@microsoft/vscode-azext-azureutils": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.3.3.tgz", - "integrity": "sha512-oE7cAavHe2xB+HC/TfbBIbm5CmbWmmfa1Sa8PxXvlUMs905O0gv6vu4GvxYfRurLZk1L5rcwUX/KcMCFaKTJgg==", + "resolved": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", + "integrity": "sha512-v1zH43lvLdlSwX8EaivElXiVriCVslxb5iVy55CsRDTLOEftunx2EOlqjKYFBEbIkySfXS6Ok9Le/FuMUzxtXA==", + "license": "MIT", "dependencies": { "@azure/arm-authorization": "^9.0.0", "@azure/arm-authorization-profile-2020-09-01-hybrid": "^2.1.0", diff --git a/package.json b/package.json index 565fa26a8..e8cbc34c8 100644 --- a/package.json +++ b/package.json @@ -1482,7 +1482,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.3.3", + "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index 47aea75ed..7719d244c 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -9,6 +9,7 @@ import { DurableTaskProvider, DurableTaskSchedulersResourceType } from '../../.. import { localSettingsDescription } from '../../../../../constants-nls'; import { localize } from '../../../../../localize'; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; +import { FunctionAppUserAssignedIdentitiesListStep } from '../../../../identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep'; import { type IDTSAzureConnectionWizardContext } from '../IDTSConnectionWizardContext'; import { DurableTaskHubListStep } from './DurableTaskHubListStep'; import { DurableTaskSchedulerCreateStep } from './DurableTaskSchedulerCreateStep'; @@ -54,7 +55,12 @@ export class DurableTaskSchedulerListStep { +export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); const remoteDTSConnection: string | undefined = app?.properties?.[ConnectionKey.DTS]; @@ -78,7 +78,7 @@ export async function validateDTSConnection(context: ValidateDTSConnectionContex // If not creating a new DTS, you can skip this // After adding the role, we need to set the new connection for DTS which should include the client ID for the connection string -async function getDTSResource(context: ValidateDTSConnectionContext, dtsEndpoint: string): Promise { +async function getDTSResource(context: DTSConnectionContext, dtsEndpoint: string): Promise { try { const client = new HttpDurableTaskSchedulerClient(); const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulers(context.subscription, nonNullValueAndProp(context.resourceGroup, 'name')) ?? []; diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts new file mode 100644 index 000000000..ff39f59f0 --- /dev/null +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RoleAssignmentExecuteStep, type Role } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; +import { type Progress } from "vscode"; +import { localize } from "../../../localize"; +import { type FunctionAppUserAssignedIdentitiesContext } from "./FunctionAppUserAssignedIdentitiesContext"; + +export class FunctionAppRoleVerifyStep extends AzureWizardExecuteStep { + priority: number = 900; + + constructor(readonly role: Role) { + super(); + } + + public async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { + progress.report({ message: localize('verifyingRoleAssignment', 'Verifying role "{0}"...', this.role.roleDefinitionName) }); + + this.addExecuteSteps = () => [new RoleAssignmentExecuteStep(() => [this.role])]; + } + + public shouldExecute(context: T): boolean { + return !!context.managedIdentity; + } +} diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts new file mode 100644 index 000000000..ca50cf7be --- /dev/null +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ParsedSite } from "@microsoft/vscode-azext-azureappservice"; +import { type IResourceGroupWizardContext } from "@microsoft/vscode-azext-azureutils"; + +export interface FunctionAppUserAssignedIdentitiesContext extends IResourceGroupWizardContext { + site?: ParsedSite; +} diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts new file mode 100644 index 000000000..8688e2118 --- /dev/null +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ManagedServiceIdentityClient } from '@azure/arm-msi'; +import { type ParsedSite } from '@microsoft/vscode-azext-azureappservice'; +import { createAuthorizationManagementClient, createManagedServiceIdentityClient, parseAzureResourceId, type ParsedAzureResourceId, type Role } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; +import { localize } from '../../../localize'; +import { type FunctionAppUserAssignedIdentitiesContext } from './FunctionAppUserAssignedIdentitiesContext'; + +/** + * Wizard step to select a user-assigned managed identity from the parsed site of a function app. + * Upon selection, retrieves and stores the identity on the wizard context. + * + * @populates `context.managedIdentity` + */ +export class FunctionAppUserAssignedIdentitiesListStep extends AzureWizardPromptStep { + private _msiClient: ManagedServiceIdentityClient; + + constructor(readonly role: Role) { + super(); + } + + public async configureBeforePrompt(context: T): Promise { + this._msiClient ??= await createManagedServiceIdentityClient(context); + const amClient = await createAuthorizationManagementClient(context) + + const site: ParsedSite = nonNullProp(context, 'site'); + const identityIds: string[] = Object.keys(site.identity?.userAssignedIdentities ?? {}) ?? []; + amClient.roleAssignments. + } + + public async prompt(context: T): Promise { + const site: ParsedSite = nonNullProp(context, 'site'); + const identityId: string = (await context.ui.showQuickPick(await this.getPicks(site), { + placeHolder: localize('selectFunctionAppIdentity', 'Select a function app identity for new role assignments'), + })).data; + + const parsedIdentity: ParsedAzureResourceId = parseAzureResourceId(identityId); + this._msiClient ??= await createManagedServiceIdentityClient(context); + + context.managedIdentity = await this._msiClient.userAssignedIdentities.get(parsedIdentity.resourceGroup, parsedIdentity.resourceName); + context.telemetry.properties.functionAppUserAssignedIdentityCount = String(Object.keys(site.identity?.userAssignedIdentities ?? {}).length); + } + + public shouldPrompt(context: T): boolean { + return !context.managedIdentity; + } + + private async getPicks(site: ParsedSite): Promise[]> { + return Object.keys(site.identity?.userAssignedIdentities ?? {}).map((id) => { + return { + label: parseAzureResourceId(id).resourceName, + description: id, + data: id, + }; + }); + } +} From b9d458357bc5435eb12dbba41fa6ac44f3f36b95 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 20 Jun 2025 21:30:57 -0500 Subject: [PATCH 30/52] WIP --- .../IDTSConnectionWizardContext.ts | 4 +- .../azure/DurableTaskHubListStep.ts | 6 +- .../azure/DurableTaskSchedulerListStep.ts | 45 ++++++++++---- .../validateDTSConnection.ts | 18 ++---- src/commands/deploy/deploy.ts | 2 +- .../FunctionAppRoleVerifyStep.ts | 28 --------- ...nctionAppUserAssignedIdentitiesListStep.ts | 59 ++++++++++++++++--- ...nctionAppUserAssignedIdentitiesContext.ts} | 2 +- .../DurableTaskSchedulerClient.ts | 23 ++++++-- 9 files changed, 116 insertions(+), 71 deletions(-) delete mode 100644 src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts rename src/commands/identity/listUserAssignedIdentities/{FunctionAppUserAssignedIdentitiesContext.ts => IFunctionAppUserAssignedIdentitiesContext.ts} (84%) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index 122a5e88f..f7349c587 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type IResourceGroupWizardContext } from "@microsoft/vscode-azext-azureutils"; import { type IActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { type ConnectionType } from "../../../../constants"; import { type DurableTaskHubResource, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type IFunctionAppUserAssignedIdentitiesContext } from "../../../identity/listUserAssignedIdentities/IFunctionAppUserAssignedIdentitiesContext"; import { type StorageConnectionType } from "../IConnectionTypesContext"; import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext"; @@ -20,7 +20,7 @@ export interface IDTSConnectionWizardContext extends IActionContext, ISetConnect newDTSHubNameConnectionSetting?: string; } -export interface IDTSAzureConnectionWizardContext extends IResourceGroupWizardContext, IDTSConnectionWizardContext { +export interface IDTSAzureConnectionWizardContext extends IFunctionAppUserAssignedIdentitiesContext, IDTSConnectionWizardContext { subscription?: AzureSubscription; suggestedDTSEndpointLocalSettings?: string; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts index 95221c353..9f4711b32 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubListStep.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { parseAzureResourceId } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { localSettingsDescription } from '../../../../../constants-nls'; import { localize } from '../../../../../localize'; import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from '../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient'; @@ -46,9 +47,8 @@ export class DurableTaskHubListStep } private async getPicks(context: T): Promise[]> { - const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); const taskHubs: DurableTaskHubResource[] = context.dts ? - await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), resourceGroupName, context.dts.name) : []; + await this.schedulerClient.getSchedulerTaskHubs(nonNullProp(context, 'subscription'), parseAzureResourceId(context.dts.id).resourceGroup, context.dts.name) : []; const createPick = { label: localize('createTaskHub', '$(plus) Create new durable task hub'), diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index 7719d244c..573e1b0a3 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LocationListStep, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils'; -import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { CommonRoleDefinitions, createRoleId, LocationListStep, parseAzureResourceId, RoleAssignmentExecuteStep, type ILocationWizardContext, type Role } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils'; import { DurableTaskProvider, DurableTaskSchedulersResourceType } from '../../../../../constants'; import { localSettingsDescription } from '../../../../../constants-nls'; import { localize } from '../../../../../localize'; @@ -16,11 +16,11 @@ import { DurableTaskSchedulerCreateStep } from './DurableTaskSchedulerCreateStep import { DurableTaskSchedulerNameStep } from './DurableTaskSchedulerNameStep'; export class DurableTaskSchedulerListStep extends AzureWizardPromptStep { - private readonly schedulerClient: DurableTaskSchedulerClient; + private readonly _schedulerClient: DurableTaskSchedulerClient; constructor(schedulerClient?: DurableTaskSchedulerClient) { super(); - this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + this._schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); } public async prompt(context: T): Promise { @@ -51,22 +51,42 @@ export class DurableTaskSchedulerListStep[]); - promptSteps.push(new DurableTaskSchedulerNameStep(this.schedulerClient)); - executeSteps.push(new DurableTaskSchedulerCreateStep(this.schedulerClient)); + promptSteps.push(new DurableTaskSchedulerNameStep(this._schedulerClient)); + executeSteps.push(new DurableTaskSchedulerCreateStep(this._schedulerClient)); } if (!context.dtsHub) { - promptSteps.push(new DurableTaskHubListStep(this.schedulerClient)); + promptSteps.push(new DurableTaskHubListStep(this._schedulerClient)); } - promptSteps.push(new FunctionAppUserAssignedIdentitiesListStep()) + const dtsContributorRole: Role = { + scopeId: context.dts?.id, + roleDefinitionId: createRoleId(context.subscriptionId, CommonRoleDefinitions.durableTaskDataContributor), + roleDefinitionName: CommonRoleDefinitions.durableTaskDataContributor.roleName, + }; + + const identitiesListStep = new FunctionAppUserAssignedIdentitiesListStep(dtsContributorRole /** target role */); + promptSteps.push(identitiesListStep); + executeSteps.push(new RoleAssignmentExecuteStep(getDTSRoleAssignmentCallback(context, identitiesListStep, dtsContributorRole))); return { promptSteps, executeSteps }; + + function getDTSRoleAssignmentCallback(context: T, functionAppIdentitiesListStep: FunctionAppUserAssignedIdentitiesListStep, role: Role): () => Role[] { + return () => { + 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, + }; + + return functionAppIdentitiesListStep.hasIdentityWithAssignedRole ? [] : [roleAssignment]; + }; + } } private async getPicks(context: T): Promise[]> { - const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); - const schedulers: DurableTaskSchedulerResource[] = await this.schedulerClient.getSchedulers(nonNullProp(context, 'subscription'), resourceGroupName); + const schedulers: DurableTaskSchedulerResource[] = await this._schedulerClient.getSchedulersBySubscription(nonNullProp(context, 'subscription')); const createPick = { label: localize('createTaskScheduler', '$(plus) Create new durable task scheduler'), @@ -76,9 +96,12 @@ export class DurableTaskSchedulerListStep { + const resourceGroupName: string = parseAzureResourceId(s.id).resourceGroup; return { label: s.name, - description: s.properties.endpoint === context.suggestedDTSEndpointLocalSettings ? localSettingsDescription : undefined, + description: s.properties.endpoint === context.suggestedDTSEndpointLocalSettings ? + `${resourceGroupName} ${localSettingsDescription}` : + resourceGroupName, data: s, }; }), diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index 8d4a01200..b4c1f2032 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type StringDictionary } from "@azure/arm-appservice"; -import { type SiteClient } from "@microsoft/vscode-azext-azureappservice"; +import { type ParsedSite, type SiteClient } from "@microsoft/vscode-azext-azureappservice"; import { ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils"; import { AzureWizard, nonNullValueAndProp, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; @@ -21,7 +21,7 @@ import { type IDTSAzureConnectionWizardContext } from "./IDTSConnectionWizardCon type DTSConnectionContext = IFuncDeployContext & ISubscriptionActionContext & { subscription: AzureSubscription }; type DTSConnection = { [ConnectionKey.DTS]?: string, [ConnectionKey.DTSHub]?: string }; -export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, projectPath: string): Promise { +export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, site: ParsedSite, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); const remoteDTSConnection: string | undefined = app?.properties?.[ConnectionKey.DTS]; @@ -42,6 +42,7 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien const wizardContext: IDTSAzureConnectionWizardContext = { ...context, ...await createActivityContext(), + site, projectPath, action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, @@ -52,7 +53,7 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien }; const wizard: AzureWizard = new AzureWizard(wizardContext, { - title: localize('getDTSResources', 'Get Durable Task Scheduler resources'), + title: localize('prepareDTSConnection', 'Prepare durable task scheduler connection'), promptSteps: [ new ResourceGroupListStep(), new DTSConnectionTypeListStep(availableDeployConnectionTypes), @@ -69,19 +70,10 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien }; } -// Check function app for user assigned identity, -// 1. if exists, assign it to context -// 2. If it doesn't exist we need to either select or create a new user assigned identity. Do we need to prompt for managed identity vs. secret - Lily said only MI connection supported -// For this bottom choice we can probably follow whatever the create function app logic is doing - -// After creating the new DTS, add a step to assign the Durable Task Scheduler Contributor role to the user assigned identity -// If not creating a new DTS, you can skip this -// After adding the role, we need to set the new connection for DTS which should include the client ID for the connection string - async function getDTSResource(context: DTSConnectionContext, dtsEndpoint: string): Promise { try { const client = new HttpDurableTaskSchedulerClient(); - const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulers(context.subscription, nonNullValueAndProp(context.resourceGroup, 'name')) ?? []; + const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulersByResourceGroup(context.subscription, nonNullValueAndProp(context.resourceGroup, 'name')) ?? []; return schedulers.find(s => s.properties.endpoint === dtsEndpoint); } catch (e) { const pe: IParsedError = parseError(e); diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index bb17d3541..ccc43acf4 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -150,7 +150,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | // Preliminary local validation done to ensure all required resources have been created and are available. Final deploy writes are made in 'verifyAppSettings' if (durableStorageType === DurableBackend.DTS) { - const dtsConnections = await validateDTSConnection(Object.assign(context, subscriptionContext), client, context.projectPath); + const dtsConnections = await validateDTSConnection(Object.assign(context, subscriptionContext), client, node.site, context.projectPath); context[ConnectionKey.DTS] = dtsConnections?.[ConnectionKey.DTS]; context[ConnectionKey.DTSHub] = dtsConnections?.[ConnectionKey.DTSHub]; } diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts deleted file mode 100644 index ff39f59f0..000000000 --- a/src/commands/identity/listUserAssignedIdentities/FunctionAppRoleVerifyStep.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { RoleAssignmentExecuteStep, type Role } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; -import { type Progress } from "vscode"; -import { localize } from "../../../localize"; -import { type FunctionAppUserAssignedIdentitiesContext } from "./FunctionAppUserAssignedIdentitiesContext"; - -export class FunctionAppRoleVerifyStep extends AzureWizardExecuteStep { - priority: number = 900; - - constructor(readonly role: Role) { - super(); - } - - public async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise { - progress.report({ message: localize('verifyingRoleAssignment', 'Verifying role "{0}"...', this.role.roleDefinitionName) }); - - this.addExecuteSteps = () => [new RoleAssignmentExecuteStep(() => [this.role])]; - } - - public shouldExecute(context: T): boolean { - return !!context.managedIdentity; - } -} diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts index 8688e2118..b51e8f49e 100644 --- a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts @@ -5,37 +5,79 @@ import { type ManagedServiceIdentityClient } from '@azure/arm-msi'; import { type ParsedSite } from '@microsoft/vscode-azext-azureappservice'; -import { createAuthorizationManagementClient, createManagedServiceIdentityClient, parseAzureResourceId, type ParsedAzureResourceId, type Role } from '@microsoft/vscode-azext-azureutils'; +import { createAuthorizationManagementClient, createManagedServiceIdentityClient, parseAzureResourceId, uiUtils, type ParsedAzureResourceId, type Role } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; import { localize } from '../../../localize'; -import { type FunctionAppUserAssignedIdentitiesContext } from './FunctionAppUserAssignedIdentitiesContext'; +import { type IFunctionAppUserAssignedIdentitiesContext } from './IFunctionAppUserAssignedIdentitiesContext'; /** * Wizard step to select a user-assigned managed identity from the parsed site of a function app. * Upon selection, retrieves and stores the identity on the wizard context. * + * @param role Optional. If provided, the function app will be checked for a user assigned identity with this target role. + * If such an identity exists, it will be automatically assigned as a managed identity without prompting the user. + * * @populates `context.managedIdentity` */ -export class FunctionAppUserAssignedIdentitiesListStep extends AzureWizardPromptStep { +export class FunctionAppUserAssignedIdentitiesListStep extends AzureWizardPromptStep { private _msiClient: ManagedServiceIdentityClient; + private _hasAssignedRole?: boolean; - constructor(readonly role: Role) { + constructor(readonly role?: Role) { super(); } + /** + * Indicates whether there is at least one user-assigned identity on the function app with the provided role. + * If no role is provided, or if the step has not yet been run, this will return `undefined`. + */ + get hasIdentityWithAssignedRole(): boolean | undefined { + return this._hasAssignedRole; + } + + // Verify if any of the existing user assigned identities for the function app have the required role already public async configureBeforePrompt(context: T): Promise { + if (!this.role || !this.role?.scopeId) { + this._hasAssignedRole = undefined; + return; + } + + this._hasAssignedRole = false; this._msiClient ??= await createManagedServiceIdentityClient(context); - const amClient = await createAuthorizationManagementClient(context) + const amClient = await createAuthorizationManagementClient(context); + const role: Role = this.role; const site: ParsedSite = nonNullProp(context, 'site'); const identityIds: string[] = Object.keys(site.identity?.userAssignedIdentities ?? {}) ?? []; - amClient.roleAssignments. + context.telemetry.properties.functionAppUserAssignedIdentityCount = String(identityIds.length); + + for (const identityId of identityIds) { + const uaid = site.identity?.userAssignedIdentities?.[identityId]; + const roleAssignments = await uiUtils.listAllIterator(amClient.roleAssignments.listForScope( + this.role.scopeId, + { + // $filter=principalId eq {id} + filter: `principalId eq '{${uaid?.principalId}}'`, + } + )); + + if (roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(role.roleDefinitionId))) { + const parsedIdentity = parseAzureResourceId(identityId); + context.managedIdentity = await this._msiClient.userAssignedIdentities.get(parsedIdentity.resourceGroup, parsedIdentity.resourceName); + this._hasAssignedRole = true; + break; + } + } + + // Todo: Add output log messages } public async prompt(context: T): Promise { const site: ParsedSite = nonNullProp(context, 'site'); const identityId: string = (await context.ui.showQuickPick(await this.getPicks(site), { placeHolder: localize('selectFunctionAppIdentity', 'Select a function app identity for new role assignments'), + // Todo: Remove when create + assign is implemented + noPicksMessage: localize('noUserAssignedIdentities', 'No identities found. Add a user assigned identity to the function app before proceeding.'), })).data; const parsedIdentity: ParsedAzureResourceId = parseAzureResourceId(identityId); @@ -51,9 +93,10 @@ export class FunctionAppUserAssignedIdentitiesListStep[]> { return Object.keys(site.identity?.userAssignedIdentities ?? {}).map((id) => { + const parsedResource: ParsedAzureResourceId = parseAzureResourceId(id); return { - label: parseAzureResourceId(id).resourceName, - description: id, + label: parsedResource.resourceName, + description: parsedResource.resourceGroup, data: id, }; }); diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts b/src/commands/identity/listUserAssignedIdentities/IFunctionAppUserAssignedIdentitiesContext.ts similarity index 84% rename from src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts rename to src/commands/identity/listUserAssignedIdentities/IFunctionAppUserAssignedIdentitiesContext.ts index ca50cf7be..752cdf625 100644 --- a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesContext.ts +++ b/src/commands/identity/listUserAssignedIdentities/IFunctionAppUserAssignedIdentitiesContext.ts @@ -6,6 +6,6 @@ import { type ParsedSite } from "@microsoft/vscode-azext-azureappservice"; import { type IResourceGroupWizardContext } from "@microsoft/vscode-azext-azureutils"; -export interface FunctionAppUserAssignedIdentitiesContext extends IResourceGroupWizardContext { +export interface IFunctionAppUserAssignedIdentitiesContext extends IResourceGroupWizardContext { site?: ParsedSite; } diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts index 62a3837dd..17cd1aa07 100644 --- a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts +++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts @@ -73,7 +73,8 @@ export interface DurableTaskSchedulerClient { deleteTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getScheduler(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; - getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise; + getSchedulersBySubscription(subscription: AzureSubscription): Promise; + getSchedulersByResourceGroup(subscription: AzureSubscription, resourceGroupName: string): Promise; getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise; getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise; @@ -147,7 +148,15 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien return scheduler; } - async getSchedulers(subscription: AzureSubscription, resourceGroupName: string): Promise { + async getSchedulersBySubscription(subscription: AzureSubscription): Promise { + const schedulerUrl = HttpDurableTaskSchedulerClient.getBaseUrl(subscription); + + const schedulers = await this.getAsJson<{ value: DurableTaskSchedulerResource[] }>(schedulerUrl, subscription.authentication); + + return schedulers?.value ?? []; + } + + async getSchedulersByResourceGroup(subscription: AzureSubscription, resourceGroupName: string): Promise { const schedulerUrl = HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName); const schedulers = await this.getAsJson<{ value: DurableTaskSchedulerResource[] }>(schedulerUrl, subscription.authentication); @@ -171,11 +180,17 @@ export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClien return response?.value ?? []; } - private static getBaseUrl(subscription: AzureSubscription, resourceGroupName: string, schedulerName?: string, relativeUrl?: string | undefined) { + private static getBaseUrl(subscription: AzureSubscription, resourceGroupName?: string, schedulerName?: string, relativeUrl?: string | undefined) { const provider = 'Microsoft.DurableTask'; const apiVersion = '2024-10-01-preview'; - let url = `${subscription.environment.resourceManagerEndpointUrl}subscriptions/${subscription.subscriptionId}/resourceGroups/${resourceGroupName}/providers/${provider}/schedulers`; + let url = `${subscription.environment.resourceManagerEndpointUrl}subscriptions/${subscription.subscriptionId}`; + + if (resourceGroupName) { + url += `/resourceGroups/${resourceGroupName}`; + } + + url += `/providers/${provider}/schedulers`; if (schedulerName) { url += `/${schedulerName}`; From 414576ed4b4a7e86d17fa9e4ae3acda789078791 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Sun, 22 Jun 2025 23:07:53 -0500 Subject: [PATCH 31/52] Entire flow is working --- .../DTSConnectionSetSettingStep.ts | 8 +- .../IDTSConnectionWizardContext.ts | 6 ++ .../azure/DurableTaskSchedulerCreateStep.ts | 3 +- .../azure/DurableTaskSchedulerListStep.ts | 7 +- .../emulator/DTSEmulatorStartStep.ts | 3 +- .../copyEmulatorConnectionString.ts | 13 +-- .../copySchedulerConnectionString.ts | 81 +++++++++++-------- ...nctionAppUserAssignedIdentitiesListStep.ts | 23 +++--- 8 files changed, 88 insertions(+), 56 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts index 74b8a55f4..3c01f2f9b 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts @@ -6,14 +6,18 @@ import { nonNullProp } from '@microsoft/vscode-azext-utils'; import { ConnectionKey } from '../../../../constants'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; -import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; +import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; export class DTSConnectionSetSettingStep extends SetConnectionSettingStepBase { public priority: number = 240; public debugDeploySetting: ConnectionKey = ConnectionKey.DTS; public async execute(context: T): Promise { - await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnectionSetting')); + let newDTSConnectionSetting = nonNullProp(context, 'newDTSConnectionSetting'); + if ((context as unknown as IDTSAzureConnectionWizardContext).managedIdentity) { + newDTSConnectionSetting = newDTSConnectionSetting.replace('', (context as unknown as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? ''); + } + await this.setConnectionSetting(context, newDTSConnectionSetting); } public shouldExecute(context: T): boolean { diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts index f7349c587..43ceb26d8 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext.ts @@ -23,7 +23,13 @@ export interface IDTSConnectionWizardContext extends IActionContext, ISetConnect export interface IDTSAzureConnectionWizardContext extends IFunctionAppUserAssignedIdentitiesContext, IDTSConnectionWizardContext { subscription?: AzureSubscription; + /** + * Durable Task Scheduler endpoint detected in local settings JSON + */ suggestedDTSEndpointLocalSettings?: string; + /** + * Durable Task Scheduler hub name detected in local settings JSON + */ suggestedDTSHubNameLocalSettings?: string; newDTSName?: string; diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts index 8fa4f0d10..8bd4aac27 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerCreateStep.ts @@ -9,6 +9,7 @@ import { type Progress } from "vscode"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { withCancellation } from "../../../../../utils/cancellation"; +import { getSchedulerConnectionString, SchedulerAuthenticationType } from "../../../../durableTaskScheduler/copySchedulerConnectionString"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; export class DurableTaskSchedulerCreateStep extends AzureWizardExecuteStep { @@ -37,7 +38,7 @@ export class DurableTaskSchedulerCreateStep extends AzureWizardExecuteStep { @@ -29,7 +30,7 @@ export class DTSEmulatorStartStep extends localize('couldNotFindEmulator', 'Internal error: Failed to retrieve info on the started DTS emulator.'), ); - context.newDTSConnectionSetting = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`; + context.newDTSConnectionSetting = getSchedulerConnectionString(emulator.schedulerEndpoint.toString(), SchedulerAuthenticationType.None); context.newDTSHubNameConnectionSetting = 'default'; } diff --git a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts index ba94b0d2e..854eaeead 100644 --- a/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts +++ b/src/commands/durableTaskScheduler/copyEmulatorConnectionString.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { type IActionContext } from "@microsoft/vscode-azext-utils"; -import { localize } from "../../localize"; -import { ext } from "../../extensionVariables"; import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; +import { ext } from "../../extensionVariables"; +import { localize } from "../../localize"; import { type DurableTaskSchedulerEmulatorWorkspaceResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorWorkspaceResourceModel"; +import { getSchedulerConnectionString, SchedulerAuthenticationType } from "./copySchedulerConnectionString"; export function copyEmulatorConnectionStringCommandFactory() { return async (actionContext: IActionContext, scheduler: DurableTaskSchedulerEmulatorWorkspaceResourceModel | undefined): Promise => { @@ -17,16 +18,16 @@ export function copyEmulatorConnectionStringCommandFactory() { const { endpointUrl } = scheduler; - let connectionString = `Endpoint=${endpointUrl};Authentication=None`; + let connectionString = getSchedulerConnectionString(endpointUrl.toString(), SchedulerAuthenticationType.None); const taskHubs = scheduler.taskHubs; if (taskHubs.length > 0) { const noTaskHubItem: QuickPickItem = { - detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), - label: localize('noTaskHubLabel', 'None') - } + detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), + label: localize('noTaskHubLabel', 'None') + } const taskHubItems: QuickPickItem[] = taskHubs.map(taskHub => ({ label: taskHub })); diff --git a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts index 603e9541c..f40461b3b 100644 --- a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts +++ b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts @@ -3,12 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type IActionContext } from "@microsoft/vscode-azext-utils"; +import { type IActionContext, type IAzureQuickPickItem } from "@microsoft/vscode-azext-utils"; +import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; +import { ext } from "../../extensionVariables"; +import { localize } from "../../localize"; import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel"; -import { localize } from "../../localize"; -import { ext } from "../../extensionVariables"; -import { env, QuickPickItemKind, type QuickPickItem } from "vscode"; + +export enum SchedulerAuthenticationType { + None, + Local, + SystemAssignedIdentity, + UserAssignedIdentity, +} export function copySchedulerConnectionStringCommandFactory(schedulerClient: DurableTaskSchedulerClient) { return async (actionContext: IActionContext, scheduler: DurableTaskSchedulerResourceModel | undefined): Promise => { @@ -18,27 +25,31 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur const { endpointUrl } = scheduler; - const noAuthentication: QuickPickItem = { + const noAuthentication: IAzureQuickPickItem = { detail: localize('noAuthenticationDetail', 'No credentials will be used.'), - label: localize('noAuthenticationLabel', 'None') + label: localize('noAuthenticationLabel', 'None'), + data: SchedulerAuthenticationType.None, } - const localDevelopment: QuickPickItem = { + const localDevelopment: IAzureQuickPickItem = { detail: localize('localDevelopmentDetail', 'Use the credentials of the local developer.'), - label: localize('localDevelopmentLabel', 'Local development') + label: localize('localDevelopmentLabel', 'Local development'), + data: SchedulerAuthenticationType.Local, }; - const userAssignedManagedIdentity: QuickPickItem = { + const userAssignedManagedIdentity: IAzureQuickPickItem = { detail: localize('userAssignedManagedIdentityDetail', 'Use managed identity credentials for a specific client.'), - label: localize('userAssignedManagedIdentityLabel', 'User-assigned managed identity') + label: localize('userAssignedManagedIdentityLabel', 'User-assigned managed identity'), + data: SchedulerAuthenticationType.UserAssignedIdentity, } - const systemAssignedManagedIdentity: QuickPickItem = { + const systemAssignedManagedIdentity: IAzureQuickPickItem = { detail: localize('systemAssignedManagedIdentityDetail', 'Use managed identity credentials for a client assigned to a specific Azure resource.'), - label: localize('systemAssignedManagedIdentityLabel', 'System-assigned managed identity') + label: localize('systemAssignedManagedIdentityLabel', 'System-assigned managed identity'), + data: SchedulerAuthenticationType.SystemAssignedIdentity, } - const result = await actionContext.ui.showQuickPick( + const authenticationType = (await actionContext.ui.showQuickPick( [ noAuthentication, localDevelopment, @@ -48,23 +59,9 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur { canPickMany: false, placeHolder: localize('authenticationTypePlaceholder', 'Select the credentials to be used to connect to the scheduler') - }); - - let connectionString = `Endpoint=${endpointUrl};Authentication=` - - if (result === noAuthentication) { - connectionString += 'None'; - } - else if (result === localDevelopment) { - connectionString += 'DefaultAzure'; - } - else { - connectionString += 'ManagedIdentity'; + })).data; - if (result === userAssignedManagedIdentity) { - connectionString += ';ClientID='; - } - } + let connectionString = getSchedulerConnectionString(endpointUrl ?? '', authenticationType); const taskHubs = await schedulerClient.getSchedulerTaskHubs( scheduler.subscription, @@ -74,9 +71,9 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur if (taskHubs.length > 0) { const noTaskHubItem: QuickPickItem = { - detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), - label: localize('noTaskHubLabel', 'None') - } + detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'), + label: localize('noTaskHubLabel', 'None') + } const taskHubItems: QuickPickItem[] = taskHubs.map(taskHub => ({ label: taskHub.name })); @@ -106,3 +103,23 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur ext.outputChannel.appendLog(localize('schedulerConnectionStringCopiedMessage', 'Connection string copied to clipboard: {0}', connectionString)); } } + +export function getSchedulerConnectionString(endpointUrl: string, authenticationType: SchedulerAuthenticationType): string { + let schedulerConnectionString = `Endpoint=${endpointUrl};Authentication=` + + if (authenticationType === SchedulerAuthenticationType.None) { + schedulerConnectionString += 'None'; + } + else if (authenticationType === SchedulerAuthenticationType.Local) { + schedulerConnectionString += 'DefaultAzure'; + } + else { + schedulerConnectionString += 'ManagedIdentity'; + + if (authenticationType === SchedulerAuthenticationType.UserAssignedIdentity) { + schedulerConnectionString += ';ClientID='; + } + } + + return schedulerConnectionString; +} diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts index b51e8f49e..eba6b531d 100644 --- a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts @@ -14,16 +14,16 @@ import { type IFunctionAppUserAssignedIdentitiesContext } from './IFunctionAppUs * Wizard step to select a user-assigned managed identity from the parsed site of a function app. * Upon selection, retrieves and stores the identity on the wizard context. * - * @param role Optional. If provided, the function app will be checked for a user assigned identity with this target role. + * @param role Optional. If provided, the function app will be pre-checked for an existing user assigned identity with this target role. * If such an identity exists, it will be automatically assigned as a managed identity without prompting the user. * * @populates `context.managedIdentity` */ export class FunctionAppUserAssignedIdentitiesListStep extends AzureWizardPromptStep { private _msiClient: ManagedServiceIdentityClient; - private _hasAssignedRole?: boolean; + private _hasTargetRole?: boolean; - constructor(readonly role?: Role) { + constructor(readonly targetRole?: Role) { super(); } @@ -31,22 +31,22 @@ export class FunctionAppUserAssignedIdentitiesListStep { - if (!this.role || !this.role?.scopeId) { - this._hasAssignedRole = undefined; + if (!this.targetRole || !this.targetRole?.scopeId) { + this._hasTargetRole = undefined; return; } - this._hasAssignedRole = false; + this._hasTargetRole = false; this._msiClient ??= await createManagedServiceIdentityClient(context); const amClient = await createAuthorizationManagementClient(context); - const role: Role = this.role; + const role: Role = this.targetRole; const site: ParsedSite = nonNullProp(context, 'site'); const identityIds: string[] = Object.keys(site.identity?.userAssignedIdentities ?? {}) ?? []; context.telemetry.properties.functionAppUserAssignedIdentityCount = String(identityIds.length); @@ -54,7 +54,7 @@ export class FunctionAppUserAssignedIdentitiesListStep !!r.roleDefinitionId?.endsWith(role.roleDefinitionId))) { const parsedIdentity = parseAzureResourceId(identityId); context.managedIdentity = await this._msiClient.userAssignedIdentities.get(parsedIdentity.resourceGroup, parsedIdentity.resourceName); - this._hasAssignedRole = true; + this._hasTargetRole = true; break; } } + context.telemetry.properties.functionAppHasIdentityWithTargetRole = String(this.hasIdentityWithTargetRole); // Todo: Add output log messages } From 9a090e4a3323c89acbaee3ec32845e4beb013427 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:17:57 -0500 Subject: [PATCH 32/52] Add validation notes --- .../azure/DurableTaskSchedulerNameStep.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts index 7c3a8b927..cf73c8b76 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -27,4 +27,14 @@ export class DurableTaskSchedulerNameStep Date: Mon, 23 Jun 2025 14:37:20 -0500 Subject: [PATCH 33/52] Rename step --- ...peListStep.ts => DTSConnectionListStep.ts} | 2 +- .../azure/DurableTaskHubNameStep.ts | 54 ++++++++++++++++--- .../azure/DurableTaskSchedulerNameStep.ts | 51 +++++++++++++----- .../validateDTSConnection.ts | 4 +- .../durable/validateDTSConnectionPreDebug.ts | 4 +- 5 files changed, 91 insertions(+), 24 deletions(-) rename src/commands/appSettings/connectionSettings/durableTaskScheduler/{DTSConnectionTypeListStep.ts => DTSConnectionListStep.ts} (97%) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionListStep.ts similarity index 97% rename from src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts rename to src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionListStep.ts index a5b390b30..5133e30a5 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionListStep.ts @@ -17,7 +17,7 @@ import { DTSHubNameSetSettingStep } from './DTSHubNameSetSettingStep'; import { DTSEmulatorStartStep } from './emulator/DTSEmulatorStartStep'; import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; -export class DTSConnectionTypeListStep extends AzureWizardPromptStep { +export class DTSConnectionListStep extends AzureWizardPromptStep { constructor(readonly connectionTypes: Set) { super(); } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index f70770bd7..808b3ccca 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -3,25 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +import { parseAzureResourceId, type ParsedAzureResourceId } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizardPromptStep, nonNullProp, validationUtils } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; -import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; export class DurableTaskHubNameStep extends AzureWizardPromptStep { - private readonly schedulerClient: DurableTaskSchedulerClient; + private readonly _schedulerClient: DurableTaskSchedulerClient; constructor(schedulerClient?: DurableTaskSchedulerClient) { super(); - this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + this._schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); } public async prompt(context: T): Promise { - context.newDTSHubName = await context.ui.showInputBox({ + context.newDTSHubName = (await context.ui.showInputBox({ prompt: localize('taskSchedulerName', 'Enter a name for the durable task hub'), value: context.suggestedDTSHubNameLocalSettings, - // Todo: validation - }); + validateInput: this.validateInput, + asyncValidationTask: (name: string) => this.validateNameAvailable(context, name), + })).trim(); context.telemetry.properties.usedDTSDefaultHub = 'true'; @@ -34,4 +36,42 @@ export class DurableTaskHubNameStep public shouldPrompt(context: T): boolean { return !context.newDTSHubName; } + + private validateInput(hubName: string = ''): string | undefined { + hubName = hubName.trim(); + + const rc: validationUtils.RangeConstraints = { lowerLimitIncl: 3, upperLimitIncl: 64 }; + if (!validationUtils.hasValidCharLength(hubName, rc)) { + return validationUtils.getInvalidCharLengthMessage(rc); + } + + if (!/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/.test(hubName)) { + return localize('nameValidationMessage', 'Name may use alphanumeric characters as well as non-consecutive dashes ("-") in between.'); + } + + return undefined; + } + + private async validateNameAvailable(context: T, hubName: string = ''): Promise { + hubName = hubName.trim(); + + try { + const parsedScheduler: ParsedAzureResourceId = parseAzureResourceId(nonNullProp(context, 'dts').id); + + const hub: DurableTaskHubResource | undefined = await this._schedulerClient.getSchedulerTaskHub( + nonNullProp(context, 'subscription'), + parsedScheduler.resourceGroup, + parsedScheduler.resourceName, + hubName, + ); + + if (hub) { + return localize('hubAlreadyExists', 'A task hub with name "{0}" already exists with scheduler "{1}".', hubName, parsedScheduler.resourceName); + } + } catch (e) { + console.log(e) + } + + return undefined; + } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts index cf73c8b76..34c46aca4 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -3,24 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, validationUtils } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; -import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; export class DurableTaskSchedulerNameStep extends AzureWizardPromptStep { - private readonly schedulerClient: DurableTaskSchedulerClient; + private readonly _schedulerClient: DurableTaskSchedulerClient; constructor(schedulerClient?: DurableTaskSchedulerClient) { super(); - this.schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); + this._schedulerClient = schedulerClient ?? new HttpDurableTaskSchedulerClient(); } public async prompt(context: T): Promise { - context.newDTSName = await context.ui.showInputBox({ + context.newDTSName = (await context.ui.showInputBox({ prompt: localize('taskSchedulerName', 'Enter a name for the durable task scheduler'), - // Todo: validation - }); + validateInput: this.validateInput, + asyncValidationTask: (name: string) => this.validateNameAvailable(context, name), + })).trim(); context.valuesToMask.push(context.newDTSName); } @@ -28,11 +29,37 @@ export class DurableTaskSchedulerNameStep { + schedulerName = schedulerName.trim(); + + try { + const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); + const scheduler: DurableTaskSchedulerResource | undefined = await this._schedulerClient.getScheduler(nonNullProp(context, 'subscription'), resourceGroupName, schedulerName); + + if (scheduler) { + return localize('schedulerAlreadyExists', 'A scheduler with name "{0}" already exists in resource group "{1}".', schedulerName, resourceGroupName); + } + } catch (e) { + console.log(e) + } + + return undefined; + } // async validation // Also check that other schedulers / hubs with same name don't exist diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index b4c1f2032..367c2be9d 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -15,7 +15,7 @@ import { localize } from "../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { createActivityContext } from "../../../../utils/activityUtils"; import { type IFuncDeployContext } from "../../../deploy/deploy"; -import { DTSConnectionTypeListStep } from "./DTSConnectionTypeListStep"; +import { DTSConnectionListStep } from "./DTSConnectionListStep"; import { type IDTSAzureConnectionWizardContext } from "./IDTSConnectionWizardContext"; type DTSConnectionContext = IFuncDeployContext & ISubscriptionActionContext & { subscription: AzureSubscription }; @@ -56,7 +56,7 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien title: localize('prepareDTSConnection', 'Prepare durable task scheduler connection'), promptSteps: [ new ResourceGroupListStep(), - new DTSConnectionTypeListStep(availableDeployConnectionTypes), + new DTSConnectionListStep(availableDeployConnectionTypes), ], showLoadingPrompt: true, }); diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts index 1b0103627..b77429578 100644 --- a/src/debug/durable/validateDTSConnectionPreDebug.ts +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizard, type IActionContext } from "@microsoft/vscode-azext-utils"; -import { DTSConnectionTypeListStep } from "../../commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep"; +import { DTSConnectionListStep } from "../../commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionListStep"; import { type IDTSConnectionWizardContext } from "../../commands/appSettings/connectionSettings/durableTaskScheduler/IDTSConnectionWizardContext"; import { CodeAction, ConnectionKey, ConnectionType } from "../../constants"; import { getLocalSettingsConnectionString } from "../../funcConfig/local.settings"; @@ -30,7 +30,7 @@ export async function validateDTSConnectionPreDebug(context: IActionContext, pro const wizard: AzureWizard = new AzureWizard(wizardContext, { title: localize('acquireDTSConnection', 'Acquire DTS connection'), - promptSteps: [new DTSConnectionTypeListStep(availableDebugConnectionTypes)], + promptSteps: [new DTSConnectionListStep(availableDebugConnectionTypes)], }); await wizard.prompt(); From 7282e90c80ca9cc5e2555f923af0654770987683 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:34:53 -0500 Subject: [PATCH 34/52] Update validation logic for name availability --- .../azure/DurableTaskHubNameStep.ts | 10 ++++++---- .../azure/DurableTaskSchedulerNameStep.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index 808b3ccca..e6cdb0b20 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { parseAzureResourceId, type ParsedAzureResourceId } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizardPromptStep, nonNullProp, validationUtils } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, nonNullProp, parseError, validationUtils, type IParsedError } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; @@ -68,10 +68,12 @@ export class DurableTaskHubNameStep if (hub) { return localize('hubAlreadyExists', 'A task hub with name "{0}" already exists with scheduler "{1}".', hubName, parsedScheduler.resourceName); } + + // `getSchedulerTaskHub` should return 'undefined' when a 404 status code is encountered + return undefined; } catch (e) { - console.log(e) + const pe: IParsedError = parseError(e); + return localize('validateNameError', 'Failed to validate name availability: "{0}"', pe.message); } - - return undefined; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts index 34c46aca4..b404928d0 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, validationUtils } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, parseError, validationUtils, type IParsedError } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; @@ -54,11 +54,13 @@ export class DurableTaskSchedulerNameStep Date: Mon, 23 Jun 2025 15:53:47 -0500 Subject: [PATCH 35/52] Address todo --- .../FunctionAppUserAssignedIdentitiesListStep.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts index eba6b531d..2a6b182b7 100644 --- a/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts +++ b/src/commands/identity/listUserAssignedIdentities/FunctionAppUserAssignedIdentitiesListStep.ts @@ -7,6 +7,7 @@ import { type ManagedServiceIdentityClient } from '@azure/arm-msi'; import { type ParsedSite } from '@microsoft/vscode-azext-azureappservice'; import { createAuthorizationManagementClient, createManagedServiceIdentityClient, parseAzureResourceId, uiUtils, type ParsedAzureResourceId, type Role } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardPromptStep, nonNullProp, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; +import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; import { type IFunctionAppUserAssignedIdentitiesContext } from './IFunctionAppUserAssignedIdentitiesContext'; @@ -70,7 +71,10 @@ export class FunctionAppUserAssignedIdentitiesListStep { From 9c964140cbe80f421f8001e25efc42b4ce0d400b Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:30:12 -0500 Subject: [PATCH 36/52] Update location logic --- package-lock.json | 8 ++++---- package.json | 2 +- .../azure/DurableTaskHubNameStep.ts | 12 +++++------- .../azure/DurableTaskSchedulerListStep.ts | 2 -- .../azure/DurableTaskSchedulerNameStep.ts | 16 +++++----------- .../validateDTSConnection.ts | 11 ++++++++++- src/commands/deploy/deploy.ts | 4 +++- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 555bebecd..6038b7ab9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", + "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", @@ -1234,9 +1234,9 @@ } }, "node_modules/@microsoft/vscode-azext-azureutils": { - "version": "3.3.3", - "resolved": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", - "integrity": "sha512-v1zH43lvLdlSwX8EaivElXiVriCVslxb5iVy55CsRDTLOEftunx2EOlqjKYFBEbIkySfXS6Ok9Le/FuMUzxtXA==", + "version": "3.3.4", + "resolved": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", + "integrity": "sha512-YfRBj5o0D8rW5UlIWFoXA+RX2TZoYluvQrUGoTOuGCBgZsvNARtvH0xbmy7Zg7gRdFUo6KsFo34OpvJkQJkYlw==", "license": "MIT", "dependencies": { "@azure/arm-authorization": "^9.0.0", diff --git a/package.json b/package.json index e8cbc34c8..3e076e94a 100644 --- a/package.json +++ b/package.json @@ -1482,7 +1482,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.3.tgz", + "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts index e6cdb0b20..93f8f186f 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskHubNameStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { parseAzureResourceId, type ParsedAzureResourceId } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizardPromptStep, nonNullProp, parseError, validationUtils, type IParsedError } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, nonNullProp, validationUtils } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskHubResource, type DurableTaskSchedulerClient } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; @@ -68,12 +68,10 @@ export class DurableTaskHubNameStep if (hub) { return localize('hubAlreadyExists', 'A task hub with name "{0}" already exists with scheduler "{1}".', hubName, parsedScheduler.resourceName); } - - // `getSchedulerTaskHub` should return 'undefined' when a 404 status code is encountered - return undefined; - } catch (e) { - const pe: IParsedError = parseError(e); - return localize('validateNameError', 'Failed to validate name availability: "{0}"', pe.message); + } catch { + // Do nothing } + + return undefined; } } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts index b2a75f3b9..a24ad255a 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerListStep.ts @@ -47,8 +47,6 @@ export class DurableTaskSchedulerListStep[] = []; if (!context.dts) { - // Note: The location offering for this provider isn't 1:1 with what's available for the function app - // Todo: We should probably update the behavior of LocationListStep so that we re-verify the provider locations even if the location is already set LocationListStep.addProviderForFiltering(context as unknown as ILocationWizardContext, DurableTaskProvider, DurableTaskSchedulersResourceType); LocationListStep.addStep(context, promptSteps as AzureWizardPromptStep[]); diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts index b404928d0..0149c0b69 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/azure/DurableTaskSchedulerNameStep.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, parseError, validationUtils, type IParsedError } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, validationUtils } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../../localize"; import { HttpDurableTaskSchedulerClient, type DurableTaskSchedulerClient, type DurableTaskSchedulerResource } from "../../../../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type IDTSAzureConnectionWizardContext } from "../IDTSConnectionWizardContext"; @@ -54,16 +54,10 @@ export class DurableTaskSchedulerNameStep = new AzureWizard(wizardContext, { title: localize('prepareDTSConnection', 'Prepare durable task scheduler connection'), promptSteps: [ diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index ccc43acf4..f9d4b4ceb 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -5,7 +5,7 @@ import { type Site, type SiteConfigResource, type StringDictionary } from '@azure/arm-appservice'; import { getDeployFsPath, getDeployNode, deploy as innerDeploy, showDeployConfirmation, type IDeployContext, type IDeployPaths } from '@microsoft/vscode-azext-azureappservice'; -import { ResourceGroupListStep } from '@microsoft/vscode-azext-azureutils'; +import { LocationListStep, ResourceGroupListStep, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils'; import { DialogResponses, subscriptionExperience, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import type * as vscode from 'vscode'; @@ -96,6 +96,8 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | resourceGroup: (await ResourceGroupListStep.getResourceGroups(Object.assign(context, subscriptionContext))).find(rg => rg.name === node.site.resourceGroup), }); + await LocationListStep.setAutoSelectLocation(context as unknown as ILocationWizardContext, node.site.location); + if (node.contextValue.includes('container')) { const learnMoreLink: string = 'https://aka.ms/deployContainerApps' await context.ui.showWarningMessage(localize('containerFunctionAppError', 'Deploy is not currently supported for containerized function apps within the Azure Functions extension. Please read here to learn how to deploy your project.'), { learnMoreLink }); From 71f11b7f18444bec722139e5c9c1f558bb23c87c Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:48:09 -0500 Subject: [PATCH 37/52] Remove comment --- .../durableTaskScheduler/validateDTSConnection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index 283f11276..9abe2933a 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -38,7 +38,6 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien const remoteDTSEndpoint: string | undefined = tryGetDTSEndpoint(remoteDTSConnection); const availableDeployConnectionTypes = new Set([ConnectionType.Azure]); - // Spread the properties onto a new wizardContext so that we can initiate a separate activity log entry const wizardContext: IDTSAzureConnectionWizardContext = { ...context, ...await createActivityContext(), From 55169b81b2e568f479d9f6b02b1558b76d594ea5 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:49:50 -0500 Subject: [PATCH 38/52] Update location logic --- .../durableTaskScheduler/validateDTSConnection.ts | 11 +++-------- src/commands/deploy/deploy.ts | 4 +--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index 9abe2933a..aa3f610a9 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -5,7 +5,7 @@ import { type StringDictionary } from "@azure/arm-appservice"; import { type ParsedSite, type SiteClient } from "@microsoft/vscode-azext-azureappservice"; -import { LocationListStep, ResourceGroupListStep, type AzExtLocation } from "@microsoft/vscode-azext-azureutils"; +import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils"; import { AzureWizard, nonNullValueAndProp, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { CodeAction, ConnectionKey, ConnectionType } from "../../../../constants"; @@ -51,14 +51,9 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien suggestedDTSHubNameLocalSettings: localDTSHubName, }; - const autoSelectLocation: AzExtLocation | undefined = LocationListStep.getAutoSelectLocation(wizardContext); - - // Always reset location to avoid mismatches with the Durable Task provider offering. If a location was already set and is not valid for this resource type, deployment may fail. + // Always reset location to avoid potential mismatches with the Durable Task provider offering. If a location was already set and is not valid for this resource type, deployment may fail. LocationListStep.resetLocation(wizardContext); - - if (autoSelectLocation) { - await LocationListStep.setAutoSelectLocation(wizardContext, autoSelectLocation.name); - } + await LocationListStep.setAutoSelectLocation(wizardContext, site.location); const wizard: AzureWizard = new AzureWizard(wizardContext, { title: localize('prepareDTSConnection', 'Prepare durable task scheduler connection'), diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index f9d4b4ceb..ccc43acf4 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -5,7 +5,7 @@ import { type Site, type SiteConfigResource, type StringDictionary } from '@azure/arm-appservice'; import { getDeployFsPath, getDeployNode, deploy as innerDeploy, showDeployConfirmation, type IDeployContext, type IDeployPaths } from '@microsoft/vscode-azext-azureappservice'; -import { LocationListStep, ResourceGroupListStep, type ILocationWizardContext } from '@microsoft/vscode-azext-azureutils'; +import { ResourceGroupListStep } from '@microsoft/vscode-azext-azureutils'; import { DialogResponses, subscriptionExperience, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import type * as vscode from 'vscode'; @@ -96,8 +96,6 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | resourceGroup: (await ResourceGroupListStep.getResourceGroups(Object.assign(context, subscriptionContext))).find(rg => rg.name === node.site.resourceGroup), }); - await LocationListStep.setAutoSelectLocation(context as unknown as ILocationWizardContext, node.site.location); - if (node.contextValue.includes('container')) { const learnMoreLink: string = 'https://aka.ms/deployContainerApps' await context.ui.showWarningMessage(localize('containerFunctionAppError', 'Deploy is not currently supported for containerized function apps within the Azure Functions extension. Please read here to learn how to deploy your project.'), { learnMoreLink }); From c73c58e25f395e46bcb5a40f068f71e57473d363 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:02:30 -0500 Subject: [PATCH 39/52] Add comment to validateDTSConnection --- .../durableTaskScheduler/validateDTSConnection.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index aa3f610a9..354f3648f 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -21,6 +21,13 @@ import { type IDTSAzureConnectionWizardContext } from "./IDTSConnectionWizardCon type DTSConnectionContext = IFuncDeployContext & ISubscriptionActionContext & { subscription: AzureSubscription }; type DTSConnection = { [ConnectionKey.DTS]?: string, [ConnectionKey.DTSHub]?: string }; +/** + * A pre-flight deployment operation that ensures that: + * + * a) A remote DTS connection already exists for the given function app or + * + * b) That a new DTS resource and hub is created and ready to be added for use by the function app + */ export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, site: ParsedSite, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); From 8173f6aa69d989823a4b0d79975008cf2dbab677 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:08:34 -0500 Subject: [PATCH 40/52] Update comment --- .../durableTaskScheduler/validateDTSConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index 354f3648f..de69b22b7 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -26,7 +26,7 @@ type DTSConnection = { [ConnectionKey.DTS]?: string, [ConnectionKey.DTSHub]?: st * * a) A remote DTS connection already exists for the given function app or * - * b) That a new DTS resource and hub is created and ready to be added for use by the function app + * b) That a new DTS resource and hub is created and ready for connection to the function app */ export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, site: ParsedSite, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); From 1e34b0bcaa7d62c6b9c3781db8f367bf6060cb22 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 25 Jun 2025 18:55:30 -0500 Subject: [PATCH 41/52] Revert deps --- package-lock.json | 14 +++++++------- package.json | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6038b7ab9..3cc5d6c58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "vscode-azurefunctions", - "version": "1.17.3", + "version": "1.17.4-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-azurefunctions", - "version": "1.17.3", + "version": "1.17.4-alpha", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@azure/arm-appinsights": "^5.0.0-alpha.20230530.1", "@azure/arm-appservice": "^15.0.0", "@azure/arm-cosmosdb": "^15.0.0", "@azure/arm-eventhub": "^5.1.0", + "@azure/arm-resourcegraph": "^5.0.0-beta.3", "@azure/arm-resources-profile-2020-09-01-hybrid": "^2.1.0", "@azure/arm-servicebus": "^5.0.0", "@azure/arm-sql": "^9.1.0", @@ -22,7 +23,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", + "@microsoft/vscode-azext-azureutils": "^3.3.3", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", @@ -1234,10 +1235,9 @@ } }, "node_modules/@microsoft/vscode-azext-azureutils": { - "version": "3.3.4", - "resolved": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", - "integrity": "sha512-YfRBj5o0D8rW5UlIWFoXA+RX2TZoYluvQrUGoTOuGCBgZsvNARtvH0xbmy7Zg7gRdFUo6KsFo34OpvJkQJkYlw==", - "license": "MIT", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.3.3.tgz", + "integrity": "sha512-oE7cAavHe2xB+HC/TfbBIbm5CmbWmmfa1Sa8PxXvlUMs905O0gv6vu4GvxYfRurLZk1L5rcwUX/KcMCFaKTJgg==", "dependencies": { "@azure/arm-authorization": "^9.0.0", "@azure/arm-authorization-profile-2020-09-01-hybrid": "^2.1.0", diff --git a/package.json b/package.json index 3e076e94a..2da4916d8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-azurefunctions", "displayName": "Azure Functions", "description": "%azureFunctions.description%", - "version": "1.17.3", + "version": "1.17.4-alpha", "publisher": "ms-azuretools", "icon": "resources/azure-functions.png", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", @@ -1473,6 +1473,7 @@ "@azure/arm-appservice": "^15.0.0", "@azure/arm-cosmosdb": "^15.0.0", "@azure/arm-eventhub": "^5.1.0", + "@azure/arm-resourcegraph": "^5.0.0-beta.3", "@azure/arm-resources-profile-2020-09-01-hybrid": "^2.1.0", "@azure/arm-servicebus": "^5.0.0", "@azure/arm-sql": "^9.1.0", @@ -1482,7 +1483,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "file:../vscode-azuretools/azure/microsoft-vscode-azext-azureutils-3.3.4.tgz", + "@microsoft/vscode-azext-azureutils": "^3.3.3", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", From 482862909b2dd2ec76f7fac3586b15e77f259a92 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 25 Jun 2025 18:56:23 -0500 Subject: [PATCH 42/52] Upgrade azure utils --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3cc5d6c58..4682834a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.3.3", + "@microsoft/vscode-azext-azureutils": "^3.4.0", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", @@ -1235,9 +1235,9 @@ } }, "node_modules/@microsoft/vscode-azext-azureutils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.3.3.tgz", - "integrity": "sha512-oE7cAavHe2xB+HC/TfbBIbm5CmbWmmfa1Sa8PxXvlUMs905O0gv6vu4GvxYfRurLZk1L5rcwUX/KcMCFaKTJgg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-3.4.0.tgz", + "integrity": "sha512-avjpW4Pf6W81l/c4lHIqx+Y9HL4Jlp90WmsIe2LFZHHIm/tyQpx42SGXUAY/31yzRF1KvmtWl352klGxM3IBUg==", "dependencies": { "@azure/arm-authorization": "^9.0.0", "@azure/arm-authorization-profile-2020-09-01-hybrid": "^2.1.0", diff --git a/package.json b/package.json index 2da4916d8..d4b1c9129 100644 --- a/package.json +++ b/package.json @@ -1483,7 +1483,7 @@ "@azure/storage-blob": "^12.5.0", "@microsoft/vscode-azext-azureappservice": "^3.6.3", "@microsoft/vscode-azext-azureappsettings": "^0.2.8", - "@microsoft/vscode-azext-azureutils": "^3.3.3", + "@microsoft/vscode-azext-azureutils": "^3.4.0", "@microsoft/vscode-azext-utils": "^3.1.1", "@microsoft/vscode-azureresources-api": "^2.0.4", "@microsoft/vscode-container-client": "^0.1.2", From 08ff543cb95dc1b3c7e18efbd947ac81398bca01 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:07:24 -0500 Subject: [PATCH 43/52] Revert accidental autoformatting --- .../durableTaskScheduler/createTaskHub.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands/durableTaskScheduler/createTaskHub.ts b/src/commands/durableTaskScheduler/createTaskHub.ts index 76ee462d9..6df8c4aa2 100644 --- a/src/commands/durableTaskScheduler/createTaskHub.ts +++ b/src/commands/durableTaskScheduler/createTaskHub.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, type ExecuteActivityContext, type IActionContext } from "@microsoft/vscode-azext-utils"; -import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -import { type Progress } from "vscode"; import { localize } from '../../localize'; -import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel"; +import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient"; +import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; +import { type Progress } from "vscode"; import { createActivityContext } from "../../utils/activityUtils"; interface ICreateTaskHubContext extends IActionContext, ExecuteActivityContext { @@ -58,13 +58,13 @@ export function createTaskHubCommandFactory(schedulerClient: DurableTaskSchedule } const wizardContext: ICreateTaskHubContext = - { - subscription: scheduler.subscription, - resourceGroup: scheduler.resourceGroup, - schedulerName: scheduler.name, - ...actionContext, - ...await createActivityContext() - }; + { + subscription: scheduler.subscription, + resourceGroup: scheduler.resourceGroup, + schedulerName: scheduler.name, + ...actionContext, + ...await createActivityContext() + }; const wizard = new AzureWizard( wizardContext, From 6829729d9c0494db48b23a8c5df7cf920338d83b Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:57:52 -0500 Subject: [PATCH 44/52] Improve comment --- .../durableTaskScheduler/validateDTSConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index de69b22b7..f5b3bf221 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -22,7 +22,7 @@ type DTSConnectionContext = IFuncDeployContext & ISubscriptionActionContext & { type DTSConnection = { [ConnectionKey.DTS]?: string, [ConnectionKey.DTSHub]?: string }; /** - * A pre-flight deployment operation that ensures that: + * A pre-flight operation that ensures that: * * a) A remote DTS connection already exists for the given function app or * From 6874d6d7f8b62786a3e55c42e41c896d7b91476c Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:16:00 -0500 Subject: [PATCH 45/52] Update comment --- .../durableTaskScheduler/validateDTSConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts index f5b3bf221..3d3e36ad5 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts @@ -53,7 +53,7 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, dts: remoteDTSEndpoint ? await getDTSResource(context, remoteDTSEndpoint) : undefined, - // If the local settings are using the emulator (i.e. localhost), it's fine because it won't match up with any remote resources and the suggestion will end up hidden from the user + // If the local settings are using the emulator (i.e. localhost), it's fine because it won't match up with any remote resources and there will be no suggestion for the user suggestedDTSEndpointLocalSettings: localDTSEndpoint ? tryGetDTSEndpoint(localDTSConnection) : undefined, suggestedDTSHubNameLocalSettings: localDTSHubName, }; From b594736a146b87c59613306679ca00eae1ed47ae Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:07:02 -0500 Subject: [PATCH 46/52] Update wizard title --- src/debug/durable/validateDTSConnectionPreDebug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug/durable/validateDTSConnectionPreDebug.ts b/src/debug/durable/validateDTSConnectionPreDebug.ts index 1b0103627..7047ea27b 100644 --- a/src/debug/durable/validateDTSConnectionPreDebug.ts +++ b/src/debug/durable/validateDTSConnectionPreDebug.ts @@ -29,7 +29,7 @@ export async function validateDTSConnectionPreDebug(context: IActionContext, pro }); const wizard: AzureWizard = new AzureWizard(wizardContext, { - title: localize('acquireDTSConnection', 'Acquire DTS connection'), + title: localize('prepareDTSDebugConnection', 'Prepare DTS debug connection'), promptSteps: [new DTSConnectionTypeListStep(availableDebugConnectionTypes)], }); From 2691c1d0aa9eb932cd02135eb18ec8f911d25416 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:02:43 -0700 Subject: [PATCH 47/52] Update connection prompts --- .../durableTaskScheduler/DTSConnectionTypeListStep.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts index 4a4a1cbb6..034ac4d5a 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionTypeListStep.ts @@ -23,7 +23,7 @@ export class DTSConnectionTypeListStep ex public async prompt(context: T): Promise { const connectAzureButton = { title: localize('connectAzureTaskScheduler', 'Connect Azure Task Scheduler'), data: ConnectionType.Azure }; const connectEmulatorButton = { title: useEmulator, data: ConnectionType.Emulator }; - const connectCustomDTSButton = { title: localize('connectCustomTaskScheduler', 'Connect Custom Task Scheduler'), data: ConnectionType.Custom }; + const connectCustomDTSButton = { title: localize('connectCustomTaskScheduler', 'Manually Set a Connection String'), data: ConnectionType.Custom }; const buttons: MessageItem[] = []; if (this.connectionTypes.has(ConnectionType.Azure)) { @@ -36,7 +36,7 @@ export class DTSConnectionTypeListStep ex buttons.push(connectCustomDTSButton); } - const message: string = localize('selectDTSConnection', 'In order to proceed, you must connect a Durable Task Scheduler for internal use by the Azure Functions runtime.'); + const message: string = localize('selectDTSConnection', 'Durable Functions needs to be configured to use a Durable Task Scheduler.'); context.dtsConnectionType = (await context.ui.showWarningMessage(message, { modal: true }, ...buttons) as { title: string; data: ConnectionType; From ad25e4f006e8ef388d02a1f7382e69d0476024b2 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:06:59 -0700 Subject: [PATCH 48/52] Add emulator started message --- .../durableTaskScheduler/DTSEmulatorStartStep.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts index a94a68600..0e234fb42 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSEmulatorStartStep.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { AzureWizardExecuteStep, nonNullValue } from '@microsoft/vscode-azext-utils'; -import { commands } from 'vscode'; +import { commands, window } from 'vscode'; import { ConnectionType } from '../../../../constants'; +import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; import { type DurableTaskSchedulerEmulator } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient'; import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; @@ -29,6 +30,12 @@ export class DTSEmulatorStartStep extends localize('couldNotFindEmulator', 'Internal error: Failed to retrieve info on the started DTS emulator.'), ); + const { schedulerEndpoint, dashboardEndpoint } = emulator; + + const message: string = localize('emulatorStartedMessage', `Durable Task Scheduler (DTS) emulator has been started by your container client at "{0}". Its dashboard is available at "{1}".`, schedulerEndpoint.toString(), dashboardEndpoint.toString()); + void window.showInformationMessage(message); + ext.outputChannel.appendLog(message); + context.newDTSConnection = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`; context.newDTSHubName = 'default'; } From 2e55e34f01648712d61eed227047ac552f9cc15e Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:26:24 -0700 Subject: [PATCH 49/52] Update DTSConnectionSetSettingStep --- .../durableTaskScheduler/DTSConnectionSetSettingStep.ts | 7 ++++--- .../durableTaskScheduler/copySchedulerConnectionString.ts | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts index 3c01f2f9b..f7c540564 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts @@ -5,17 +5,18 @@ import { nonNullProp } from '@microsoft/vscode-azext-utils'; import { ConnectionKey } from '../../../../constants'; +import { clientIdKeyword } from '../../../durableTaskScheduler/copySchedulerConnectionString'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; -export class DTSConnectionSetSettingStep extends SetConnectionSettingStepBase { +export class DTSConnectionSetSettingStep extends SetConnectionSettingStepBase { public priority: number = 240; public debugDeploySetting: ConnectionKey = ConnectionKey.DTS; public async execute(context: T): Promise { let newDTSConnectionSetting = nonNullProp(context, 'newDTSConnectionSetting'); - if ((context as unknown as IDTSAzureConnectionWizardContext).managedIdentity) { - newDTSConnectionSetting = newDTSConnectionSetting.replace('', (context as unknown as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? ''); + if ((context as IDTSAzureConnectionWizardContext).managedIdentity) { + newDTSConnectionSetting = newDTSConnectionSetting.replace(clientIdKeyword, (context as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? clientIdKeyword); } await this.setConnectionSetting(context, newDTSConnectionSetting); } diff --git a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts index f40461b3b..1e54971fc 100644 --- a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts +++ b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts @@ -104,6 +104,8 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur } } +export const clientIdKeyword: string = ''; + export function getSchedulerConnectionString(endpointUrl: string, authenticationType: SchedulerAuthenticationType): string { let schedulerConnectionString = `Endpoint=${endpointUrl};Authentication=` @@ -117,7 +119,7 @@ export function getSchedulerConnectionString(endpointUrl: string, authentication schedulerConnectionString += 'ManagedIdentity'; if (authenticationType === SchedulerAuthenticationType.UserAssignedIdentity) { - schedulerConnectionString += ';ClientID='; + schedulerConnectionString += `;ClientID=${clientIdKeyword}>`; } } From 67cf9570196e3e7c1cc5ce682c55f794e92a0448 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:13:16 -0700 Subject: [PATCH 50/52] Add feedback --- .../durableTaskScheduler/DTSConnectionSetSettingStep.ts | 4 ++-- .../{validateDTSConnection.ts => getDTSConnection.ts} | 5 +++-- src/commands/deploy/deploy.ts | 4 ++-- .../durableTaskScheduler/copySchedulerConnectionString.ts | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) rename src/commands/appSettings/connectionSettings/durableTaskScheduler/{validateDTSConnection.ts => getDTSConnection.ts} (91%) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts index f7c540564..feaa51ec7 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/DTSConnectionSetSettingStep.ts @@ -5,7 +5,7 @@ import { nonNullProp } from '@microsoft/vscode-azext-utils'; import { ConnectionKey } from '../../../../constants'; -import { clientIdKeyword } from '../../../durableTaskScheduler/copySchedulerConnectionString'; +import { clientIdKey } from '../../../durableTaskScheduler/copySchedulerConnectionString'; import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase'; import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'; @@ -16,7 +16,7 @@ export class DTSConnectionSetSettingStep { let newDTSConnectionSetting = nonNullProp(context, 'newDTSConnectionSetting'); if ((context as IDTSAzureConnectionWizardContext).managedIdentity) { - newDTSConnectionSetting = newDTSConnectionSetting.replace(clientIdKeyword, (context as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? clientIdKeyword); + newDTSConnectionSetting = newDTSConnectionSetting.replace(clientIdKey, (context as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? clientIdKey); } await this.setConnectionSetting(context, newDTSConnectionSetting); } diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts similarity index 91% rename from src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts rename to src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts index 3d3e36ad5..b52638ef5 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts @@ -28,7 +28,7 @@ type DTSConnection = { [ConnectionKey.DTS]?: string, [ConnectionKey.DTSHub]?: st * * b) That a new DTS resource and hub is created and ready for connection to the function app */ -export async function validateDTSConnection(context: DTSConnectionContext, client: SiteClient, site: ParsedSite, projectPath: string): Promise { +export async function getDTSConnectionIfNeeded(context: DTSConnectionContext, client: SiteClient, site: ParsedSite, projectPath: string): Promise { const app: StringDictionary = await client.listApplicationSettings(); const remoteDTSConnection: string | undefined = app?.properties?.[ConnectionKey.DTS]; @@ -53,7 +53,8 @@ export async function validateDTSConnection(context: DTSConnectionContext, clien action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, dts: remoteDTSEndpoint ? await getDTSResource(context, remoteDTSEndpoint) : undefined, - // If the local settings are using the emulator (i.e. localhost), it's fine because it won't match up with any remote resources and there will be no suggestion for the user + // Local settings connection string could include a useable remote resource, so try to suggest it if it's available + // If the local settings are pointing to an emulator (i.e. localhost), it's fine because it won't match up with any remote resources and thus won't show up as a suggestion for the user suggestedDTSEndpointLocalSettings: localDTSEndpoint ? tryGetDTSEndpoint(localDTSConnection) : undefined, suggestedDTSHubNameLocalSettings: localDTSHubName, }; diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index d793e7bcc..183a60104 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -24,7 +24,7 @@ import { treeUtils } from '../../utils/treeUtils'; import { getWorkspaceSetting } from '../../vsCodeConfig/settings'; import { verifyInitForVSCode } from '../../vsCodeConfig/verifyInitForVSCode'; import { type ISetConnectionSettingContext } from '../appSettings/connectionSettings/ISetConnectionSettingContext'; -import { validateDTSConnection } from '../appSettings/connectionSettings/durableTaskScheduler/validateDTSConnection'; +import { getDTSConnectionIfNeeded } from '../appSettings/connectionSettings/durableTaskScheduler/getDTSConnection'; import { validateEventHubsConnection } from '../appSettings/connectionSettings/eventHubs/validateEventHubsConnection'; import { validateSqlDbConnection } from '../appSettings/connectionSettings/sqlDatabase/validateSqlDbConnection'; import { getEolWarningMessages } from '../createFunctionApp/stacks/getStackPicks'; @@ -168,7 +168,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string | // Preliminary local validation done to ensure all required resources have been created and are available. Final deploy writes are made in 'verifyAppSettings' if (durableStorageType === DurableBackend.DTS) { - const dtsConnections = await validateDTSConnection(Object.assign(context, subscriptionContext), client, site, context.projectPath); + const dtsConnections = await getDTSConnectionIfNeeded(Object.assign(context, subscriptionContext), client, site, context.projectPath); context[ConnectionKey.DTS] = dtsConnections?.[ConnectionKey.DTS]; context[ConnectionKey.DTSHub] = dtsConnections?.[ConnectionKey.DTSHub]; } diff --git a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts index 1e54971fc..5457d5885 100644 --- a/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts +++ b/src/commands/durableTaskScheduler/copySchedulerConnectionString.ts @@ -104,7 +104,7 @@ export function copySchedulerConnectionStringCommandFactory(schedulerClient: Dur } } -export const clientIdKeyword: string = ''; +export const clientIdKey: string = ''; export function getSchedulerConnectionString(endpointUrl: string, authenticationType: SchedulerAuthenticationType): string { let schedulerConnectionString = `Endpoint=${endpointUrl};Authentication=` @@ -119,7 +119,7 @@ export function getSchedulerConnectionString(endpointUrl: string, authentication schedulerConnectionString += 'ManagedIdentity'; if (authenticationType === SchedulerAuthenticationType.UserAssignedIdentity) { - schedulerConnectionString += `;ClientID=${clientIdKeyword}>`; + schedulerConnectionString += `;ClientID=${clientIdKey}>`; } } From 86cb0fdf71b665a3dc250e726cff7417c798b3cf Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:22:25 -0700 Subject: [PATCH 51/52] Update comment --- .../durableTaskScheduler/getDTSConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts index b52638ef5..b46563aff 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts @@ -53,8 +53,8 @@ export async function getDTSConnectionIfNeeded(context: DTSConnectionContext, cl action: CodeAction.Deploy, dtsConnectionType: ConnectionType.Azure, dts: remoteDTSEndpoint ? await getDTSResource(context, remoteDTSEndpoint) : undefined, - // Local settings connection string could include a useable remote resource, so try to suggest it if it's available - // If the local settings are pointing to an emulator (i.e. localhost), it's fine because it won't match up with any remote resources and thus won't show up as a suggestion for the user + // Local settings connection string could point to a useable remote resource, so try to suggest it if one is detected. + // If the local settings are pointing to an emulator (i.e. localhost), it's not a concern because it won't actually match up with any remote resources and thus won't show up as a suggestion. suggestedDTSEndpointLocalSettings: localDTSEndpoint ? tryGetDTSEndpoint(localDTSConnection) : undefined, suggestedDTSHubNameLocalSettings: localDTSHubName, }; From 1e2091308a37eab98e44583a0962c94dea9220b6 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 14 Jul 2025 20:53:23 -0700 Subject: [PATCH 52/52] Fetch schedulers by subscription --- .../durableTaskScheduler/getDTSConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts index b46563aff..c49dd6d8a 100644 --- a/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts +++ b/src/commands/appSettings/connectionSettings/durableTaskScheduler/getDTSConnection.ts @@ -6,7 +6,7 @@ import { type StringDictionary } from "@azure/arm-appservice"; import { type ParsedSite, type SiteClient } from "@microsoft/vscode-azext-azureappservice"; import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizard, nonNullValueAndProp, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, parseError, type IParsedError, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { CodeAction, ConnectionKey, ConnectionType } from "../../../../constants"; import { ext } from "../../../../extensionVariables"; @@ -84,7 +84,7 @@ export async function getDTSConnectionIfNeeded(context: DTSConnectionContext, cl async function getDTSResource(context: DTSConnectionContext, dtsEndpoint: string): Promise { try { const client = new HttpDurableTaskSchedulerClient(); - const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulersByResourceGroup(context.subscription, nonNullValueAndProp(context.resourceGroup, 'name')) ?? []; + const schedulers: DurableTaskSchedulerResource[] = await client.getSchedulersBySubscription(context.subscription) ?? []; return schedulers.find(s => s.properties.endpoint === dtsEndpoint); } catch (e) { const pe: IParsedError = parseError(e);