Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b8f19e5
initial commit
motm32 Jan 29, 2025
6979c6e
Add changes
motm32 Feb 24, 2025
f8f10e3
merge main
motm32 Feb 24, 2025
562fad5
add ability to right click from nodes
motm32 Feb 26, 2025
b75809a
more changes
motm32 Feb 28, 2025
5e423da
small changes
motm32 Feb 28, 2025
df7712c
small changes
motm32 Mar 5, 2025
453f0af
add in clientId and credential
motm32 Mar 7, 2025
4387565
don't delete settings
motm32 Mar 10, 2025
2d13d56
requested changes
motm32 Mar 12, 2025
4373847
update appsettings and small changes
motm32 Mar 12, 2025
64cee3d
change warning message
motm32 Mar 13, 2025
3c126a7
remove managed identity step from local
motm32 Mar 13, 2025
2e432f7
fix
motm32 Mar 14, 2025
1d9d860
changed to appSettings package
motm32 Mar 17, 2025
7164e58
add app setting changes
motm32 Mar 18, 2025
af3704b
fix right click on node
motm32 Mar 18, 2025
842b462
requested changes
motm32 Mar 20, 2025
099a0f4
merge main
motm32 Mar 20, 2025
b5250a9
more changes
motm32 Mar 20, 2025
a7520fd
small changes
motm32 Mar 24, 2025
098b932
merge main
motm32 Mar 24, 2025
b6433f8
update
motm32 Mar 24, 2025
28e99cf
add changes
motm32 Mar 25, 2025
87ed16d
change
motm32 Mar 25, 2025
cdfc374
more changes
motm32 Mar 26, 2025
e82e6f6
more changes
motm32 Mar 27, 2025
5a969bc
merge main
motm32 Mar 27, 2025
87d2988
add changed for regex
motm32 Mar 27, 2025
8c63d4f
small changes so nathan hopefully doesn't yell at me
motm32 Mar 28, 2025
b70e19f
merge main
motm32 Mar 28, 2025
f43f490
small change
motm32 Mar 28, 2025
0dd20d5
change package lock
motm32 Mar 28, 2025
e1dafec
merge main
motm32 Mar 28, 2025
e4819a6
Minor fixes
nturinski Mar 28, 2025
4e63165
Merge branch 'main' into meganmott/convertSettings
nturinski Mar 28, 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
12,192 changes: 11,267 additions & 925 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 23 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@
"title": "%azureFunctions.connectToGitHub%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.addLocalMIConnections",
"title": "%azureFunctions.addLocalMIConnections%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.addRemoteMIConnections",
"title": "%azureFunctions.addRemoteMIConnections%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.copyFunctionUrl",
"title": "%azureFunctions.copyFunctionUrl%",
Expand Down Expand Up @@ -484,6 +494,16 @@
"when": "view == azureResourceGroups && viewItem =~ /functionapp/i && viewItem =~ /azureResourceTypeGroup/i",
"group": "1@2"
},
{
"command": "azureFunctions.addLocalMIConnections",
"when": "view == azureWorkspace && viewItem =~ /(azFuncLocalProject.*convert|applicationSettingItem.*localSettings)/i",
"group": "1@3"
},
{
"command": "azureFunctions.addRemoteMIConnections",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /(applicationSettings.*azFunc.*convert)|(applicationSettingItem.*azFunc.*convertSetting)/i",
"group": "1@4"
},
{
"command": "azureFunctions.browseWebsite",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc(Production|)(Slot|Flex)(?!s)/",
Expand Down Expand Up @@ -661,7 +681,7 @@
},
{
"command": "azureFunctions.toggleAppSettingVisibility",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /applicationSettingItem.*azFunc/",
"when": "view =~ /(azureResourceGroups|azureFocusView|azureWorkspace)/ && viewItem =~ /applicationSettingItem.*(azFunc|localSettings)/",
"group": "inline"
},
{
Expand Down Expand Up @@ -1345,8 +1365,8 @@
"@azure/core-rest-pipeline": "^1.11.0",
"@azure/storage-blob": "^12.5.0",
"@microsoft/vscode-azext-azureappservice": "^3.3.1",
"@microsoft/vscode-azext-azureappsettings": "^0.2.2",
"@microsoft/vscode-azext-azureutils": "^3.1.3",
"@microsoft/vscode-azext-azureappsettings": "^0.2.4",
"@microsoft/vscode-azext-azureutils": "^3.1.5",
"@microsoft/vscode-azext-serviceconnector": "^0.1.3",
"@microsoft/vscode-azext-utils": "^2.5.7",
"@microsoft/vscode-azureresources-api": "^2.0.4",
Expand Down
4 changes: 2 additions & 2 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"azureFunctions.createNewProjectWithDockerfile": "Create New Containerized Project...",
"azureFunctions.createPythonVenv": "Create a virtual environment when creating a new Python project.",
"azureFunctions.createSlot": "Create Slot...",
"azureFunctions.addLocalMIConnections": "Add Local Project Identity Connections...",
"azureFunctions.addRemoteMIConnections": "Add Function App Identity Connections...",
"azureFunctions.deleteFunction": "Delete Function...",
"azureFunctions.deleteFunctionApp": "Delete Function App...",
"azureFunctions.deleteSlot": "Delete Slot...",
Expand Down Expand Up @@ -119,7 +121,6 @@
"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).",
"azureFunctions.walkthrough.functionsStart.scenarios.title": "Explore common scenarios",
"azureFunctions.walkthrough.functionsStart.title": "Get Started with Azure Functions",

"azureFunctions.durableTaskScheduler.copySchedulerConnectionString": "Copy Connection String",
"azureFunctions.durableTaskScheduler.copySchedulerEndpoint": "Copy Endpoint",
"azureFunctions.durableTaskScheduler.createScheduler": "Create Durable Task Scheduler...",
Expand All @@ -128,6 +129,5 @@
"azureFunctions.durableTaskScheduler.deleteScheduler": "Delete Scheduler...",
"azureFunctions.durableTaskScheduler.deleteTaskHub": "Delete Task Hub...",
"azureFunctions.durableTaskScheduler.openTaskHubDashboard": "Open in Dashboard",

"azureFunctions.durableTaskScheduler.enablePreviewFeatures": "Enable Durable Task Scheduler preview features"
}
3 changes: 1 addition & 2 deletions src/LocalResourceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ export class FunctionsLocalResourceProvider implements WorkspaceResourceProvider

