Skip to content

Commit fed1afe

Browse files
authored
Consolidate durable project configure logic (#4628)
1 parent e86813f commit fed1afe

File tree

2 files changed

+150
-157
lines changed

2 files changed

+150
-157
lines changed

src/commands/createFunction/durableSteps/DurableProjectConfigureStep.ts

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

6-
import { AzExtFsExtra, AzureWizardExecuteStepWithActivityOutput } from '@microsoft/vscode-azext-utils';
6+
import { AzExtFsExtra, AzureWizardExecuteStepWithActivityOutput, parseError, type IParsedError } from '@microsoft/vscode-azext-utils';
77
import * as path from "path";
88
import { type Progress } from 'vscode';
99
import { ConnectionKey, DurableBackend, hostFileName, ProjectLanguage } from '../../../constants';
1010
import { viewOutput } from '../../../constants-nls';
1111
import { ext } from '../../../extensionVariables';
12-
import { type IHostJsonV2 } from '../../../funcConfig/host';
12+
import { type IDTSTaskJson, type IHostJsonV2, type INetheriteTaskJson, type ISqlTaskJson, type IStorageTaskJson } from '../../../funcConfig/host';
1313
import { MismatchBehavior, setLocalAppSetting } from '../../../funcConfig/local.settings';
1414
import { localize } from '../../../localize';
15+
import { cpUtils } from '../../../utils/cpUtils';
1516
import { durableUtils } from '../../../utils/durableUtils';
17+
import { pythonUtils } from '../../../utils/pythonUtils';
18+
import { venvUtils } from '../../../utils/venvUtils';
1619
import { type IFunctionWizardContext } from '../IFunctionWizardContext';
1720

1821
export class DurableProjectConfigureStep<T extends IFunctionWizardContext> extends AzureWizardExecuteStepWithActivityOutput<T> {
@@ -25,22 +28,22 @@ export class DurableProjectConfigureStep<T extends IFunctionWizardContext> exten
2528
protected getOutputLogFail(_context: T): string {
2629
return localize('failedToConfigureDurableProject', 'Failed to configure durable project settings.');
2730
}
28-
protected getOutputLogProgress(_context: T): string {
29-
return localize('configuringDurableProject', 'Configuring durable project settings...');
30-
}
3131
protected preDeployTask: string = 'funcHostStart';
3232
public stepName: string = 'DurableProjectConfigureStep';
3333
public priority: number = 225;
3434

35-
public async execute(context: T, _progress: Progress<{ message?: string; increment?: number }>): Promise<void> {
35+
public async execute(context: T, progress: Progress<{ message?: string; increment?: number }>): Promise<void> {
36+
progress.report({ message: localize('configuringDurableProject', 'Configuring durable project settings...') });
3637
await this.configureHostAndLocalSettingsJson(context);
37-
await durableUtils.tryInstallDurableDependencies(context);
38+
await this.tryInstallDurableDependencies(context);
3839
}
3940

4041
public shouldExecute(context: T): boolean {
4142
return !!context.newDurableStorageType;
4243
}
4344

45+
// #region Durable Task Local Settings
46+
4447
private async configureHostAndLocalSettingsJson(context: T): Promise<void> {
4548
const hostJsonPath: string = path.join(context.projectPath, hostFileName);
4649

@@ -65,15 +68,15 @@ export class DurableProjectConfigureStep<T extends IFunctionWizardContext> exten
6568

6669
switch (context.newDurableStorageType) {
6770
case DurableBackend.Storage:
68-
hostJson.extensions.durableTask = durableUtils.getDefaultStorageTaskConfig();
71+
hostJson.extensions.durableTask = this.getDefaultStorageTaskConfig();
6972
// Omit setting azureWebJobsStorage since it should already be initialized during 'createNewProject'
7073
break;
7174
case DurableBackend.Netherite:
72-
hostJson.extensions.durableTask = durableUtils.getDefaultNetheriteTaskConfig();
75+
hostJson.extensions.durableTask = this.getDefaultNetheriteTaskConfig();
7376
await setLocalAppSetting(context, context.projectPath, ConnectionKey.EventHubs, '', MismatchBehavior.Overwrite);
7477
break;
7578
case DurableBackend.DTS:
76-
hostJson.extensions.durableTask = durableUtils.getDefaultDTSTaskConfig();
79+
hostJson.extensions.durableTask = this.getDefaultDTSTaskConfig();
7780
// Non- .NET projects require a special preview extension bundle to work properly
7881
// Todo: Remove once this functionality is out of preview
7982
if (context.language !== ProjectLanguage.CSharp && context.language !== ProjectLanguage.FSharp) {
@@ -84,15 +87,147 @@ export class DurableProjectConfigureStep<T extends IFunctionWizardContext> exten
8487
ext.outputChannel.appendLog(localize('extensionBundlePreview', 'Updated "host.json" extension bundle to preview version to enable new DTS features.'));
8588
}
8689
await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTS, '', MismatchBehavior.Overwrite);
87-
await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTSHub, 'default', MismatchBehavior.Overwrite);
90+
await setLocalAppSetting(context, context.projectPath, ConnectionKey.DTSHub, '', MismatchBehavior.Overwrite);
8891
break;
8992
case DurableBackend.SQL:
90-
hostJson.extensions.durableTask = durableUtils.getDefaultSqlTaskConfig();
93+
hostJson.extensions.durableTask = this.getDefaultSqlTaskConfig();
9194
await setLocalAppSetting(context, context.projectPath, ConnectionKey.SQL, '', MismatchBehavior.Overwrite);
9295
break;
9396
default:
9497
}
9598

9699
await AzExtFsExtra.writeJSON(hostJsonPath, hostJson);
97100
}
101+
102+
private getDefaultStorageTaskConfig(): IStorageTaskJson {
103+
return {
104+
storageProvider: {
105+
type: DurableBackend.Storage,
106+
}
107+
};
108+
}
109+
110+
private getDefaultNetheriteTaskConfig(): INetheriteTaskJson {
111+
return {
112+
hubName: '',
113+
useGracefulShutdown: true,
114+
storageProvider: {
115+
type: DurableBackend.Netherite,
116+
StorageConnectionName: ConnectionKey.Storage,
117+
EventHubsConnectionName: ConnectionKey.EventHubs,
118+
}
119+
};
120+
}
121+
122+
private getDefaultDTSTaskConfig(): IDTSTaskJson {
123+
return {
124+
hubName: '%TASKHUB_NAME%',
125+
storageProvider: {
126+
type: DurableBackend.DTS,
127+
connectionStringName: ConnectionKey.DTS,
128+
}
129+
};
130+
}
131+
132+
private getDefaultSqlTaskConfig(): ISqlTaskJson {
133+
return {
134+
storageProvider: {
135+
type: DurableBackend.SQL,
136+
connectionStringName: ConnectionKey.SQL,
137+
taskEventLockTimeout: "00:02:00",
138+
createDatabaseIfNotExists: true,
139+
}
140+
};
141+
}
142+
143+
// #endregion Durable Task Local Settings
144+
145+
// #region Install Durable Dependencies
146+
147+
private async tryInstallDurableDependencies(context: IFunctionWizardContext): Promise<void> {
148+
switch (context.language) {
149+
case ProjectLanguage.Java:
150+
// Todo: Revisit when adding Java implementation
151+
break;
152+
case ProjectLanguage.CSharp:
153+
case ProjectLanguage.FSharp:
154+
await this.installDotnetDependencies(context);
155+
break;
156+
case ProjectLanguage.JavaScript:
157+
case ProjectLanguage.TypeScript:
158+
await this.installNodeDependencies(context);
159+
break;
160+
case ProjectLanguage.Python:
161+
await pythonUtils.addDependencyToRequirements(durableUtils.pythonDfPackage, context.projectPath);
162+
await venvUtils.runPipInstallCommandIfPossible(context.projectPath);
163+
break;
164+
case ProjectLanguage.PowerShell:
165+
// Todo: Revisit when adding PowerShell implementation
166+
break;
167+
default:
168+
}
169+
}
170+
171+
private async installDotnetDependencies(context: IFunctionWizardContext): Promise<void> {
172+
const packages: { name: string; prerelease?: boolean }[] = [];
173+
const isDotnetIsolated: boolean = /Isolated/i.test(context.functionTemplate?.id ?? '');
174+
175+
switch (context.newDurableStorageType) {
176+
case DurableBackend.Netherite:
177+
isDotnetIsolated ?
178+
packages.push({ name: durableUtils.dotnetIsolatedDfNetheritePackage }) :
179+
packages.push({ name: durableUtils.dotnetInProcDfNetheritePackage });
180+
break;
181+
case DurableBackend.DTS:
182+
// Todo: Remove prerelease flag once this functionality is out of preview
183+
isDotnetIsolated ?
184+
packages.push({ name: durableUtils.dotnetIsolatedDTSPackage, prerelease: true }) :
185+
packages.push({ name: durableUtils.dotnetInProcDTSPackage, prerelease: true });
186+
break;
187+
case DurableBackend.SQL:
188+
isDotnetIsolated ?
189+
packages.push({ name: durableUtils.dotnetIsolatedDfSqlPackage }) :
190+
packages.push({ name: durableUtils.dotnetInProcDfSqlPackage });
191+
break;
192+
case DurableBackend.Storage:
193+
default:
194+
}
195+
196+
// Although the templates should incorporate this package already, it is often included with an out-dated version
197+
// which can lead to errors on first run. To improve this experience for our users, ensure that the latest version is used.
198+
if (!isDotnetIsolated) {
199+
packages.push({ name: durableUtils.dotnetInProcDfBasePackage });
200+
}
201+
202+
const failedPackages: string[] = [];
203+
for (const p of packages) {
204+
try {
205+
const packageArgs: string[] = [p.name];
206+
if (p.prerelease) {
207+
packageArgs.push('--prerelease');
208+
}
209+
await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'dotnet', 'add', 'package', ...packageArgs);
210+
} catch {
211+
failedPackages.push(p.name);
212+
}
213+
}
214+
215+
if (failedPackages.length) {
216+
ext.outputChannel.appendLog(localize('durableDependencyInstallFailed', 'WARNING: Failed to install and update Durable Functions NuGet packages to the root .csproj project file. You may need to install the following packages manually: "{0}".', failedPackages.join('", "')));
217+
}
218+
}
219+
220+
private async installNodeDependencies(context: IFunctionWizardContext): Promise<void> {
221+
try {
222+
const packageVersion = context.languageModel === 4 ? '3' : '2';
223+
await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'npm', 'install', `${durableUtils.nodeDfPackage}@${packageVersion}`);
224+
} catch (error) {
225+
const pError: IParsedError = parseError(error);
226+
const dfDepInstallFailed: string = localize('failedToAddDurableNodeDependency', 'Failed to add or install the "{0}" dependency. Please inspect and verify if it needs to be added manually.', durableUtils.nodeDfPackage);
227+
ext.outputChannel.appendLog(pError.message);
228+
ext.outputChannel.appendLog(dfDepInstallFailed);
229+
}
230+
}
231+
232+
// #endregion Install Durable Dependencies
98233
}

