Skip to content

Commit

Permalink
Add addEnvironmentVariable command (#781)
Browse files Browse the repository at this point in the history
  • Loading branch information
MicroFish91 authored Dec 17, 2024
1 parent d8a7196 commit cd1e562
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 9 deletions.
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@
"shortTitle": "%containerApps.editContainerImage.shortTitle%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.addEnvironmentVariable",
"title": "%containerApps.addEnvironmentVariable%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.editScaleRange",
"title": "%containerApps.editScaleRange%",
Expand Down Expand Up @@ -474,6 +479,11 @@
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /imageItem/i",
"group": "1@1"
},
{
"command": "containerApps.addEnvironmentVariable",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /environmentVariablesItem/i",
"group": "1@1"
},
{
"command": "containerApps.editScaleRange",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /scaleItem/i",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"containerApps.editContainer": "Edit Container...",
"containerApps.editContainerImage.title": "Edit Container Image...",
"containerApps.editContainerImage.shortTitle": "Edit Image...",
"containerApps.addEnvironmentVariable": "Add Environment Variable...",
"containerApps.deployImageApi": "Deploy Image to Container App (API)...",
"containerApps.deployWorkspaceProject": "Deploy Project from Workspace...",
"containerApps.deployWorkspaceProjectApi": "Deploy Project from Workspace (API)...",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/editContainer/editContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function editContainer(context: IActionContext, node?: ContainersIt
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;

const wizard: AzureWizard<ContainerEditContext> = new AzureWizard(wizardContext, {
title: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
title: localize('editContainer', 'Edit container profile for "{0}" (draft)', parentResource.name),
promptSteps: [
new ImageSourceListStep(),
new RevisionDraftDeployPromptStep(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function editContainerImage(context: IActionContext, node?: ImageIt
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;

const wizard: AzureWizard<ContainerEditUpdateContext> = new AzureWizard(wizardContext, {
title: localize('editContainerImage', 'Edit container image for app "{0}" (draft)', parentResource.name),
title: localize('editContainerImage', 'Edit container image for "{0}" (draft)', parentResource.name),
promptSteps: [
new ImageSourceListStep({ suppressEnvPrompt: true }),
new RevisionDraftDeployPromptStep(),
Expand Down
11 changes: 11 additions & 0 deletions src/commands/environmentVariables/EnvironmentVariablesContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps";
import { type SetTelemetryProps } from "../../telemetry/SetTelemetryProps";
import { type ContainerEditBaseContext } from "../editContainer/ContainerEditContext";

export type EnvironmentVariablesBaseContext = ContainerEditBaseContext;
export type EnvironmentVariablesContext = EnvironmentVariablesBaseContext & SetTelemetryProps<TelemetryProps>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps";
import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../../telemetry/commandTelemetryProps";
import { type ISecretContext } from "../../secret/ISecretContext";
import { type EnvironmentVariablesBaseContext } from "../EnvironmentVariablesContext";
import { type EnvironmentVariableType } from "./EnvironmentVariableTypeListStep";

export interface EnvironmentVariableAddBaseContext extends EnvironmentVariablesBaseContext, Pick<ISecretContext, 'secretName'> {
newEnvironmentVariableType?: EnvironmentVariableType;
newEnvironmentVariableName?: string;
newEnvironmentVariableManualInput?: string;
// secretName
}

export type EnvironmentVariableAddContext = EnvironmentVariableAddBaseContext & SetTelemetryProps<TelemetryProps>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Container } from "@azure/arm-appcontainers";
import { type Progress } from "vscode";
import { type ContainerAppItem } from "../../../tree/ContainerAppItem";
import { type RevisionsItemModel } from "../../../tree/revisionManagement/RevisionItem";
import { localize } from "../../../utils/localize";
import { RevisionDraftUpdateBaseStep } from "../../revisionDraft/RevisionDraftUpdateBaseStep";
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";

export class EnvironmentVariableAddDraftStep<T extends EnvironmentVariableAddContext> extends RevisionDraftUpdateBaseStep<T> {
public priority: number = 590;

constructor(baseItem: ContainerAppItem | RevisionsItemModel) {
super(baseItem);
}

public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('addingEnv', 'Adding environment variable (draft)...') });
this.revisionDraftTemplate.containers ??= [];

const container: Container = this.revisionDraftTemplate.containers[context.containersIdx] ?? {};
container.env ??= [];
container.env.push({
name: context.newEnvironmentVariableName,
value: context.newEnvironmentVariableManualInput ?? '', // The server doesn't allow this value to be undefined
secretRef: context.secretName,
});

await this.updateRevisionDraftWithTemplate(context);
}

public shouldExecute(context: T): boolean {
return context.containersIdx !== undefined && !!context.newEnvironmentVariableName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
import { localize } from "../../../utils/localize";
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";

export class EnvironmentVariableManualInputStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
public async prompt(context: T): Promise<void> {
context.newEnvironmentVariableManualInput = (await context.ui.showInputBox({
prompt: localize('envManualPrompt', 'Enter a value for the environment variable'),
})).trim();
context.valuesToMask.push(context.newEnvironmentVariableManualInput);
}

public shouldPrompt(context: T): boolean {
return !context.newEnvironmentVariableManualInput;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Container, type EnvironmentVar } from "@azure/arm-appcontainers";
import { AzureWizardPromptStep, validationUtils } from "@microsoft/vscode-azext-utils";
import { ext } from "../../../extensionVariables";
import { type EnvironmentVariableItem } from "../../../tree/containers/EnvironmentVariableItem";
import { type EnvironmentVariablesItem } from "../../../tree/containers/EnvironmentVariablesItem";
import { localize } from "../../../utils/localize";
import { getParentResourceFromItem } from "../../../utils/revisionDraftUtils";
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";

export class EnvironmentVariableNameStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
constructor(readonly baseItem: EnvironmentVariableItem | EnvironmentVariablesItem) {
super();
}

public async prompt(context: T): Promise<void> {
context.newEnvironmentVariableName = (await context.ui.showInputBox({
prompt: localize('envNamePrompt', 'Enter a name for the environment variable'),
validateInput: (value: string) => this.validateInput(context, value),
})).trim();
context.valuesToMask.push(context.newEnvironmentVariableName);
}

public shouldPrompt(context: T): boolean {
return !context.newEnvironmentVariableName;
}

private validateInput(context: T, value: string): string | undefined {
if (!validationUtils.hasValidCharLength(value)) {
return validationUtils.getInvalidCharLengthMessage();
}

// This is the same regex used by the portal with similar warning verbiage
const rule = /^[-._a-zA-z][-._a-zA-Z0-9]*$/;
if (!rule.test(value)) {
return localize('invalidEnvName', 'Name contains invalid character. Regex used for validation is "{0}".', String(rule));
}

// Check for duplicates
let container: Container | undefined;
if (ext.revisionDraftFileSystem.doesContainerAppsItemHaveRevisionDraft(this.baseItem)) {
container = ext.revisionDraftFileSystem.parseRevisionDraft(this.baseItem)?.containers?.[context.containersIdx];
} else {
container = getParentResourceFromItem(this.baseItem).template?.containers?.[context.containersIdx];
}

const envs: EnvironmentVar[] = container?.env ?? [];
if (envs.some(env => env.name === value)) {
return localize('duplicateEnv', 'Environment variable with name "{0}" already exists for this container.', value);
}

return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { localize } from "../../../utils/localize";
import { SecretListStep } from "../../secret/SecretListStep";
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
import { EnvironmentVariableManualInputStep } from "./EnvironmentVariableManualInputStep";

export enum EnvironmentVariableType {
ManualInput = 'manual',
SecretRef = 'secretRef',
}

export class EnvironmentVariableTypeListStep<T extends EnvironmentVariableAddContext> extends AzureWizardPromptStep<T> {
public async prompt(context: T): Promise<void> {
const placeHolder: string = localize('environmentVariableTypePrompt', 'Select an environment variable type');
const picks: IAzureQuickPickItem<EnvironmentVariableType>[] = [
{
label: localize('manualLabel', 'Manual entry'),
data: EnvironmentVariableType.ManualInput,
},
{
label: localize('secretRefLabel', 'Reference a secret'),
data: EnvironmentVariableType.SecretRef,
},
];
context.newEnvironmentVariableType = (await context.ui.showQuickPick(picks, {
placeHolder,
suppressPersistence: true,
})).data;
}

public shouldPrompt(context: T): boolean {
return !context.newEnvironmentVariableType;
}

public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
const promptSteps: AzureWizardPromptStep<T>[] = [];

switch (context.newEnvironmentVariableType) {
case EnvironmentVariableType.ManualInput:
promptSteps.push(new EnvironmentVariableManualInputStep());
break;
case EnvironmentVariableType.SecretRef:
promptSteps.push(new SecretListStep({ suppressCreatePick: true }));
break;
default:
}

return { promptSteps };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Revision } from "@azure/arm-appcontainers";
import { AzureWizard, createSubscriptionContext, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { type ContainerAppModel } from "../../../tree/ContainerAppItem";
import { type EnvironmentVariablesItem } from "../../../tree/containers/EnvironmentVariablesItem";
import { createActivityContext } from "../../../utils/activityUtils";
import { getManagedEnvironmentFromContainerApp } from "../../../utils/getResourceUtils";
import { getVerifyProvidersStep } from "../../../utils/getVerifyProvidersStep";
import { localize } from "../../../utils/localize";
import { pickEnvironmentVariables } from "../../../utils/pickItem/pickEnvironmentVariables";
import { getParentResourceFromItem, isTemplateItemEditable, TemplateItemNotEditableError } from "../../../utils/revisionDraftUtils";
import { RevisionDraftDeployPromptStep } from "../../revisionDraft/RevisionDraftDeployPromptStep";
import { type EnvironmentVariableAddContext } from "./EnvironmentVariableAddContext";
import { EnvironmentVariableAddDraftStep } from "./EnvironmentVariableAddDraftStep";
import { EnvironmentVariableNameStep } from "./EnvironmentVariableNameStep";
import { EnvironmentVariableTypeListStep } from "./EnvironmentVariableTypeListStep";

export async function addEnvironmentVariable(context: IActionContext, node?: EnvironmentVariablesItem): Promise<void> {
const item: EnvironmentVariablesItem = node ?? await pickEnvironmentVariables(context, { autoSelectDraft: true });
const { subscription, containerApp } = item;

if (!isTemplateItemEditable(item)) {
throw new TemplateItemNotEditableError(item);
}

const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription);
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(item);

const wizardContext: EnvironmentVariableAddContext = {
...context,
...subscriptionContext,
...await createActivityContext(),
subscription,
managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp),
containerApp,
containersIdx: item.containersIdx,
};
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;

const wizard: AzureWizard<EnvironmentVariableAddContext> = new AzureWizard(wizardContext, {
title: localize('updateEnvironmentVariables', 'Add environment variable to "{0}" (draft)', parentResource.name),
promptSteps: [
new EnvironmentVariableNameStep(item),
new EnvironmentVariableTypeListStep(),
new RevisionDraftDeployPromptStep(),
],
executeSteps: [
getVerifyProvidersStep<EnvironmentVariableAddContext>(),
new EnvironmentVariableAddDraftStep(item),
],
});

await wizard.prompt();
wizardContext.activityTitle = localize('updateEnvironmentVariables', 'Add environment variable "{0}" to "{1}" (draft)', wizardContext.newEnvironmentVariableName, parentResource.name);
await wizard.execute();
}
4 changes: 4 additions & 0 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { deployWorkspaceProject } from './deployWorkspaceProject/deployWorkspace
import { editContainer } from './editContainer/editContainer';
import { editContainerImage } from './editContainer/editContainerImage/editContainerImage';
import { editContainerApp } from './editContainerApp';
import { addEnvironmentVariable } from './environmentVariables/addEnvironmentVariable/addEnvironmentVariable';
import { connectToGitHub } from './gitHub/connectToGitHub/connectToGitHub';
import { disconnectRepo } from './gitHub/disconnectRepo/disconnectRepo';
import { openGitHubRepo } from './gitHub/openGitHubRepo';
Expand Down Expand Up @@ -68,6 +69,9 @@ export function registerCommands(): void {
registerCommandWithTreeNodeUnwrapping('containerApps.editContainer', editContainer);
registerCommandWithTreeNodeUnwrapping('containerApps.editContainerImage', editContainerImage);

// environment variables
registerCommandWithTreeNodeUnwrapping('containerApps.addEnvironmentVariable', addEnvironmentVariable);

// deploy
registerCommandWithTreeNodeUnwrapping('containerApps.deployImageApi', deployImageApi);
registerCommandWithTreeNodeUnwrapping('containerApps.deployRevisionDraft', deployRevisionDraft);
Expand Down
Loading

0 comments on commit cd1e562

Please sign in to comment.