diff --git a/package.json b/package.json index 9b88235b1..4dff80ac8 100644 --- a/package.json +++ b/package.json @@ -471,6 +471,21 @@ { "title": "Azure Container Apps", "properties": { + "containerApps.deployWorkspaceProject.containerRegistryName": { + "scope": "machine-overridable", + "type": "string", + "description": "%containerApps.deployWorkspaceProject.containerRegistryName%" + }, + "containerApps.deployWorkspaceProject.containerAppName": { + "scope": "machine-overridable", + "type": "string", + "description": "%containerApps.deployWorkspaceProject.containerAppName%" + }, + "containerApps.deployWorkspaceProject.containerAppResourceGroupName": { + "scope": "machine-overridable", + "type": "string", + "description": "%containerApps.deployWorkspaceProject.containerAppResourceGroupName%" + }, "containerApps.enableOutputTimestamps": { "type": "boolean", "description": "%containerApps.enableOutputTimestamps%", diff --git a/package.nls.json b/package.nls.json index 349d9639f..6df4a2738 100644 --- a/package.nls.json +++ b/package.nls.json @@ -42,5 +42,8 @@ "containerApps.openGitHubRepo": "Open Repo in GitHub", "containerApps.startStreamingLogs": "Start Streaming Logs...", "containerApps.stopStreamingLogs": "Stop Streaming Logs...", - "containerApps.createAcr": "Create Azure Container Registry..." + "containerApps.createAcr": "Create Azure Container Registry...", + "containerApps.deployWorkspaceProject.containerAppName": "When deploying from a local workspace project, the name of the target container app to deploy to.", + "containerApps.deployWorkspaceProject.containerAppResourceGroupName": "When deploying from a local workspace project, the name of the target container app's resource group.", + "containerApps.deployWorkspaceProject.containerRegistryName": "When deploying from a local workspace project, the name of the Azure Container Registry to use for storing and building images." } diff --git a/src/commands/deployImage/imageSource/containerRegistry/acr/AcrListStep.ts b/src/commands/deployImage/imageSource/containerRegistry/acr/AcrListStep.ts index a3b5faedc..5482cadb7 100644 --- a/src/commands/deployImage/imageSource/containerRegistry/acr/AcrListStep.ts +++ b/src/commands/deployImage/imageSource/containerRegistry/acr/AcrListStep.ts @@ -5,7 +5,7 @@ import type { ContainerRegistryManagementClient, Registry } from "@azure/arm-containerregistry"; import { uiUtils } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizardPromptStep, IAzureQuickPickItem, IWizardOptions, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { AzureWizardPromptStep, IAzureQuickPickItem, ISubscriptionActionContext, IWizardOptions, nonNullProp } from "@microsoft/vscode-azext-utils"; import { acrDomain, currentlyDeployed, quickStartImageName } from "../../../../../constants"; import { createContainerRegistryManagementClient } from "../../../../../utils/azureClients"; import { parseImageName } from "../../../../../utils/imageNameUtils"; @@ -33,8 +33,7 @@ export class AcrListStep extends AzureWizardPromptStep[]> { - const client: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); - const registries: Registry[] = await uiUtils.listAllIterator(client.registries.list()); + const registries: Registry[] = await AcrListStep.getRegistries(context); // Try to suggest a registry only when the user is deploying to a Container App let suggestedRegistry: string | undefined; @@ -63,5 +62,10 @@ export class AcrListStep extends AzureWizardPromptStep { + const client: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); + return await uiUtils.listAllIterator(client.registries.list()); + } } diff --git a/src/commands/deployWorkspaceProject/DeployWorkspaceProjectSettings.ts b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectSettings.ts new file mode 100644 index 000000000..4a6e78b64 --- /dev/null +++ b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectSettings.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { WorkspaceFolder } from "vscode"; +import { settingUtils } from "../../utils/settingUtils"; + +export interface DeployWorkspaceProjectSettings { + // Container app names are unique to a resource group + containerAppResourceGroupName?: string; + containerAppName?: string; + + containerRegistryName?: string; +} + +const deployWorkspaceProjectPrefix: string = 'deployWorkspaceProject'; + +export async function getDeployWorkspaceProjectSettings(rootFolder: WorkspaceFolder): Promise { + const settingsPath: string = settingUtils.getDefaultRootWorkspaceSettingsPath(rootFolder); + + const containerAppName: string | undefined = settingUtils.getWorkspaceSetting(`${deployWorkspaceProjectPrefix}.containerAppName`, settingsPath); + const containerAppResourceGroupName: string | undefined = settingUtils.getWorkspaceSetting(`${deployWorkspaceProjectPrefix}.containerAppResourceGroupName`, settingsPath); + const containerRegistryName: string | undefined = settingUtils.getWorkspaceSetting(`${deployWorkspaceProjectPrefix}.containerRegistryName`, settingsPath); + + return { + containerAppName, + containerAppResourceGroupName, + containerRegistryName + }; +} diff --git a/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultAcrResources.ts b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultAcrResources.ts new file mode 100644 index 000000000..9574e4104 --- /dev/null +++ b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultAcrResources.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import type { Registry } from "@azure/arm-containerregistry"; +import type { ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { ext } from "../../../extensionVariables"; +import { localize } from "../../../utils/localize"; +import { AcrListStep } from "../../deployImage/imageSource/containerRegistry/acr/AcrListStep"; +import { DeployWorkspaceProjectSettings } from "../DeployWorkspaceProjectSettings"; + +interface DefaultAcrResources { + registry?: Registry; + imageName?: string; +} + +export async function getDefaultAcrResources(context: ISubscriptionActionContext, settings: DeployWorkspaceProjectSettings | undefined): Promise { + const noMatchingResource = { registry: undefined, imageName: undefined }; + + if (!settings || !settings.containerRegistryName) { + return noMatchingResource; + } + + const registries: Registry[] = await AcrListStep.getRegistries(context); + const savedRegistry: Registry | undefined = registries.find(r => r.name === settings.containerRegistryName); + + if (savedRegistry) { + ext.outputChannel.appendLog(localize('foundResourceMatch', 'Used saved workspace settings and found an existing container registry.')); + return { + registry: savedRegistry, + imageName: `${settings.containerAppName || savedRegistry.name}:latest` + }; + } else { + ext.outputChannel.appendLog(localize('noResourceMatch', 'Used saved workspace settings to search for Azure Container Registry "{0}" but found no match.', settings.containerRegistryName)); + return noMatchingResource; + } +} diff --git a/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContainerAppsResources.ts b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContainerAppsResources.ts new file mode 100644 index 000000000..773c1a40d --- /dev/null +++ b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContainerAppsResources.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { ContainerApp, ContainerAppsAPIClient, ManagedEnvironment } from "@azure/arm-appcontainers"; +import type { ResourceGroup } from "@azure/arm-resources"; +import { ResourceGroupListStep, uiUtils } from "@microsoft/vscode-azext-azureutils"; +import type { ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { ext } from "../../../extensionVariables"; +import { ContainerAppItem, ContainerAppModel } from "../../../tree/ContainerAppItem"; +import { createContainerAppsAPIClient } from "../../../utils/azureClients"; +import { localize } from "../../../utils/localize"; +import { DeployWorkspaceProjectSettings } from "../DeployWorkspaceProjectSettings"; + +interface DefaultContainerAppsResources { + resourceGroup?: ResourceGroup; + managedEnvironment?: ManagedEnvironment; + containerApp?: ContainerAppModel; +} + +export async function getDefaultContainerAppsResources(context: ISubscriptionActionContext, settings: DeployWorkspaceProjectSettings | undefined): Promise { + const noMatchingResources = { + resourceGroup: undefined, + managedEnvironment: undefined, + containerApp: undefined + }; + + if (!settings || !settings.containerAppResourceGroupName || !settings.containerAppName) { + return noMatchingResources; + } + + const resourceGroupName: string = settings.containerAppResourceGroupName; + const containerAppName: string = settings.containerAppName; + + try { + const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context) + const containerApp: ContainerApp = await client.containerApps.get(resourceGroupName, containerAppName); + const containerAppModel: ContainerAppModel = ContainerAppItem.CreateContainerAppModel(containerApp); + + const managedEnvironments: ManagedEnvironment[] = await uiUtils.listAllIterator(client.managedEnvironments.listBySubscription()); + const managedEnvironment = managedEnvironments.find(env => env.id === containerAppModel.managedEnvironmentId); + + const resourceGroups: ResourceGroup[] = await ResourceGroupListStep.getResourceGroups(context); + const resourceGroup = resourceGroups.find(rg => rg.name === containerAppModel.resourceGroup); + + ext.outputChannel.appendLog(localize('foundResourceMatch', 'Used saved workspace settings and found existing container app resources.')); + + return { + resourceGroup, + managedEnvironment, + containerApp: containerAppModel + }; + } catch { + ext.outputChannel.appendLog(localize('noResourceMatch', 'Used saved workspace settings to search for container app "{0}" in resource group "{1}" but found no match.', settings.containerAppName, settings.containerAppResourceGroupName)); + return noMatchingResources; + } +} diff --git a/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContextValues.ts b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContextValues.ts index 5035a3ce4..3cbdedfb7 100644 --- a/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContextValues.ts +++ b/src/commands/deployWorkspaceProject/getDefaultValues/getDefaultContextValues.ts @@ -4,22 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import type { ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { relativeSettingsFilePath } from "../../../constants"; +import { ext } from "../../../extensionVariables"; +import { localize } from "../../../utils/localize"; import type { DeployWorkspaceProjectContext } from "../DeployWorkspaceProjectContext"; +import { DeployWorkspaceProjectSettings, getDeployWorkspaceProjectSettings } from "../DeployWorkspaceProjectSettings"; +import { getDefaultAcrResources } from "./getDefaultAcrResources"; +import { getDefaultContainerAppsResources } from "./getDefaultContainerAppsResources"; import { getWorkspaceProjectPaths } from "./getWorkspaceProjectPaths"; export async function getDefaultContextValues(context: ISubscriptionActionContext): Promise> { const { rootFolder, dockerfilePath } = await getWorkspaceProjectPaths(context); - // const settings: IDeployWorkspaceProjectSettings | undefined = await getDeployWorkspaceProjectSettings(rootFolder); - // if (!settings) { - // ext.outputChannel.appendLog(localize('noWorkspaceSettings', 'Scanned and found no matching resource settings at "{0}".', relativeSettingsFilePath)); - // } else if (!settings.containerAppResourceGroupName || !settings.containerAppName || !settings.containerRegistryName) { - // ext.outputChannel.appendLog(localize('resourceSettingsIncomplete', 'Scanned and found incomplete container app resource settings at "{0}".', relativeSettingsFilePath)); - // } + const settings: DeployWorkspaceProjectSettings = await getDeployWorkspaceProjectSettings(rootFolder); + if (!settings.containerAppName && !settings.containerAppResourceGroupName && !settings.containerRegistryName) { + ext.outputChannel.appendLog(localize('noWorkspaceSettings', 'Scanned and found no matching resource settings at "{0}".', relativeSettingsFilePath)); + } else if (!settings.containerAppResourceGroupName || !settings.containerAppName || !settings.containerRegistryName) { + ext.outputChannel.appendLog(localize('resourceSettingsIncomplete', 'Scanned and found incomplete container app resource settings at "{0}".', relativeSettingsFilePath)); + } return { - // ...await getDefaultContainerAppsResources(context, settings), - // ...await getDefaultAzureContainerRegistry(context, settings), + ...await getDefaultContainerAppsResources(context, settings), + ...await getDefaultAcrResources(context, settings), // newRegistrySku: KnownSkuName.Basic, dockerfilePath, // environmentVariables: await EnvironmentVariablesListStep.workspaceHasEnvFile() ? undefined : [], diff --git a/src/utils/settingUtils.ts b/src/utils/settingUtils.ts index 6783de431..c6abadf42 100644 --- a/src/utils/settingUtils.ts +++ b/src/utils/settingUtils.ts @@ -66,7 +66,7 @@ export namespace settingUtils { const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix, fsPath ? Uri.file(fsPath) : undefined); const configurationLevel: ConfigurationTarget | undefined = getLowestConfigurationLevel(projectConfiguration, key); - if (targetLimit && configurationLevel && (configurationLevel < targetLimit)) { + if (!configurationLevel || (configurationLevel && (configurationLevel < targetLimit))) { return undefined; }