src/utils/durableUtils.ts

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

6-
import { AzExtFsExtra, parseError, type IParsedError } from "@microsoft/vscode-azext-utils";
6+
import { AzExtFsExtra } from "@microsoft/vscode-azext-utils";
77
import * as path from "path";
88
import { type Uri } from "vscode";
99
import * as xml2js from "xml2js";
1010
import { type IFunctionWizardContext } from "../commands/createFunction/IFunctionWizardContext";
11-
import { ConnectionKey, DurableBackend, ProjectLanguage, hostFileName, requirementsFileName } from "../constants";
12-
import { ext } from "../extensionVariables";
13-
import { type IDTSTaskJson, type IHostJsonV2, type INetheriteTaskJson, type ISqlTaskJson, type IStorageTaskJson } from "../funcConfig/host";
14-
import { localize } from "../localize";
15-
import { cpUtils } from "./cpUtils";
11+
import { DurableBackend, ProjectLanguage, hostFileName, requirementsFileName } from "../constants";
12+
import { type IHostJsonV2 } from "../funcConfig/host";
1613
import { dotnetUtils } from "./dotnetUtils";
1714
import { hasNodeJsDependency } from "./nodeJsUtils";
1815
import { pythonUtils } from "./pythonUtils";
19-
import { venvUtils } from "./venvUtils";
2016
import { findFiles } from "./workspace";
2117