public async provideResources(parent: AzExtParentTreeItem): Promise<AzExtTreeItem[] | null | undefined> {
const children: AzExtTreeItem[] = [];

Disposable.from(...this._projectDisposables).dispose();
this._projectDisposables = [];

const localProjects = await listLocalProjects();
let hasLocalProject = false;

for (const project of localProjects.initializedProjects) {
const treeItem: LocalProjectTreeItem = new LocalProjectTreeItem(parent, project as LocalProjectInternal);
const treeItem: LocalProjectTreeItem = await LocalProjectTreeItem.createLocalProjectTreeItem(parent, project as LocalProjectInternal);
this._projectDisposables.push(treeItem);
children.push(treeItem);
}
Expand Down
115 changes: 115 additions & 0 deletions src/commands/addMIConnections/ConnectionsListStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type StringDictionary } from "@azure/arm-appservice";
import { isSettingConvertible } from "@microsoft/vscode-azext-azureappsettings";
import { AzExtFsExtra, AzureWizardPromptStep, nonNullValue, type IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
import * as vscode from 'vscode';
import { type ILocalSettingsJson } from "../../funcConfig/local.settings";
import { localize } from "../../localize";
import { decryptLocalSettings } from "../appSettings/localSettings/decryptLocalSettings";
import { encryptLocalSettings } from "../appSettings/localSettings/encryptLocalSettings";
import { getLocalSettingsFile } from "../appSettings/localSettings/getLocalSettingsFile";
import { type IAddMIConnectionsContext } from "./IAddMIConnectionsContext";

export interface Connection {
name: string;
value: string;
}

export class ConnectionsListStep extends AzureWizardPromptStep<IAddMIConnectionsContext> {
public async prompt(context: IAddMIConnectionsContext): Promise<void> {
const picks = await this.getPicks(context);

if (picks.length === 0) {
const noItemFoundMessage: string = localize('noConnectionsFound', 'No connections found in local settings');
(await context.ui.showQuickPick(picks, {
placeHolder: localize('selectConnections', 'Select the connections you want to add managed identity support for'),
suppressPersistence: true,
noPicksMessage: noItemFoundMessage
}));
} else {
context.connections = (await context.ui.showQuickPick(picks, {
Comment thread
nturinski marked this conversation as resolved.
placeHolder: localize('selectConnections', 'Select the connections you want to add managed identity support for'),
suppressPersistence: true,
canPickMany: true,
})).map(item => item.data);
}
}

public shouldPrompt(context: IAddMIConnectionsContext): boolean {
return !context.connections || context.connections.length === 0;
}

private async getPicks(context: IAddMIConnectionsContext): Promise<IAzureQuickPickItem<Connection>[]> {
if (context.functionapp) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: return context.functionapp ? this.getRemoteQuickPicks(context) : this.getLocalQuickPicks(context)

return this.getRemoteQuickPicks(context);
} else {
return this.getLocalQuickPicks(context);
}
}

private async getLocalQuickPicks(context: IAddMIConnectionsContext, workspaceFolder?: vscode.WorkspaceFolder): Promise<IAzureQuickPickItem<Connection>[]> {
const picks: IAzureQuickPickItem<Connection>[] = [];
const message: string = localize('selectLocalSettings', 'Select the local settings to add identity settings for.');
const localSettingsPath: string = await getLocalSettingsFile(context, message, workspaceFolder);
const localSettingsUri: vscode.Uri = vscode.Uri.file(localSettingsPath);
context.localSettingsPath = localSettingsPath;

if (await AzExtFsExtra.pathExists(localSettingsPath)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you're using this function, I think you can get rid of the if statement above.

let localSettings: ILocalSettingsJson = <ILocalSettingsJson>await AzExtFsExtra.readJSON(localSettingsPath);
if (localSettings.IsEncrypted) {
await decryptLocalSettings(context, localSettingsUri);
try {
localSettings = await AzExtFsExtra.readJSON<ILocalSettingsJson>(localSettingsPath);
} finally {
await encryptLocalSettings(context, localSettingsUri);
}
}

if (localSettings.Values) {
for (const [key, value] of Object.entries(localSettings.Values)) {
if (!isSettingConvertible(key, value)) {
continue;
}

picks.push({
label: key,
data: {
name: key,
value: value
}
});
}
}
}

return picks;
}

private async getRemoteQuickPicks(context: IAddMIConnectionsContext): Promise<IAzureQuickPickItem<Connection>[]> {
const picks: IAzureQuickPickItem<Connection>[] = [];

const client = await nonNullValue(context.functionapp?.site.createClient(context));
const appSettings: StringDictionary = await client.listApplicationSettings();
if (appSettings.properties) {
for (const [key, value] of Object.entries(appSettings.properties)) {
if (!isSettingConvertible(key, value)) {
continue;
}

picks.push({
label: key,
data: {
name: key,
value: value
}
});
}
}

return picks;
}
}
17 changes: 17 additions & 0 deletions src/commands/addMIConnections/IAddMIConnectionsContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type IResourceGroupWizardContext, type Role } from "@microsoft/vscode-azext-azureutils";
import { type ExecuteActivityContext, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils";
import { type SlotTreeItem } from "../../tree/SlotTreeItem";
import { type Connection } from "./ConnectionsListStep";

export interface IAddMIConnectionsContext extends ExecuteActivityContext, IResourceGroupWizardContext, ISubscriptionActionContext {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: No "👁️" needed here.................................

functionapp?: SlotTreeItem;
connections?: Connection[];
connectionsToAdd?: Connection[];
roles?: Role[];
localSettingsPath?: string;
}
42 changes: 42 additions & 0 deletions src/commands/addMIConnections/LocalSettingsAddStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { activitySuccessContext, activitySuccessIcon, AzExtFsExtra, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp } from "@microsoft/vscode-azext-utils";
import { ext } from "../../extensionVariables";
import { type ILocalSettingsJson } from "../../funcConfig/local.settings";
import { localize } from "../../localize";
import { getLocalSettingsFile } from "../appSettings/localSettings/getLocalSettingsFile";
import { type IAddMIConnectionsContext } from "./IAddMIConnectionsContext";

export class LocalSettingsAddStep extends AzureWizardExecuteStep<IAddMIConnectionsContext> {
public priority: number = 125;

public async execute(context: IAddMIConnectionsContext): Promise<void> {
// If right clicking on a connection we will have the connections to convert but not the local settings path
if (!context.localSettingsPath) {
const message: string = localize('selectLocalSettings', 'Select the local settings file to add connections to.');
context.localSettingsPath = await getLocalSettingsFile(context, message);
}

const localSettings = await AzExtFsExtra.readJSON<ILocalSettingsJson>(nonNullProp(context, 'localSettingsPath'));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should just make getting the settings from the local.settings.json a helper function.

if (localSettings.Values) {
for (const connection of nonNullProp(context, 'connectionsToAdd')) {
localSettings.Values[connection.name] = connection.value;
context.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['useExistingResourceGroupInfoItem', activitySuccessContext]),
label: localize('addedLocalSetting', 'Add Local setting "{0}"', connection.name),
iconPath: activitySuccessIcon
})
);
}
await AzExtFsExtra.writeJSON(nonNullProp(context, 'localSettingsPath'), localSettings);
await ext.rgApi.workspaceResourceTree.refresh(context);
}
}
public shouldExecute(context: IAddMIConnectionsContext): boolean {
return !context.functionapp && !!context.connections
}
}
43 changes: 43 additions & 0 deletions src/commands/addMIConnections/RemoteSettingsAddStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { activitySuccessContext, activitySuccessIcon, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp } from "@microsoft/vscode-azext-utils";
import { ext } from "../../extensionVariables";
import { localize } from "../../localize";
import { type IAddMIConnectionsContext } from "./IAddMIConnectionsContext";

