Skip to content

Commit 18eaa57

Browse files
authored
Detect changes in environment variables and prompt if they conflict. Add new env setting. (#820)
1 parent ea2d2f0 commit 18eaa57

File tree

10 files changed

+222
-26
lines changed

10 files changed

+222
-26
lines changed

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/DeploymentConfigurationListStep.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ContainerAppVerifyStep } from "./azureResources/ContainerAppVerifyStep"
1313
import { ContainerRegistryVerifyStep } from "./azureResources/ContainerRegistryVerifyStep";
1414
import { ResourceGroupVerifyStep } from "./azureResources/ResourceGroupVerifyStep";
1515
import { DockerfileValidateStep } from "./filePaths/DockerfileValidateStep";
16+
import { EnvUseRemoteConfigurationPromptStep } from "./filePaths/EnvUseRemoteConfigurationPromptStep";
1617
import { EnvValidateStep } from "./filePaths/EnvValidateStep";
1718
import { SrcValidateStep } from "./filePaths/SrcValidateStep";
1819

@@ -52,6 +53,9 @@ export class DeploymentConfigurationListStep extends AzureWizardPromptStep<Works
5253
context.activityChildren ??= [];
5354

5455
return {
56+
promptSteps: [
57+
new EnvUseRemoteConfigurationPromptStep(),
58+
],
5559
executeSteps: [
5660
new DockerfileValidateStep(),
5761
new SrcValidateStep(),

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/azureResources/AzureResourceVerifyStepBase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export abstract class AzureResourceVerifyStepBase extends AzureWizardExecuteStep
3737
protected abstract verifyResource(context: WorkspaceDeploymentConfigurationContext): Promise<void>;
3838

3939
public shouldExecute(context: WorkspaceDeploymentConfigurationContext): boolean {
40-
return !!context.deploymentConfigurationSettings?.[this.deploymentSettingsKey] && !context?.[this.contextKey];
40+
return !!context.deploymentConfigurationSettings?.[this.deploymentSettingsKey];
4141
}
4242

4343
public createSuccessOutput(context: WorkspaceDeploymentConfigurationContext): ExecuteActivityOutput {

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/azureResources/ContainerAppVerifyStep.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export class ContainerAppVerifyStep extends AzureResourceVerifyStepBase {
1818
protected contextKey = 'containerApp' as const;
1919

2020
protected async verifyResource(context: WorkspaceDeploymentConfigurationContext): Promise<void> {
21+
await ContainerAppVerifyStep.verifyContainerApp(context);
22+
}
23+
24+
static async verifyContainerApp(context: WorkspaceDeploymentConfigurationContext): Promise<void> {
25+
if (context.containerApp) {
26+
return;
27+
}
28+
2129
const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context);
2230
const containerApp: ContainerApp = await client.containerApps.get(nonNullValueAndProp(context.resourceGroup, 'name'), nonNullValueAndProp(context.deploymentConfigurationSettings, 'containerApp'));
2331
context.containerApp = ContainerAppItem.CreateContainerAppModel(containerApp);

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/azureResources/ResourceGroupVerifyStep.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ export class ResourceGroupVerifyStep extends AzureResourceVerifyStepBase {
1616
protected contextKey = 'resourceGroup' as const;
1717

1818
protected async verifyResource(context: WorkspaceDeploymentConfigurationContext): Promise<void> {
19+
await ResourceGroupVerifyStep.verifyResourceGroup(context);
20+
}
21+
22+
static async verifyResourceGroup(context: WorkspaceDeploymentConfigurationContext): Promise<void> {
23+
if (context.resourceGroup) {
24+
return;
25+
}
26+
1927
const resourceGroups: ResourceGroup[] = await ResourceGroupListStep.getResourceGroups(context);
2028
context.resourceGroup = resourceGroups.find(rg => rg.name === context.deploymentConfigurationSettings?.resourceGroup);
2129
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 EnvironmentVar } from "@azure/arm-appcontainers";
7+
import { activitySuccessContext, activitySuccessIcon, AzureWizardPromptStep, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp, nonNullValueAndProp, type IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
8+
import * as deepEqual from "deep-eql";
9+
import * as path from "path";
10+
import { ext } from "../../../../../extensionVariables";
11+
import { localize } from "../../../../../utils/localize";
12+
import { EnvFileListStep } from "../../../../image/imageSource/EnvFileListStep";
13+
import { type WorkspaceDeploymentConfigurationContext } from "../WorkspaceDeploymentConfigurationContext";
14+
import { ContainerAppVerifyStep } from "../azureResources/ContainerAppVerifyStep";
15+
import { ResourceGroupVerifyStep } from "../azureResources/ResourceGroupVerifyStep";
16+
17+
export const useRemoteConfigurationKey: string = 'useRemoteConfiguration';
18+
export const useRemoteConfigurationLabel: string = localize('useRemoteConfiguration', 'Remote env configuration');
19+
export const useRemoteConfigurationOutputMessage: string = localize('usingRemoteConfiguration', 'Using the existing remote env configuration.');
20+
21+
export class EnvUseRemoteConfigurationPromptStep<T extends WorkspaceDeploymentConfigurationContext> extends AzureWizardPromptStep<T> {
22+
private shouldPromptEnvVars?: boolean;
23+
24+
public async configureBeforePrompt(context: T): Promise<void> {
25+
const envPath: string | undefined = context.deploymentConfigurationSettings?.envPath;
26+
if (!envPath || envPath === useRemoteConfigurationKey) {
27+
this.shouldPromptEnvVars = false;
28+
return;
29+
}
30+
31+
// Verify the resource group and container app ahead of time so we can inspect the current environment variables
32+
try {
33+
await ResourceGroupVerifyStep.verifyResourceGroup(context);
34+
await ContainerAppVerifyStep.verifyContainerApp(context);
35+
} catch {
36+
this.shouldPromptEnvVars = false;
37+
return;
38+
}
39+
40+
const rootPath: string = nonNullProp(context, 'rootFolder').uri.fsPath;
41+
const fullPath: string = path.join(rootPath, envPath);
42+
const configVars: EnvironmentVar[] = await EnvFileListStep.parseEnvironmentVariablesFromEnvPath(fullPath);
43+
const currentVars: EnvironmentVar[] = context.containerApp?.template?.containers?.[0]?.env ?? [];
44+
this.shouldPromptEnvVars = !deepEqual(configVars, currentVars);
45+
}
46+
47+
public async prompt(context: T): Promise<void> {
48+
const envPath: string = nonNullValueAndProp(context.deploymentConfigurationSettings, 'envPath');
49+
const useEnvFile: string = localize('useEnvFile', 'Local config');
50+
const useExistingConfig: string = localize('useExistingConfig', 'Remote config');
51+
52+
const picks: IAzureQuickPickItem<string>[] = [
53+
{ label: useEnvFile, data: useEnvFile, description: envPath },
54+
{ label: useExistingConfig, data: useExistingConfig, description: context.containerApp?.name },
55+
];
56+
57+
const result: string = (await context.ui.showQuickPick(picks, {
58+
placeHolder: localize('selectSourcePrompt', 'Detected conflicts between local and remote environment variables. Select source.'),
59+
suppressPersistence: true,
60+
})).data;
61+
62+
if (result === useEnvFile) {
63+
// Do nothing, later steps will verify the file path
64+
} else if (result === useExistingConfig) {
65+
context.envPath = '';
66+
context.activityChildren?.push(
67+
new GenericTreeItem(undefined, {
68+
contextValue: createUniversallyUniqueContextValue(['envUseExistingConfigurationPromptStepItem', activitySuccessContext]),
69+
label: useRemoteConfigurationLabel,
70+
iconPath: activitySuccessIcon,
71+
})
72+
);
73+
ext.outputChannel.appendLog(useRemoteConfigurationOutputMessage);
74+
}
75+
}
76+
77+
public shouldPrompt(): boolean {
78+
return !!this.shouldPromptEnvVars;
79+
}
80+
}

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/filePaths/EnvValidateStep.ts

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,82 @@
33
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, AzExtFsExtra, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp, nonNullValueAndProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
7+
import * as path from "path";
8+
import { type Progress } from "vscode";
9+
import { localize } from "../../../../../utils/localize";
610
import { type WorkspaceDeploymentConfigurationContext } from "../WorkspaceDeploymentConfigurationContext";
7-
import { FilePathsVerifyStep } from "./FilePathsVerifyStep";
11+
import { useRemoteConfigurationKey, useRemoteConfigurationLabel, useRemoteConfigurationOutputMessage } from "./EnvUseRemoteConfigurationPromptStep";
12+
import { verifyingFilePaths } from "./FilePathsVerifyStep";
813

9-
export class EnvValidateStep extends FilePathsVerifyStep {
10-
priority: number = 120;
14+
export class EnvValidateStep<T extends WorkspaceDeploymentConfigurationContext> extends AzureWizardExecuteStep<T> {
15+
public priority: number = 120;
16+
private configEnvPath: string;
1117

12-
deploymentSettingskey = 'envPath' as const;
13-
contextKey = 'envPath' as const;
14-
fileType = 'environment variables';
18+
public async execute(context: T, progress: Progress<{ message?: string; increment?: number; }>): Promise<void> {
19+
this.options.continueOnFail = true;
20+
progress.report({ message: verifyingFilePaths });
1521

16-
public shouldExecute(context: WorkspaceDeploymentConfigurationContext): boolean {
17-
return !context.envPath;
22+
this.configEnvPath = nonNullValueAndProp(context.deploymentConfigurationSettings, 'envPath');
23+
if (this.configEnvPath === useRemoteConfigurationKey) {
24+
context.envPath = '';
25+
return;
26+
}
27+
28+
const rootPath: string = nonNullProp(context, 'rootFolder').uri.fsPath;
29+
if (!context.envPath && this.configEnvPath) {
30+
const fullPath: string = path.join(rootPath, this.configEnvPath);
31+
if (await this.verifyFilePath(fullPath)) {
32+
context.envPath = fullPath;
33+
}
34+
}
35+
}
36+
37+
public shouldExecute(context: T): boolean {
38+
return context.envPath === undefined;
39+
}
40+
41+
public async verifyFilePath(path: string): Promise<boolean> {
42+
if (await AzExtFsExtra.pathExists(path)) {
43+
return true;
44+
} else {
45+
throw new Error(localize('fileNotFound', 'File not found: {0}', path));
46+
}
47+
}
48+
49+
public createSuccessOutput(context: T): ExecuteActivityOutput {
50+
if (context.envPath === undefined) {
51+
return {};
52+
}
53+
54+
let label: string;
55+
let message: string;
56+
if (context.envPath === '') {
57+
label = useRemoteConfigurationLabel;
58+
message = useRemoteConfigurationOutputMessage;
59+
} else {
60+
label = localize('envPathLabel', 'Env path');
61+
message = localize('envPathSuccessMessage', 'Successfully verified {0} path "{1}".', '.env', context.envPath);
62+
}
63+
64+
return {
65+
item: new GenericTreeItem(undefined, {
66+
contextValue: createUniversallyUniqueContextValue(['envValidateStepSuccessItem', activitySuccessContext]),
67+
label,
68+
iconPath: activitySuccessIcon
69+
}),
70+
message,
71+
};
72+
}
73+
74+
public createFailOutput(): ExecuteActivityOutput {
75+
return {
76+
item: new GenericTreeItem(undefined, {
77+
contextValue: createUniversallyUniqueContextValue(['envValidateStepFailItem', activityFailContext]),
78+
label: localize('envPathLabel', 'Env path'),
79+
iconPath: activityFailIcon
80+
}),
81+
message: localize('envPathFailMessage', 'Failed to verify {0} path "{1}".', '.env', this.configEnvPath),
82+
};
1883
}
1984
}

src/commands/deployWorkspaceProject/deploymentConfiguration/workspace/filePaths/FilePathsVerifyStep.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { localize } from "../../../../../utils/localize";
1010
import { type DeploymentConfigurationSettings } from "../../../settings/DeployWorkspaceProjectSettingsV2";
1111
import { type WorkspaceDeploymentConfigurationContext } from "../WorkspaceDeploymentConfigurationContext";
1212

13+
export const verifyingFilePaths: string = localize('verifyingFilePaths', `Verifying file paths...`);
14+
1315
export abstract class FilePathsVerifyStep extends AzureWizardExecuteStep<WorkspaceDeploymentConfigurationContext> {
1416
abstract deploymentSettingskey: keyof DeploymentConfigurationSettings;
1517
abstract contextKey: keyof Pick<WorkspaceDeploymentConfigurationContext, 'srcPath' | 'envPath' | 'dockerfilePath'>;
@@ -23,7 +25,7 @@ export abstract class FilePathsVerifyStep extends AzureWizardExecuteStep<Workspa
2325

2426
public async execute(context: WorkspaceDeploymentConfigurationContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
2527
this.options.continueOnFail = true;
26-
progress.report({ message: localize('verifyingFilePaths', `Verifying file paths...`) });
28+
progress.report({ message: verifyingFilePaths });
2729

2830
const rootPath: string = nonNullProp(context, 'rootFolder').uri.fsPath;
2931

src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectSaveSettingsStep.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as path from "path";
88
import { type Progress, type WorkspaceFolder } from "vscode";
99
import { relativeSettingsFilePath } from "../../../constants";
1010
import { localize } from "../../../utils/localize";
11+
import { useRemoteConfigurationKey } from "../deploymentConfiguration/workspace/filePaths/EnvUseRemoteConfigurationPromptStep";
1112
import { type DeploymentConfigurationSettings } from "../settings/DeployWorkspaceProjectSettingsV2";
1213
import { dwpSettingUtilsV2 } from "../settings/dwpSettingUtilsV2";
1314
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
@@ -30,7 +31,7 @@ export class DeployWorkspaceProjectSaveSettingsStep extends AzureWizardExecuteSt
3031
type: 'AcrDockerBuildRequest',
3132
dockerfilePath: path.relative(rootFolder.uri.fsPath, nonNullProp(context, 'dockerfilePath')),
3233
srcPath: path.relative(rootFolder.uri.fsPath, context.srcPath || rootFolder.uri.fsPath) || ".",
33-
envPath: context.envPath ? path.relative(rootFolder.uri.fsPath, context.envPath) : "",
34+
envPath: this.getEnvPath(rootFolder, context.envPath),
3435
resourceGroup: context.resourceGroup?.name,
3536
containerApp: context.containerApp?.name,
3637
containerRegistry: context.registry?.name,
@@ -49,6 +50,16 @@ export class DeployWorkspaceProjectSaveSettingsStep extends AzureWizardExecuteSt
4950
return !!context.shouldSaveDeploySettings;
5051
}
5152

53+
private getEnvPath(rootFolder: WorkspaceFolder, envPath: string | undefined): string {
54+
if (envPath === undefined) {
55+
return '';
56+
} else if (envPath === '') {
57+
return useRemoteConfigurationKey;
58+
} else {
59+
return path.relative(rootFolder.uri.fsPath, envPath);
60+
}
61+
}
62+
5263
public createSuccessOutput(context: DeployWorkspaceProjectInternalContext): ExecuteActivityOutput {
5364
context.telemetry.properties.didSaveSettings = 'true';
5465

src/commands/deployWorkspaceProject/internal/ShouldSaveDeploySettingsPromptStep.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AzureWizardPromptStep, nonNullProp } from "@microsoft/vscode-azext-util
77
import * as path from "path";
88
import { type WorkspaceFolder } from "vscode";
99
import { localize } from "../../../utils/localize";
10+
import { useRemoteConfigurationKey } from "../deploymentConfiguration/workspace/filePaths/EnvUseRemoteConfigurationPromptStep";
1011
import { type DeploymentConfigurationSettings } from "../settings/DeployWorkspaceProjectSettingsV2";
1112
import { dwpSettingUtilsV2 } from "../settings/dwpSettingUtilsV2";
1213
import { type DeployWorkspaceProjectInternalContext } from "./DeployWorkspaceProjectInternalContext";
@@ -20,6 +21,15 @@ export class ShouldSaveDeploySettingsPromptStep extends AzureWizardPromptStep<De
2021
const settings: DeploymentConfigurationSettings[] | undefined = await dwpSettingUtilsV2.getWorkspaceDeploymentConfigurations(rootFolder);
2122
const setting: DeploymentConfigurationSettings | undefined = settings?.[context.configurationIdx];
2223

24+
let hasNewEnvPath: boolean;
25+
if (context.envPath) {
26+
hasNewEnvPath = convertRelativeToAbsolutePath(rootPath, setting?.envPath) !== context.envPath;
27+
} else if (context.envPath === '') {
28+
hasNewEnvPath = setting?.envPath !== useRemoteConfigurationKey;
29+
} else {
30+
hasNewEnvPath = context.envPath !== setting?.envPath;
31+
}
32+
2333
const hasNewResourceGroupSetting: boolean = (!!context.newResourceGroupName && setting?.resourceGroup !== context.newResourceGroupName) ||
2434
(!!context.resourceGroup && setting?.resourceGroup !== context.resourceGroup.name);
2535
const hasNewContainerAppSetting: boolean = (!!context.newContainerAppName && setting?.containerApp !== context.newContainerAppName) ||
@@ -31,7 +41,7 @@ export class ShouldSaveDeploySettingsPromptStep extends AzureWizardPromptStep<De
3141
!setting?.label ||
3242
setting?.type !== 'AcrDockerBuildRequest' ||
3343
(context.dockerfilePath && convertRelativeToAbsolutePath(rootPath, setting?.dockerfilePath) !== context.dockerfilePath) ||
34-
(context.envPath && convertRelativeToAbsolutePath(rootPath, setting?.envPath) !== context.envPath) ||
44+
hasNewEnvPath ||
3545
(context.srcPath && convertRelativeToAbsolutePath(rootPath, setting?.srcPath) !== context.srcPath) ||
3646
hasNewResourceGroupSetting ||
3747
hasNewContainerAppSetting ||

0 commit comments

Comments
 (0)