Skip to content

Add Azure deployment support for DTS projects #4566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 59 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f858697
Add new storage type pick and host/local settings logic
MicroFish91 May 27, 2025
c84b288
Update host.json preview dep
MicroFish91 May 27, 2025
eb005a9
Install deps
MicroFish91 May 27, 2025
cbe1707
Update extension bundle
MicroFish91 May 27, 2025
4cb51ba
Update durable detection logic
MicroFish91 May 27, 2025
5d5e2dd
Remove extra prop
MicroFish91 May 29, 2025
8cb081c
Add .NET deps
MicroFish91 May 30, 2025
ffb7b43
Fix accidental change
MicroFish91 May 30, 2025
bd1c815
Revert todo
MicroFish91 May 30, 2025
d89c079
Add todo
MicroFish91 May 30, 2025
e201fe5
Update comment
MicroFish91 May 30, 2025
dece3e9
Update comment
MicroFish91 Jun 5, 2025
0e94b73
Fully working implementation
MicroFish91 Jun 10, 2025
e43c2aa
Update comment
MicroFish91 Jun 11, 2025
e96ceb9
Update comment again
MicroFish91 Jun 11, 2025
0529729
Formatting
MicroFish91 Jun 11, 2025
96eae40
Make storage connection type reuse logic more simple
MicroFish91 Jun 11, 2025
98dc295
Merge with main
MicroFish91 Jun 11, 2025
3a3512d
Merge branch 'main' of https://github.com/microsoft/vscode-azurefunct…
MicroFish91 Jun 12, 2025
612ec9c
Update connection types
MicroFish91 Jun 12, 2025
c676ce9
Revert autoformatting change
MicroFish91 Jun 12, 2025
2537cc2
Revert more autoformatting
MicroFish91 Jun 12, 2025
0da96c1
Add dts connection validation for azure deployment
MicroFish91 Jun 13, 2025
616f84b
Remove extra step
MicroFish91 Jun 13, 2025
a0f194b
Don't need to update this file
MicroFish91 Jun 13, 2025
5dfb869
Standardize name casing
MicroFish91 Jun 13, 2025
be7032e
Unexpected code path
MicroFish91 Jun 14, 2025
4284d23
Misc
MicroFish91 Jun 14, 2025
6d19b79
Fix some stuff, add location + todo
MicroFish91 Jun 14, 2025
34258ac
Misc
MicroFish91 Jun 16, 2025
7ba0ef6
WIP
MicroFish91 Jun 17, 2025
b9d4583
WIP
MicroFish91 Jun 21, 2025
414576e
Entire flow is working
MicroFish91 Jun 23, 2025
9a090e4
Add validation notes
MicroFish91 Jun 23, 2025
9942756
Rename step
MicroFish91 Jun 23, 2025
7282e90
Update validation logic for name availability
MicroFish91 Jun 23, 2025
b56e20a
Address todo
MicroFish91 Jun 23, 2025
9c96414
Update location logic
MicroFish91 Jun 23, 2025
71f11b7
Remove comment
MicroFish91 Jun 23, 2025
55169b8
Update location logic
MicroFish91 Jun 23, 2025
c73c58e
Add comment to validateDTSConnection
MicroFish91 Jun 24, 2025
8173f6a
Update comment
MicroFish91 Jun 24, 2025
1e34b0b
Revert deps
MicroFish91 Jun 25, 2025
4828629
Upgrade azure utils
MicroFish91 Jun 25, 2025
9cff4b3
Merge branch 'main' of https://github.com/microsoft/vscode-azurefunct…
MicroFish91 Jun 25, 2025
5f299ae
Merge with main
MicroFish91 Jun 26, 2025
08ff543
Revert accidental autoformatting
MicroFish91 Jun 26, 2025
6829729
Improve comment
MicroFish91 Jun 26, 2025
6874d6d
Update comment
MicroFish91 Jun 26, 2025
b594736
Update wizard title
MicroFish91 Jun 27, 2025
e83b450
Merge branch 'main' of https://github.com/microsoft/vscode-azurefunct…
MicroFish91 Jul 14, 2025
2691c1d
Update connection prompts
MicroFish91 Jul 14, 2025
ad25e4f
Add emulator started message
MicroFish91 Jul 14, 2025
cd67792
Merge with mwf/convinced-gray
MicroFish91 Jul 14, 2025
2e55e34
Update DTSConnectionSetSettingStep
MicroFish91 Jul 14, 2025
67cf957
Add feedback
MicroFish91 Jul 15, 2025
86cb0fd
Update comment
MicroFish91 Jul 15, 2025
1e20913
Fetch schedulers by subscription
MicroFish91 Jul 15, 2025
caa24ca
Merge with main
MicroFish91 Jul 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,7 @@
"@azure/storage-blob": "^12.5.0",
"@microsoft/vscode-azext-azureappservice": "^3.6.4",
"@microsoft/vscode-azext-azureappsettings": "^0.2.8",
"@microsoft/vscode-azext-azureutils": "^3.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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends ISetConnectionSettingContext> extends AzureWizardExecuteStep<T> {
public abstract readonly debugDeploySetting: ConnectionKeyValues;
public abstract readonly debugDeploySetting: ConnectionKey;

