Skip to content

Commit cd1e562

Browse files
authored
Add addEnvironmentVariable command (#781)
1 parent d8a7196 commit cd1e562

16 files changed

+340
-9
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@
209209
"shortTitle": "%containerApps.editContainerImage.shortTitle%",
210210
"category": "Azure Container Apps"
211211
},
212+
{
213+
"command": "containerApps.addEnvironmentVariable",
214+
"title": "%containerApps.addEnvironmentVariable%",
215+
"category": "Azure Container Apps"
216+
},
212217
{
213218
"command": "containerApps.editScaleRange",
214219
"title": "%containerApps.editScaleRange%",
@@ -474,6 +479,11 @@
474479
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /imageItem/i",
475480
"group": "1@1"
476481
},
482+
{
483+
"command": "containerApps.addEnvironmentVariable",
484+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /environmentVariablesItem/i",
485+
"group": "1@1"
486+
},
477487
{
478488
"command": "containerApps.editScaleRange",
479489
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /scaleItem/i",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"containerApps.editContainer": "Edit Container...",
1313
"containerApps.editContainerImage.title": "Edit Container Image...",
1414
"containerApps.editContainerImage.shortTitle": "Edit Image...",
15+
"containerApps.addEnvironmentVariable": "Add Environment Variable...",
1516
"containerApps.deployImageApi": "Deploy Image to Container App (API)...",
1617
"containerApps.deployWorkspaceProject": "Deploy Project from Workspace...",
1718
"containerApps.deployWorkspaceProjectApi": "Deploy Project from Workspace (API)...",

src/commands/editContainer/editContainer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export async function editContainer(context: IActionContext, node?: ContainersIt
5252
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;
5353

5454
const wizard: AzureWizard<ContainerEditContext> = new AzureWizard(wizardContext, {
55-
title: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
55+
title: localize('editContainer', 'Edit container profile for "{0}" (draft)', parentResource.name),
5656
promptSteps: [
5757
new ImageSourceListStep(),
5858
new RevisionDraftDeployPromptStep(),

src/commands/editContainer/editContainerImage/editContainerImage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export async function editContainerImage(context: IActionContext, node?: ImageIt
4545
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;
4646

4747
const wizard: AzureWizard<ContainerEditUpdateContext> = new AzureWizard(wizardContext, {
48-
title: localize('editContainerImage', 'Edit container image for app "{0}" (draft)', parentResource.name),
48+
title: localize('editContainerImage', 'Edit container image for "{0}" (draft)', parentResource.name),
4949
promptSteps: [
5050
new ImageSourceListStep({ suppressEnvPrompt: true }),
5151
new RevisionDraftDeployPromptStep(),
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps";
7+
import { type SetTelemetryProps } from "../../telemetry/SetTelemetryProps";
8+
import { type ContainerEditBaseContext } from "../editContainer/ContainerEditContext";
9+
10+
export type EnvironmentVariablesBaseContext = ContainerEditBaseContext;
11+
export type EnvironmentVariablesContext = EnvironmentVariablesBaseContext & SetTelemetryProps<TelemetryProps>;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps";
7+
import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../../telemetry/commandTelemetryProps";
8+
import { type ISecretContext } from "../../secret/ISecretContext";
9+
import { type EnvironmentVariablesBaseContext } from "../EnvironmentVariablesContext";
10+
import { type EnvironmentVariableType } from "./EnvironmentVariableTypeListStep";
11+
12+
export interface EnvironmentVariableAddBaseContext extends EnvironmentVariablesBaseContext, Pick<ISecretContext, 'secretName'> {
13+
newEnvironmentVariableType?: EnvironmentVariableType;
14+
newEnvironmentVariableName?: string;
15+
newEnvironmentVariableManualInput?: string;
16+
// secretName
17+
}
18+
19+
export type EnvironmentVariableAddContext = EnvironmentVariableAddBaseContext & SetTelemetryProps<TelemetryProps>;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 Container } from "@azure/arm-appcontainers";
7+
import { type Progress } from "vscode";
8+
import { type ContainerAppItem } from "../../../tree/ContainerAppItem";
9+
import { type RevisionsItemModel } from "../../../tree/revisionManagement/RevisionItem";
10+
import { localize } from "../../../utils/localize";
11+
import { RevisionDraftUpdateBaseStep } from "../../revisionDraft/RevisionDraftUpdateBaseStep";
12+
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
13+
14+
export class EnvironmentVariableAddDraftStep<T extends EnvironmentVariableAddContext> extends RevisionDraftUpdateBaseStep<T> {
15+
public priority: number = 590;
16+
17+
constructor(baseItem: ContainerAppItem | RevisionsItemModel) {
18+
super(baseItem);
19+
}
20+
21+
public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
22+
progress.report({ message: localize('addingEnv', 'Adding environment variable (draft)...') });
23+
this.revisionDraftTemplate.containers ??= [];
24+
25+
const container: Container = this.revisionDraftTemplate.containers[context.containersIdx] ?? {};
26+
container.env ??= [];
27+
container.env.push({
28+
name: context.newEnvironmentVariableName,
29+
value: context.newEnvironmentVariableManualInput ?? '', // The server doesn't allow this value to be undefined
30+
secretRef: context.secretName,
31+
});
32+
33+
await this.updateRevisionDraftWithTemplate(context);
34+
}
35+
36+
public shouldExecute(context: T): boolean {
37+
return context.containersIdx !== undefined && !!context.newEnvironmentVariableName;
38+
}
39+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7+
import { localize } from "../../../utils/localize";
8+
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
9+
10+
export class EnvironmentVariableManualInputStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
11+
public async prompt(context: T): Promise<void> {
12+
context.newEnvironmentVariableManualInput = (await context.ui.showInputBox({
13+
prompt: localize('envManualPrompt', 'Enter a value for the environment variable'),
14+
})).trim();
15+
context.valuesToMask.push(context.newEnvironmentVariableManualInput);
16+
}
17+
18+
public shouldPrompt(context: T): boolean {
19+
return !context.newEnvironmentVariableManualInput;
20+
}
21+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 Container, type EnvironmentVar } from "@azure/arm-appcontainers";
7+
import { AzureWizardPromptStep, validationUtils } from "@microsoft/vscode-azext-utils";
8+
import { ext } from "../../../extensionVariables";
9+
import { type EnvironmentVariableItem } from "../../../tree/containers/EnvironmentVariableItem";
10+
import { type EnvironmentVariablesItem } from "../../../tree/containers/EnvironmentVariablesItem";
11+
import { localize } from "../../../utils/localize";
12+
import { getParentResourceFromItem } from "../../../utils/revisionDraftUtils";
13+
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
14+
15+
export class EnvironmentVariableNameStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
16+
constructor(readonly baseItem: EnvironmentVariableItem | EnvironmentVariablesItem) {
17+
super();
18+
}
19+
20+
public async prompt(context: T): Promise<void> {
21+
context.newEnvironmentVariableName = (await context.ui.showInputBox({
22+
prompt: localize('envNamePrompt', 'Enter a name for the environment variable'),
23+
validateInput: (value: string) => this.validateInput(context, value),
24+
})).trim();
25+
context.valuesToMask.push(context.newEnvironmentVariableName);
26+
}
27+
28+
public shouldPrompt(context: T): boolean {
29+
return !context.newEnvironmentVariableName;
30+
}
31+
32+
private validateInput(context: T, value: string): string | undefined {
33+
if (!validationUtils.hasValidCharLength(value)) {
34+
return validationUtils.getInvalidCharLengthMessage();
35+
}
36+
37+
// This is the same regex used by the portal with similar warning verbiage
38+
const rule = /^[-._a-zA-z][-._a-zA-Z0-9]*$/;
39+
if (!rule.test(value)) {
40+
return localize('invalidEnvName', 'Name contains invalid character. Regex used for validation is "{0}".', String(rule));
41+
}
42+
43+
// Check for duplicates
44+
let container: Container | undefined;
45+
if (ext.revisionDraftFileSystem.doesContainerAppsItemHaveRevisionDraft(this.baseItem)) {
46+
container = ext.revisionDraftFileSystem.parseRevisionDraft(this.baseItem)?.containers?.[context.containersIdx];
47+
} else {
48+
container = getParentResourceFromItem(this.baseItem).template?.containers?.[context.containersIdx];
49+
}
50+
51+
const envs: EnvironmentVar[] = container?.env ?? [];
52+
if (envs.some(env => env.name === value)) {
53+
return localize('duplicateEnv', 'Environment variable with name "{0}" already exists for this container.', value);
54+
}
55+
56+
return undefined;
57+
}
58+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 { AzureWizardPromptStep, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
7+
import { localize } from "../../../utils/localize";
8+
import { SecretListStep } from "../../secret/SecretListStep";
9+
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
10+
import { EnvironmentVariableManualInputStep } from "./EnvironmentVariableManualInputStep";
11+
12+
export enum EnvironmentVariableType {
13+
ManualInput = 'manual',
14+
SecretRef = 'secretRef',
15+
}
16+
17+
export class EnvironmentVariableTypeListStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
18+
public async prompt(context: T): Promise<void> {
19+
const placeHolder: string = localize('environmentVariableTypePrompt', 'Select an environment variable type');
20+
const picks: IAzureQuickPickItem<EnvironmentVariableType>[] = [
21+
{
22+
label: localize('manualLabel', 'Manual entry'),
23+
data: EnvironmentVariableType.ManualInput,
24+
},
25+
{
26+
label: localize('secretRefLabel', 'Reference a secret'),
27+
data: EnvironmentVariableType.SecretRef,
28+
},
29+
];
30+
context.newEnvironmentVariableType = (await context.ui.showQuickPick(picks, {
31+
placeHolder,
32+
suppressPersistence: true,
33+
})).data;
34+
}
35+
36+
public shouldPrompt(context: T): boolean {
37+
return !context.newEnvironmentVariableType;
38+
}
39+
40+
public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
41+
const promptSteps: AzureWizardPromptStep<T>[] = [];
42+
43+
switch (context.newEnvironmentVariableType) {
44+
case EnvironmentVariableType.ManualInput:
45+
promptSteps.push(new EnvironmentVariableManualInputStep());
46+
break;
47+
case EnvironmentVariableType.SecretRef:
48+
promptSteps.push(new SecretListStep({ suppressCreatePick: true }));
49+
break;
50+
default:
51+
}
52+
53+
return { promptSteps };
54+
}
55+
}

0 commit comments

Comments
 (0)