Skip to content

Add DTS preview support for Create Function and remove Netherite from list of new storage options #4527

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions src/commands/createFunction/IFunctionWizardContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -17,7 +17,7 @@ export interface IFunctionWizardContext extends Partial<ISubscriptionContext>, I

// Durable Functions
hasDurableStorage?: boolean;
newDurableStorageType?: DurableBackendValues;
newDurableStorageType?: DurableBackend;

useStorageEmulator?: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -72,6 +72,19 @@ export class DurableProjectConfigureStep<T extends IFunctionWizardContext> exten
hostJson.extensions.durableTask = durableUtils.getDefaultNetheriteTaskConfig();
await setLocalAppSetting(context, context.projectPath, ConnectionKey.EventHubs, '', MismatchBehavior.Overwrite);
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 && context.language !== ProjectLanguage.FSharp) {
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);
break;
case DurableBackend.SQL:
hostJson.extensions.durableTask = durableUtils.getDefaultSqlTaskConfig();
await setLocalAppSetting(context, context.projectPath, ConnectionKey.SQL, '', MismatchBehavior.Overwrite);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -19,24 +19,19 @@ export class DurableStorageTypePromptStep<T extends IFunctionWizardContext> exte
}

public async prompt(context: T): Promise<void> {
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<DurableBackendValues | undefined>[] = [
{ 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');
}
Expand Down
5 changes: 4 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}

Expand All @@ -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];
Expand Down
10 changes: 9 additions & 1 deletion src/funcConfig/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface IHostJsonV2 {
};
}

export type IDurableTaskJson = IStorageTaskJson | INetheriteTaskJson | ISqlTaskJson;
export type IDurableTaskJson = IStorageTaskJson | INetheriteTaskJson | IDTSTaskJson | ISqlTaskJson;

export interface IStorageTaskJson {
storageProvider?: {
Expand All @@ -53,6 +53,14 @@ export interface INetheriteTaskJson {
}
}

export interface IDTSTaskJson {
hubName?: string;
storageProvider?: {
type?: DurableBackend.DTS;
connectionStringName?: string;
}
}

export interface ISqlTaskJson {
storageProvider?: {
type?: DurableBackend.SQL;
Expand Down
55 changes: 38 additions & 17 deletions src/utils/durableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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.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 nodeDfPackage: string = 'durable-functions';
export const pythonDfPackage: string = 'azure-functions-durable';
Expand Down Expand Up @@ -62,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:
Expand Down Expand Up @@ -150,39 +154,46 @@ export namespace durableUtils {
}

async function installDotnetDependencies(context: IFunctionWizardContext): Promise<void> {
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:
// Todo: Remove prerelease flag once this functionality is out of preview
isDotnetIsolated ?
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 lead to errors on first run. To improve this experience for our users, ensure that the latest version is used.
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);
}
}

Expand Down Expand Up @@ -215,9 +226,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,
Expand All @@ -228,6 +239,16 @@ export namespace durableUtils {
};
}

export function getDefaultDTSTaskConfig(): IDTSTaskJson {
return {
hubName: '%TASKHUB_NAME%',
storageProvider: {
type: DurableBackend.DTS,
connectionStringName: ConnectionKey.DTS,
}
};
}

export function getDefaultSqlTaskConfig(): ISqlTaskJson {
return {
storageProvider: {
Expand Down