Skip to content

Commit 087b06d

Browse files
authored
Support for domain name label scopes (#4909)
1 parent 812b074 commit 087b06d

File tree

9 files changed

+117
-39
lines changed

9 files changed

+117
-39
lines changed

package-lock.json

Lines changed: 28 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,7 @@
14741474
},
14751475
"dependencies": {
14761476
"@azure/arm-appinsights": "^5.0.0-alpha.20230530.1",
1477-
"@azure/arm-appservice": "^15.0.0",
1477+
"@azure/arm-appservice": "^18.0.0",
14781478
"@azure/arm-cosmosdb": "^15.0.0",
14791479
"@azure/arm-eventhub": "^5.1.0",
14801480
"@azure/arm-resourcegraph": "^5.0.0-beta.3",
@@ -1485,7 +1485,7 @@
14851485
"@azure/core-client": "^1.7.3",
14861486
"@azure/core-rest-pipeline": "^1.11.0",
14871487
"@azure/storage-blob": "^12.5.0",
1488-
"@microsoft/vscode-azext-azureappservice": "^4.0.1",
1488+
"@microsoft/vscode-azext-azureappservice": "^4.1.0",
14891489
"@microsoft/vscode-azext-azureappsettings": "^1.0.0",
14901490
"@microsoft/vscode-azext-azureutils": "^4.0.1",
14911491
"@microsoft/vscode-azext-utils": "^4.0.3",

src/FunctionAppResolver.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { type AppResource, type AppResourceResolver } from "@microsoft/vscode-az
1111
import { ResolvedFunctionAppResource } from "./tree/ResolvedFunctionAppResource";
1212
import { ResolvedContainerizedFunctionAppResource } from "./tree/containerizedFunctionApp/ResolvedContainerizedFunctionAppResource";
1313
import { createResourceGraphClient } from "./utils/azureClients";
14+
import { getGlobalSetting } from "./vsCodeConfig/settings";
1415

1516
export type FunctionAppModel = {
1617
isFlex: boolean,
@@ -39,6 +40,7 @@ export class FunctionAppResolver implements AppResourceResolver {
3940
private loaded: boolean = false;
4041
private siteCacheLastUpdated = 0;
4142
private siteCache: Map<string, FunctionAppModel> = new Map<string, FunctionAppModel>();
43+
private siteNameCounter: Map<string, number> = new Map<string, number>();
4244
private listFunctionAppsTask: Promise<void> | undefined;
4345

4446
public async resolveResource(subContext: ISubscriptionContext, resource: AppResource): Promise<ResolvedFunctionAppResource | ResolvedContainerizedFunctionAppResource | undefined> {
@@ -48,8 +50,10 @@ export class FunctionAppResolver implements AppResourceResolver {
4850
// do this before the graph client is created because the async graph client create takes enough time to mess up the following resolves
4951
this.loaded = false;
5052
this.siteCache.clear(); // clear the cache before fetching new data
53+
this.siteNameCounter.clear();
5154
this.siteCacheLastUpdated = Date.now();
5255
const graphClient = await createResourceGraphClient({ ...context, ...subContext });
56+
5357
async function fetchAllApps(graphClient: ResourceGraphClient, subContext: ISubscriptionContext, resolver: FunctionAppResolver): Promise<void> {
5458
const query = `resources | where type == 'microsoft.web/sites' and kind contains 'functionapp' and kind !contains 'workflowapp'`;
5559

@@ -65,6 +69,7 @@ export class FunctionAppResolver implements AppResourceResolver {
6569
const record = response.data as Record<string, FunctionQueryModel>;
6670
// seems as if properties can be null, so we need to check for that
6771
Object.values(record).forEach(data => {
72+
resolver.countSiteName(data.name);
6873
const dataModel: FunctionAppModel = {
6974
isFlex: data.properties?.sku?.toLocaleLowerCase() === 'flexconsumption',
7075
id: data.id,
@@ -98,18 +103,36 @@ export class FunctionAppResolver implements AppResourceResolver {
98103
await this.listFunctionAppsTask;
99104
}
100105

106+
const groupBy: string | undefined = getGlobalSetting<string>('groupBy', 'azureResourceGroups');
107+
const hasDuplicateSiteName: boolean = (this.siteNameCounter.get(resource.name) ?? 1) > 1;
108+
101109
const siteModel = this.siteCache.get(nonNullProp(resource, 'id').toLowerCase());
102110
if (!siteModel || siteModel.kind === 'functionapp,linux,container,azurecontainerapps') {
111+
// Edge Case: `siteModel` can be `undefined` after calling `createFunctionApp` because the result does not propagate to MS Graph in time; a fallback fetch + count is needed
112+
if (!siteModel) {
113+
this.countSiteName(resource.name);
114+
}
115+
103116
// if the site model is not found or if it's a containerized function app, we need the full site details
104117
const client = await createWebSiteClient({ ...context, ...subContext });
105118
const fullSite = await client.webApps.get(getResourceGroupFromId(resource.id), resource.name);
119+
106120
if (fullSite.kind === 'functionapp,linux,container,azurecontainerapps') {
107-
return ResolvedContainerizedFunctionAppResource.createResolvedFunctionAppResource(context, subContext, fullSite);
121+
return ResolvedContainerizedFunctionAppResource.createResolvedFunctionAppResource(context, subContext, fullSite, {
122+
// Display the location as well if there is a duplicate name, otherwise they won't be easily distinguishable in the tree view
123+
showLocationInTreeItemDescription: groupBy === 'resourceType' && hasDuplicateSiteName,
124+
});
108125
}
109126

110-
return new ResolvedFunctionAppResource(subContext, fullSite);
127+
return new ResolvedFunctionAppResource(subContext, fullSite, undefined, {
128+
// Display the location as well if there is a duplicate name, otherwise they won't be easily distinguishable in the tree view
129+
showLocationInTreeItemDescription: groupBy === 'resourceType' && hasDuplicateSiteName,
130+
});
111131
} else if (siteModel) {
112-
return new ResolvedFunctionAppResource(subContext, undefined, siteModel);
132+
return new ResolvedFunctionAppResource(subContext, undefined, siteModel, {
133+
// Display the location as well if there is a duplicate name, otherwise they won't be easily distinguishable in the tree view
134+
showLocationInTreeItemDescription: groupBy === 'resourceType' && hasDuplicateSiteName,
135+
});
113136
}
114137

115138
return undefined;
@@ -121,4 +144,9 @@ export class FunctionAppResolver implements AppResourceResolver {
121144
&& !!resource.kind?.includes('functionapp')
122145
&& !resource.kind?.includes('workflowapp'); // exclude logic apps
123146
}
147+
148+
private countSiteName(siteName: string): void {
149+
const count: number = (this.siteNameCounter.get(siteName) ?? 0) + 1;
150+
this.siteNameCounter.set(siteName, count);
151+
}
124152
}

src/commands/createFunctionApp/FunctionAppCreateStep.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStepWithActivityOut
168168
name: extensionVersionKey,
169169
value: '~' + getMajorVersion(context.version)
170170
}],
171-
171+
172172
...Object.entries(stackSettings.appSettingsDictionary).map(([name, value]) => { return { name, value }; }));
173173
}
174174

@@ -238,8 +238,9 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStepWithActivityOut
238238
const site = context.newFlexSku ?
239239
await this.getNewFlexSite(context, context.newFlexSku) :
240240
await this.getNewSite(context, stack);
241-
const result = await client.webApps.beginCreateOrUpdateAndWait(rgName, siteName, site);
241+
site.autoGeneratedDomainNameLabelScope = context.newSiteDomainNameLabelScope;
242242

243+
const result = await client.webApps.beginCreateOrUpdateAndWait(rgName, siteName, site);
243244
if (context.newFlexSku) {
244245
if (context.storageAccount) {
245246
await tryCreateStorageContainer(context, result, context.storageAccount);

src/commands/createFunctionApp/createCreateFunctionAppComponents.ts

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

6-
import { AppInsightsCreateStep, AppInsightsListStep, AppKind, AppServicePlanCreateStep, AppServicePlanListStep, CustomLocationListStep, LogAnalyticsCreateStep, SiteNameStep, WebsiteOS, type IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice";
6+
import { AppInsightsCreateStep, AppInsightsListStep, AppKind, AppServicePlanCreateStep, AppServicePlanListStep, CustomLocationListStep, DomainNameLabelScope, LogAnalyticsCreateStep, SiteDomainNameLabelScopeStep, SiteNameStep, WebsiteOS, type IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice";
77
import { CommonRoleDefinitions, createRoleId, LocationListStep, ResourceGroupCreateStep, ResourceGroupListStep, RoleAssignmentExecuteStep, StorageAccountCreateStep, StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication, type INewStorageAccountDefaults, type Role } from "@microsoft/vscode-azext-azureutils";
88
import { type AzureWizardExecuteStep, type AzureWizardPromptStep, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
99
import { FuncVersion, latestGAVersion, tryParseFuncVersion } from "../../FuncVersion";
@@ -60,6 +60,21 @@ export async function createCreateFunctionAppComponents(context: ICreateFunction
6060
wizardContext.telemetry.properties.durableStorageType = wizardContext.durableStorageType;
6161
}
6262

63+
// #region SiteNameStep pre-requisites
64+
if (!context.advancedCreation) {
65+
LocationListStep.addStep(wizardContext, promptSteps);
66+
} else {
67+
CustomLocationListStep.addStep(wizardContext, promptSteps);
68+
promptSteps.push(new ResourceGroupListStep());
69+
}
70+
71+
promptSteps.push(new SiteDomainNameLabelScopeStep());
72+
if (!wizardContext.advancedCreation) {
73+
wizardContext.newSiteDomainNameLabelScope = DomainNameLabelScope.Tenant;
74+
wizardContext.telemetry.properties.siteDomainNameLabelScope = DomainNameLabelScope.Tenant;
75+
}
76+
// #endregion
77+
6378
promptSteps.push(new SiteNameStep(wizardContext.dockerfilePath ? "containerizedFunctionApp" : "functionApp"));
6479

6580
if (wizardContext.dockerfilePath) {
@@ -74,7 +89,6 @@ export async function createCreateFunctionAppComponents(context: ICreateFunction
7489
promptSteps.push(new AuthenticationPromptStep());
7590

7691
if (!wizardContext.advancedCreation) {
77-
LocationListStep.addStep(wizardContext, promptSteps);
7892
// if the user is deploying to a container app, do not use a flex consumption plan
7993
wizardContext.useFlexConsumptionPlan = !wizardContext.dockerfilePath;
8094
wizardContext.stackFilter = getRootFunctionsWorkerRuntime(wizardContext.language);
@@ -87,7 +101,6 @@ export async function createCreateFunctionAppComponents(context: ICreateFunction
87101
executeSteps.push(new LogAnalyticsCreateStep());
88102
}
89103
} else {
90-
promptSteps.push(new ResourceGroupListStep());
91104
promptSteps.push(new StorageAccountListStep(
92105
storageAccountCreateOptions,
93106
{
@@ -153,8 +166,6 @@ async function createFunctionAppWizard(wizardContext: IFunctionAppWizardContext)
153166
promptSteps.push(new FunctionAppHostingPlanStep(
154167
getAvailableFunctionAppHostingPlans(wizardContext) /** availablePlans */,
155168
));
156-
CustomLocationListStep.addStep(wizardContext, promptSteps);
157-
158169
promptSteps.push(new FunctionAppStackStep());
159170

160171
if (wizardContext.advancedCreation) {

src/commands/deploy/deploy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ async function deploy(actionContext: IActionContext, arg1: vscode.Uri | string |
124124
context.telemetry.properties.projectLanguage = language;
125125
context.telemetry.properties.projectRuntime = version;
126126
context.telemetry.properties.languageModel = String(languageModel);
127+
context.telemetry.properties.siteDomainNameLabelScope = site.rawSite.autoGeneratedDomainNameLabelScope ?? 'Legacy';
127128

128129
if (language === ProjectLanguage.Python && !site.isLinux) {
129130
context.errorHandling.suppressReportIssue = true;

src/tree/ResolvedFunctionAppResource.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@ export function isResolvedFunctionApp(ti: unknown): ti is ResolvedFunctionAppRes
2929
return (ti as unknown as ResolvedFunctionAppResource).instance === ResolvedFunctionAppResource.instance;
3030
}
3131

32+
type ResolvedFunctionAppResourceOptions = {
33+
showLocationInTreeItemDescription?: boolean;
34+
};
35+
3236
export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase implements ResolvedAppResourceBase {
3337
protected _site: ParsedSite | undefined = undefined;
3438
declare public data: Site;
3539
public dataModel: FunctionAppModel;
3640

3741
private _subscription: ISubscriptionContext;
42+
private _options: ResolvedFunctionAppResourceOptions;
3843
public logStreamPath: string = '';
3944
public appSettingsTreeItem: AppSettingsTreeItem;
4045
public deploymentsNode: DeploymentsTreeItem | undefined;
@@ -67,9 +72,10 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
6772
tooltip?: string | undefined;
6873
commandArgs?: unknown[] | undefined;
6974

70-
public constructor(subscription: ISubscriptionContext, site: Site | undefined, dataModel?: FunctionAppModel) {
75+
public constructor(subscription: ISubscriptionContext, site: Site | undefined, dataModel?: FunctionAppModel, options?: ResolvedFunctionAppResourceOptions) {
7176
super();
7277
this._subscription = subscription;
78+
this._options = options ?? {};
7379
this.contextValuesToAdd = [];
7480
if (dataModel) {
7581
this._isFlex = dataModel.isFlex;
@@ -148,11 +154,18 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
148154
}
149155

150156
public get description(): string | undefined {
151-
let state = this._state?.toLowerCase() !== 'running' ? this._state : undefined;
152-
if (this._isFlex && !state) {
153-
state = localize('flexFunctionAppState', 'Flex Consumption');
157+
let description = this._state?.toLowerCase() !== 'running' ? this._state : undefined;
158+
if (this._isFlex && !description) {
159+
description = localize('flexFunctionAppState', 'Flex Consumption');
160+
}
161+
if (this._options.showLocationInTreeItemDescription) {
162+
if (description) {
163+
description += ` (${this.dataModel.location})`;
164+
} else {
165+
description = this.dataModel.location;
166+
}
154167
}
155-
return state;
168+
return description;
156169
}
157170

158171
public get iconPath(): TreeItemIconPath {
@@ -212,7 +225,7 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
212225
let data: any;
213226
try {
214227
await this.initSite(context);
215-
228+
216229
data = JSON.parse((await getFile(context, this.site, 'site/wwwroot/host.json')).data);
217230
} catch {
218231
// ignore and use default
@@ -302,7 +315,7 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
302315
return children;
303316
}
304317

305-
318+
306319
public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): Promise<AzExtTreeItem | undefined> {
307320
return await callWithTelemetryAndErrorHandling('functionApp.pickTreeItem', async (context: IActionContext) => {
308321
await this.initSite(context);

0 commit comments

Comments
 (0)