export class RemoteSettingsAddStep extends AzureWizardExecuteStep<IAddMIConnectionsContext> {
public priority: number = 110;

public async execute(context: IAddMIConnectionsContext): Promise<void> {
if (!context.functionapp) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Why not just use nonNullProp?

throw new Error(localize('functionAppNotFound', 'Function app not found.'));
}

const client = await context.functionapp?.site.createClient(context);
const remoteSettings = await client.listApplicationSettings();
for (const connection of nonNullProp(context, 'connectionsToAdd')) {
Comment thread
nturinski marked this conversation as resolved.
if (remoteSettings.properties) {
remoteSettings.properties[connection.name] = connection.value;
} else {
await client.updateApplicationSettings({ properties: { [connection.name]: connection.value } });
}
context.activityChildren?.push(
new GenericTreeItem(undefined, {
Comment thread
nturinski marked this conversation as resolved.
contextValue: createUniversallyUniqueContextValue(['useExistingResourceGroupInfoItem', activitySuccessContext]),
label: localize('addedAppSetting', 'Add app setting "{0}"', connection.name),
iconPath: activitySuccessIcon
})
);
}

await client.updateApplicationSettings({ properties: remoteSettings.properties });
await ext.rgApi.tree.refresh(context);
}

public shouldExecute(context: IAddMIConnectionsContext): boolean {
return !!context.functionapp && !!context.connections;
}
}
Loading