2218
export namespace durableUtils {
@@ -75,8 +71,6 @@ export namespace durableUtils {
7571
}
7672
}
7773

78-
// #region Verify Durable Dependencies
79-
8074
// Use workspace dependencies as an indicator to check whether the project already has durable storage setup
8175
export async function verifyHasDurableStorage(language: string | undefined, projectPath: string): Promise<boolean> {
8276
switch (language) {
@@ -124,140 +118,4 @@ export namespace durableUtils {
124118
const requirementsPath: string = path.join(projectPath, requirementsFileName);
125119
return await pythonUtils.hasDependencyInRequirements(pythonDfPackage, requirementsPath);
126120
}
127-
128-
// #endregion Verify Durable Dependencies
129-
130-
// #region Install Durable Dependencies
131-
132-
export async function tryInstallDurableDependencies(context: IFunctionWizardContext): Promise<void> {
133-
switch (context.language) {
134-
case ProjectLanguage.Java:
135-
// Todo: Revisit when adding Java implementation
136-
break;
137-
case ProjectLanguage.CSharp:
138-
case ProjectLanguage.FSharp:
139-
await installDotnetDependencies(context);
140-
break;
141-
case ProjectLanguage.JavaScript:
142-
case ProjectLanguage.TypeScript:
143-
await installNodeDependencies(context);
144-
break;
145-
case ProjectLanguage.Python:
146-
await pythonUtils.addDependencyToRequirements(pythonDfPackage, context.projectPath);
147-
await venvUtils.runPipInstallCommandIfPossible(context.projectPath);
148-
break;
149-
case ProjectLanguage.PowerShell:
150-
// Todo: Revisit when adding PowerShell implementation
151-
break;
152-
default:
153-
}
154-
}
155-
156-
async function installDotnetDependencies(context: IFunctionWizardContext): Promise<void> {
157-
const packages: { name: string; prerelease?: boolean }[] = [];
158-
const isDotnetIsolated: boolean = /Isolated/i.test(context.functionTemplate?.id ?? '');
159-
160-
switch (context.newDurableStorageType) {
161-
case DurableBackend.Netherite:
162-
isDotnetIsolated ?
163-
packages.push({ name: dotnetIsolatedDfNetheritePackage }) :
164-
packages.push({ name: dotnetInProcDfNetheritePackage });
165-
break;
166-
case DurableBackend.DTS:
167-
// Todo: Remove prerelease flag once this functionality is out of preview
168-
isDotnetIsolated ?
169-
packages.push({ name: dotnetIsolatedDTSPackage, prerelease: true }) :
170-
packages.push({ name: dotnetInProcDTSPackage, prerelease: true });
171-
break;
172-
case DurableBackend.SQL:
173-
isDotnetIsolated ?
174-
packages.push({ name: dotnetIsolatedDfSqlPackage }) :
175-
packages.push({ name: dotnetInProcDfSqlPackage });
176-
break;
177-
case DurableBackend.Storage:
178-
default:
179-
}
180-
181-
// Although the templates should incorporate this package already, it is often included with an out-dated version
182-
// which can lead to errors on first run. To improve this experience for our users, ensure that the latest version is used.
183-
if (!isDotnetIsolated) {
184-
packages.push({ name: dotnetInProcDfBasePackage });
185-
}
186-
187-
const failedPackages: string[] = [];
188-
for (const p of packages) {
189-
try {
190-
const packageArgs: string[] = [p.name];
191-
if (p.prerelease) {
192-
packageArgs.push('--prerelease');
193-
}
194-
await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'dotnet', 'add', 'package', ...packageArgs);
195-
} catch {
196-
failedPackages.push(p.name);
197-
}
198-
}
199-
200-
if (failedPackages.length) {
201-
ext.outputChannel.appendLog(localize('durableDependencyInstallFailed', 'WARNING: Failed to install and update Durable Functions NuGet packages to the root .csproj project file. You may need to install the following packages manually: "{0}".', failedPackages.join('", "')));
202-
}
203-
}
204-
205-
async function installNodeDependencies(context: IFunctionWizardContext): Promise<void> {
206-
try {
207-
const packageVersion = context.languageModel === 4 ? '3' : '2';
208-
await cpUtils.executeCommand(ext.outputChannel, context.projectPath, 'npm', 'install', `${nodeDfPackage}@${packageVersion}`);
209-
} catch (error) {
210-
const pError: IParsedError = parseError(error);
211-
const dfDepInstallFailed: string = localize('failedToAddDurableNodeDependency', 'Failed to add or install the "{0}" dependency. Please inspect and verify if it needs to be added manually.', nodeDfPackage);
212-
ext.outputChannel.appendLog(pError.message);
213-
ext.outputChannel.appendLog(dfDepInstallFailed);
214-
}
215-
}
216-
217-
// #endregion Install Durable Dependencies
218-
219-
// #region Durable Task Configs
220-
221-
export function getDefaultStorageTaskConfig(): IStorageTaskJson {
222-
return {
223-
storageProvider: {
224-
type: DurableBackend.Storage,
225-
}
226-
};
227-
}
228-
229-
export function getDefaultNetheriteTaskConfig(hubName: string = ''): INetheriteTaskJson {
230-
return {
231-
hubName,
232-
useGracefulShutdown: true,
233-
storageProvider: {
234-
type: DurableBackend.Netherite,
235-
StorageConnectionName: ConnectionKey.Storage,
236-
EventHubsConnectionName: ConnectionKey.EventHubs,
237-
}
238-
};
239-
}
240-
241-
export function getDefaultDTSTaskConfig(): IDTSTaskJson {
242-
return {
243-
hubName: '%TASKHUB_NAME%',
244-
storageProvider: {
245-
type: DurableBackend.DTS,
246-
connectionStringName: ConnectionKey.DTS,
247-
}
248-
};
249-
}
250-
251-
export function getDefaultSqlTaskConfig(): ISqlTaskJson {
252-
return {
253-
storageProvider: {
254-
type: DurableBackend.SQL,
255-
connectionStringName: ConnectionKey.SQL,
256-
taskEventLockTimeout: "00:02:00",
257-
createDatabaseIfNotExists: true,
258-
}
259-
};
260-
}
261-
262-
// #endregion Durable Task Configs
263121
}

0 commit comments

Comments
 (0)