Skip to content

Commit 952d07e

Browse files
authored
Add Identity node to Function Apps (#4382)
* Skip test to let artifacts publish * Fix stupid build issue * UI for managed identity * Add system identity enablement, still need to do managed wizard * WIP MI with azureutils package impl * Use shared package logic to create role definitions * Minor text edits * Remove some copy/pasted stuff * Remove setting * Remove unused string * Update to use real npm package * Feedback * PR feedback * Update azure utils package * Remove child under system assigned (disabled) node * PR feedback * Fix package-lock.json * Use prerelease version * cliFeed response * Stringify json * JSON parse bodyAsString * Remove serviceconnector * PR feedback
1 parent e8b4fdf commit 952d07e

16 files changed

+592
-20
lines changed

gulpfile.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ async function prepareForWebpack(): Promise<void> {
2828
let downloadLink;
2929
async function getFuncLink() {
3030
const client = new msRest.ServiceClient();
31-
const cliFeed = (await client.sendRequest({ method: 'GET', url: 'https://aka.ms/V00v5v' })).parsedBody;
32-
// const version = cliFeed.tags['v4-prerelease'].release;
33-
const version = '4.91.0';
31+
const cliFeed = JSON.parse((await client.sendRequest({ method: 'GET', url: 'https://aka.ms/V00v5v' })).bodyAsText as string);
32+
const version = cliFeed.tags['v4-prerelease'].release;
3433
console.log(`Func cli feed version: ${version}`);
3534
const cliRelease = cliFeed.releases[version].coreTools.find((rel) => {
3635
return rel.Architecture === 'x64' && (

package-lock.json

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

package.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,17 @@
381381
"category": "Azure Functions",
382382
"icon": "$(notebook-execute)"
383383
},
384+
{
385+
"command": "azureFunctions.assignManagedIdentity",
386+
"title": "%azureFunctions.assignManagedIdentity%",
387+
"category": "Azure Functions",
388+
"icon": "$(add)"
389+
},
390+
{
391+
"command": "azureFunctions.enableSystemIdentity",
392+
"title": "%azureFunctions.enableSystemIdentity%",
393+
"category": "Azure Functions"
394+
},
384395
{
385396
"command": "azureFunctions.durableTaskScheduler.copySchedulerConnectionString",
386397
"title": "%azureFunctions.durableTaskScheduler.copySchedulerConnectionString%",
@@ -709,6 +720,21 @@
709720
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.*folder/",
710721
"group": "1@1"
711722
},
723+
{
724+
"command": "azureFunctions.enableSystemIdentity",
725+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /systemIdentity.*disabled/i",
726+
"group": "1@1"
727+
},
728+
{
729+
"command": "azureFunctions.assignManagedIdentity",
730+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /userAssignedIdentities/",
731+
"group": "inline"
732+
},
733+
{
734+
"command": "azureFunctions.assignManagedIdentity",
735+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /userAssignedIdentities/",
736+
"group": "1@1"
737+
},
712738
{
713739
"command": "azureFunctions.durableTaskScheduler.copySchedulerConnectionString",
714740
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.scheduler/",
@@ -1295,6 +1321,7 @@
12951321
"all": "npm i && npm run lint && npm test"
12961322
},
12971323
"devDependencies": {
1324+
"@azure/arm-msi": "^2.1.0",
12981325
"@azure/arm-resources": "^5.2.0",
12991326
"@microsoft/eslint-config-azuretools": "^0.2.2",
13001327
"@microsoft/vscode-azext-dev": "^2.0.2",
@@ -1346,7 +1373,7 @@
13461373
"@azure/storage-blob": "^12.5.0",
13471374
"@microsoft/vscode-azext-azureappservice": "^3.3.1",
13481375
"@microsoft/vscode-azext-azureappsettings": "^0.2.2",
1349-
"@microsoft/vscode-azext-azureutils": "^3.1.5",
1376+
"@microsoft/vscode-azext-azureutils": "^3.1.6",
13501377
"@microsoft/vscode-azext-utils": "^2.6.3",
13511378
"@microsoft/vscode-azureresources-api": "^2.0.4",
13521379
"cross-fetch": "^4.0.0",

package.nls.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"azureFunctions.appSettings.rename": "Rename Setting...",
1010
"azureFunctions.appSettings.toggleSlotSetting": "Toggle as Slot Setting",
1111
"azureFunctions.appSettings.upload": "Upload Local Settings...",
12+
"azureFunctions.assignManagedIdentity": "Assign Managed Identity to Function App...",
1213
"azureFunctions.browseWebsite": "Browse Website",
1314
"azureFunctions.configureDeploymentSource": "Configure Deployment Source...",
1415
"azureFunctions.connectToGitHub": "Connect to GitHub Repository...",
@@ -36,6 +37,7 @@
3637
"azureFunctions.enableJavaRemoteDebugging": "Enable remote debugging for Java Functions Apps running on Windows. (experimental)",
3738
"azureFunctions.enableOutputTimestamps": "Prepends each line displayed in the output channel with a timestamp.",
3839
"azureFunctions.enableRemoteDebugging": "Enable remote debugging for Node.js Function Apps running on Linux App Service plans. Consumption plans are not supported. (experimental)",
40+
"azureFunctions.enableSystemIdentity": "Enable System Assigned Identity...",
3941
"azureFunctions.endOfLifeWarning": "Show a warning when creating Function Apps with stacks within 6 months of their end of life.",
4042
"azureFunctions.executeFunction": "Execute Function Now...",
4143
"azureFunctions.funcCliPath": "The path to the 'func' executable to use for debug and deploy tasks. For example, set it to 'node_modules/.bin/func' if using the func cli installed as a local npm package.",
@@ -119,7 +121,6 @@
119121
"azureFunctions.walkthrough.functionsStart.scenarios.description": "Learn how you can use Azure Functions to build event-driven systems.\n\nIf you're just getting started with Azure Functions, you can [learn about the anatomy of an Azure Functions application](https://aka.ms/functions-getstarted-devguide).",
120122
"azureFunctions.walkthrough.functionsStart.scenarios.title": "Explore common scenarios",
121123
"azureFunctions.walkthrough.functionsStart.title": "Get Started with Azure Functions",
122-
123124
"azureFunctions.durableTaskScheduler.copySchedulerConnectionString": "Copy Connection String",
124125
"azureFunctions.durableTaskScheduler.copySchedulerEndpoint": "Copy Endpoint",
125126
"azureFunctions.durableTaskScheduler.createScheduler": "Create Durable Task Scheduler...",
@@ -128,6 +129,5 @@
128129
"azureFunctions.durableTaskScheduler.deleteScheduler": "Delete Scheduler...",
129130
"azureFunctions.durableTaskScheduler.deleteTaskHub": "Delete Task Hub...",
130131
"azureFunctions.durableTaskScheduler.openTaskHubDashboard": "Open in Dashboard",
131-
132132
"azureFunctions.durableTaskScheduler.enablePreviewFeatures": "Enable Durable Task Scheduler preview features"
133133
}
Lines changed: 15 additions & 0 deletions
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type ManagedServiceIdentityType } from "@azure/arm-appservice";
7+
import { type Identity } from "@azure/arm-resources";
8+
import { createWebSiteClient, type ParsedSite } from "@microsoft/vscode-azext-azureappservice";
9+
import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
10+
import { type Progress } from "vscode";
11+
import { ext } from "../../extensionVariables";
12+
import { localize } from "../../localize";
13+
import { type ManagedIdentityAssignContext } from "./ManagedIdentityAssignContext";
14+
15+
export class EnableSystemIdentityAssignStep extends AzureWizardExecuteStep<ManagedIdentityAssignContext> {
16+
public priority: number;
17+
18+
public async execute(context: ManagedIdentityAssignContext, _progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise<void> {
19+
const site: ParsedSite = nonNullProp(context, 'site');
20+
const client = await createWebSiteClient([context, site.subscription]);
21+
22+
const identity = site.rawSite.identity || {};
23+
identity.type = addSystemAssignedType(identity);
24+
site.rawSite.identity = identity;
25+
26+
const enabling: string = localize('enabling', 'Enabling system assigned identity for "{0}"...', site.fullName);
27+
const enabled: string = localize('enabled', 'Enabled system assigned identity for "{0}"', site.fullName);
28+
ext.outputChannel.appendLog(enabling);
29+
30+
await client.webApps.update(site.resourceGroup, site.siteName, site.rawSite);
31+
ext.outputChannel.appendLog(enabled);
32+
}
33+
34+
public shouldExecute(_context: ManagedIdentityAssignContext): boolean {
35+
return true;
36+
}
37+
}
38+
39+
const addSystemAssignedType = (identity: Identity): ManagedServiceIdentityType => {
40+
if (!identity.type || identity.type === 'None') {
41+
return 'SystemAssigned';
42+
} else if (identity.type === ('UserAssigned')) {
43+
return 'SystemAssigned, UserAssigned';
44+
} else {
45+
return nonNullProp(identity, 'type');
46+
}
47+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type ParsedSite } from "@microsoft/vscode-azext-azureappservice";
7+
import { type IResourceGroupWizardContext } from "@microsoft/vscode-azext-azureutils";
8+
import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
9+
10+
export interface ManagedIdentityAssignContext extends IResourceGroupWizardContext, ExecuteActivityContext {
11+
site?: ParsedSite;
12+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type ManagedServiceIdentity, type ManagedServiceIdentityType } from "@azure/arm-appservice";
7+
import { createWebSiteClient, type ParsedSite } from "@microsoft/vscode-azext-azureappservice";
8+
import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
9+
import { type Progress } from "vscode";
10+
import { ext } from "../../extensionVariables";
11+
import { localize } from "../../localize";
12+
import { type ManagedIdentityAssignContext } from "./ManagedIdentityAssignContext";
13+
14+
export class ManagedIdentityAssignStep extends AzureWizardExecuteStep<ManagedIdentityAssignContext> {
15+
public priority: number = 500;
16+
17+
public async execute(context: ManagedIdentityAssignContext, _progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise<void> {
18+
const site: ParsedSite = nonNullProp(context, 'site');
19+
const managedIdentity = nonNullProp(context, 'managedIdentity');
20+
const id: string = nonNullProp(managedIdentity, 'id');
21+
const client = await createWebSiteClient([context, site.subscription]);
22+
23+
const existingIdentity = site.rawSite.identity || {};
24+
const updatedIdentity: ManagedServiceIdentity = {
25+
...existingIdentity,
26+
// type property is required and may not exist yet
27+
type: addUserAssignedType(existingIdentity.type),
28+
userAssignedIdentities: {
29+
...existingIdentity.userAssignedIdentities,
30+
[id]: {
31+
principalId: managedIdentity.principalId,
32+
clientId: managedIdentity.clientId,
33+
}
34+
}
35+
};
36+
37+
const newSite = site.rawSite;
38+
newSite.identity = updatedIdentity;
39+
const assigning: string = localize('assigning', 'Assigning user assigned identity "{1}" for "{0}"...', site.fullName, managedIdentity.name);
40+
const assigned: string = localize('assigned', 'Assigned user assigned identity "{1}" for "{0}".', site.fullName, managedIdentity.name);
41+
ext.outputChannel.appendLog(assigning);
42+
43+
await client.webApps.beginCreateOrUpdateAndWait(site.resourceGroup, site.siteName, newSite);
44+
ext.outputChannel.appendLog(assigned);
45+
}
46+
47+
public shouldExecute(_context: ManagedIdentityAssignContext): boolean {
48+
return true;
49+
}
50+
51+
}
52+
53+
const addUserAssignedType = (type: ManagedServiceIdentityType | undefined): ManagedServiceIdentityType => {
54+
if (type?.includes('UserAssigned')) {
55+
return type
56+
}
57+
if (type === 'SystemAssigned') {
58+
return 'SystemAssigned, UserAssigned';
59+
}
60+
return 'UserAssigned';
61+
};

0 commit comments

Comments
 (0)