protected async setConnectionSetting(context: T, value: string): Promise<void> {
if (context.action === CodeAction.Deploy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends IAzureWebJobsStorageWizardContext> extends SetConnectionSettingStepBase<T> {
public priority: number = 230;
public debugDeploySetting: ConnectionKeyValues = ConnectionKey.Storage;
public debugDeploySetting: ConnectionKey = ConnectionKey.Storage;

public async execute(context: T): Promise<void> {
let value: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'

export class DTSConnectionCustomPromptStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
public async prompt(context: T): Promise<void> {
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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, DurableTaskProvider } from '../../../../constants';
import { useEmulator } from '../../../../constants-nls';
import { localize } from '../../../../localize';
import { DurableTaskSchedulerListStep } from './azure/DurableTaskSchedulerListStep';
import { DTSConnectionCustomPromptStep } from './custom/DTSConnectionCustomPromptStep';
import { DTSHubNameCustomPromptStep } from './custom/DTSHubNameCustomPromptStep';
import { DTSConnectionSetSettingStep } from './DTSConnectionSetSettingStep';
import { DTSHubNameSetSettingStep } from './DTSHubNameSetSettingStep';
import { DTSEmulatorStartStep } from './emulator/DTSEmulatorStartStep';
import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';

export class DTSConnectionListStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
constructor(readonly connectionTypes: Set<ConnectionType>) {
super();
}

public async prompt(context: T): Promise<void> {
const connectAzureButton = { title: localize('connectAzureTaskScheduler', 'Connect Azure Task Scheduler'), data: ConnectionType.Azure };
const connectEmulatorButton = { title: useEmulator, data: ConnectionType.Emulator };
const connectCustomDTSButton = { title: localize('connectCustomTaskScheduler', 'Manually Set a Connection String'), 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', '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;
}).data;

context.telemetry.properties.dtsConnectionType = context.dtsConnectionType;
}

public shouldPrompt(context: T): boolean {
return !context.dtsConnectionType;
}

public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
const promptSteps: AzureWizardPromptStep<T | IDTSAzureConnectionWizardContext>[] = [];
const executeSteps: AzureWizardExecuteStep<T | IDTSAzureConnectionWizardContext>[] = [];

switch (context.dtsConnectionType) {
case ConnectionType.Azure:
promptSteps.push(new DurableTaskSchedulerListStep());
executeSteps.push(new VerifyProvidersStep<IDTSAzureConnectionWizardContext>([DurableTaskProvider]));
break;
case ConnectionType.Emulator:
executeSteps.push(new DTSEmulatorStartStep());
break;
case ConnectionType.Custom:
promptSteps.push(
new DTSConnectionCustomPromptStep(),
new DTSHubNameCustomPromptStep(),
);
break;
default:
throw new Error(localize('unexpectedConnectionType', 'Internal error: Unexpected DTS connection type encountered: "{0}".', context.dtsConnectionType));
}

executeSteps.push(
new DTSConnectionSetSettingStep(),
new DTSHubNameSetSettingStep(),
);

return { promptSteps, executeSteps };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@

import { nonNullProp } from '@microsoft/vscode-azext-utils';
import { ConnectionKey } from '../../../../constants';
import { clientIdKey } from '../../../durableTaskScheduler/copySchedulerConnectionString';
import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase';
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
import { type IDTSAzureConnectionWizardContext, type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';

export class DTSConnectionSetSettingStep<T extends IDTSConnectionWizardContext> extends SetConnectionSettingStepBase<T> {
export class DTSConnectionSetSettingStep<T extends IDTSConnectionWizardContext | IDTSAzureConnectionWizardContext> extends SetConnectionSettingStepBase<T> {
public priority: number = 240;
public debugDeploySetting: ConnectionKey = ConnectionKey.DTS;

public async execute(context: T): Promise<void> {
await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnection'));
let newDTSConnectionSetting = nonNullProp(context, 'newDTSConnectionSetting');
if ((context as IDTSAzureConnectionWizardContext).managedIdentity) {
newDTSConnectionSetting = newDTSConnectionSetting.replace(clientIdKey, (context as IDTSAzureConnectionWizardContext).managedIdentity?.clientId ?? clientIdKey);
}
await this.setConnectionSetting(context, newDTSConnectionSetting);
}

public shouldExecute(context: T): boolean {
return !!context.newDTSConnection;
return !!context.newDTSConnectionSetting;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export class DTSEmulatorStartStep<T extends IDTSConnectionWizardContext> extends
void window.showInformationMessage(message);
ext.outputChannel.appendLog(message);

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext'

export class DTSHubNameCustomPromptStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
public async prompt(context: T): Promise<void> {
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)
})).trim();
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export class DTSHubNameSetSettingStep<T extends IDTSConnectionWizardContext> ext
public debugDeploySetting: ConnectionKey = ConnectionKey.DTSHub;

public async execute(context: T): Promise<void> {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,38 @@
* 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 } 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";

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 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;
dts?: DurableTaskSchedulerResource;

newDTSHubName?: string;
dtsHub?: DurableTaskHubResource;
}
Original file line number Diff line number Diff line change
@@ -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 { 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<T extends IDTSAzureConnectionWizardContext> extends AzureWizardExecuteStep<T> {
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<void> {
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'),
);
context.newDTSHubNameConnectionSetting = context.dtsHub.name;
}

public shouldExecute(context: T): boolean {
return !context.dtsHub;
}
}
Loading