From 030a60b935cb8d4faa40c3c12fd12088ba86572c Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:58:15 -0700 Subject: [PATCH 01/19] Initial implementation --- .../ContainerAppCreateStep.ts | 4 +- .../imageSource/ContainerAppUpdateStep.ts | 4 +- .../image/imageSource/ImageSourceContext.ts | 8 +- .../image/imageSource/ImageSourceListStep.ts | 5 +- .../ContainerRegistryImageConfigureStep.ts | 42 +------ .../containerRegistry/acr/AcrListStep.ts | 48 ++++---- .../acr/createAcr/RegistryCreateStep.ts | 3 +- .../acr/createAcr/createAcr.ts | 3 +- .../UpdateRegistryAndSecretsStep.ts | 8 +- ...stryCredentialsAddConfigurationListStep.ts | 114 ++++++++++++++++++ ...yCredentialsAndSecretsConfigurationStep.ts | 40 ++++++ .../RegistryCredentialsContext.ts | 17 +++ .../AcrEnableAdminUserConfirmStep.ts | 19 +++ .../dockerLogin/AcrEnableAdminUserStep.ts | 55 +++++++++ ...RegistryCredentialsAddConfigurationStep.ts | 88 ++++++++++++++ .../DockerLoginRegistryCredentialsContext.ts | 18 +++ .../dockerLogin/listCredentialsFromAcr.ts | 17 +++ 17 files changed, 410 insertions(+), 83 deletions(-) create mode 100644 src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts create mode 100644 src/commands/registryCredentials/RegistryCredentialsAndSecretsConfigurationStep.ts create mode 100644 src/commands/registryCredentials/RegistryCredentialsContext.ts create mode 100644 src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts create mode 100644 src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserStep.ts create mode 100644 src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep.ts create mode 100644 src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts create mode 100644 src/commands/registryCredentials/dockerLogin/listCredentialsFromAcr.ts diff --git a/src/commands/createContainerApp/ContainerAppCreateStep.ts b/src/commands/createContainerApp/ContainerAppCreateStep.ts index d2baf40d6..9726a051f 100644 --- a/src/commands/createContainerApp/ContainerAppCreateStep.ts +++ b/src/commands/createContainerApp/ContainerAppCreateStep.ts @@ -47,7 +47,7 @@ export class ContainerAppCreateStep extends ExecuteActivityOutputStepBase extends ExecuteActivityOutputStepBase { - public priority: number = 480; + public priority: number = 680; protected async executeCore(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp'); const containerAppEnvelope = await getContainerEnvelopeWithSecrets(context, context.subscription, containerApp); containerAppEnvelope.configuration.secrets = context.secrets; - containerAppEnvelope.configuration.registries = context.registries; + containerAppEnvelope.configuration.registries = context.registryCredentials; // We want to replace the old image containerAppEnvelope.template ||= {}; diff --git a/src/commands/image/imageSource/ImageSourceContext.ts b/src/commands/image/imageSource/ImageSourceContext.ts index 32b582cbb..eae8d7e00 100644 --- a/src/commands/image/imageSource/ImageSourceContext.ts +++ b/src/commands/image/imageSource/ImageSourceContext.ts @@ -3,22 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type EnvironmentVar, type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; +import { type EnvironmentVar } from "@azure/arm-appcontainers"; import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils"; import { type ImageSource } from "../../../constants"; import { type ImageSourceTelemetryProps as TelemetryProps } from "../../../telemetry/ImageSourceTelemetryProps"; import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps"; import { type IContainerAppContext } from "../../IContainerAppContext"; +import { type RegistryCredentialsContext } from "../../registryCredentials/RegistryCredentialsContext"; -export interface ImageSourceBaseContext extends IContainerAppContext, ExecuteActivityContext { +export interface ImageSourceBaseContext extends RegistryCredentialsContext, IContainerAppContext, ExecuteActivityContext { // ImageSourceListStep imageSource?: ImageSource; showQuickStartImage?: boolean; - // Base image attributes used as a precursor for either creating or updating a container app image?: string; - registries?: RegistryCredentials[]; - secrets?: Secret[]; envPath?: string; environmentVariables?: EnvironmentVar[]; diff --git a/src/commands/image/imageSource/ImageSourceListStep.ts b/src/commands/image/imageSource/ImageSourceListStep.ts index fa6b8d110..3bff43552 100644 --- a/src/commands/image/imageSource/ImageSourceListStep.ts +++ b/src/commands/image/imageSource/ImageSourceListStep.ts @@ -8,6 +8,7 @@ import { UIKind, env, workspace } from "vscode"; import { ImageSource } from "../../../constants"; import { localize } from "../../../utils/localize"; import { setQuickStartImage } from "../../createContainerApp/setQuickStartImage"; +import { RegistryCredentialsAddConfigurationListStep } from "../../registryCredentials/RegistryCredentialsAddConfigurationListStep"; import { EnvironmentVariablesListStep } from "./EnvironmentVariablesListStep"; import { type ImageSourceContext } from "./ImageSourceContext"; import { BuildImageStep } from "./buildImageInAzure/BuildImageStep"; @@ -68,12 +69,12 @@ export class ImageSourceListStep extends AzureWizardPromptStep { - public priority: number = 470; + public priority: number = 570; - // Configures base image attributes public async execute(context: ContainerRegistryImageSourceContext): Promise { - // Store any existing secrets and registries - let secrets: Secret[] | undefined; - let registries: RegistryCredentials[] | undefined; - - if (context.containerApp) { - // Grab existing app secrets and registry credentials - const containerAppEnvelope = await getContainerEnvelopeWithSecrets(context, context.subscription, context.containerApp); - secrets = containerAppEnvelope.configuration.secrets; - registries = containerAppEnvelope.configuration.registries; - } - - if (context.registryDomain === acrDomain) { - // ACR - const acrRegistryCredentialsAndSecrets = await getAcrCredentialsAndSecrets(context, { registries, secrets }); - context.secrets = acrRegistryCredentialsAndSecrets.secrets; - context.registries = acrRegistryCredentialsAndSecrets.registries; - } else { - // Docker Hub or other third party registry... - if (context.registryName && context.username && context.secret) { - const thirdPartyRegistryCredentialsAndSecrets = getThirdPartyCredentialsAndSecrets(context, { registries, secrets }); - context.secrets = thirdPartyRegistryCredentialsAndSecrets.secrets; - context.registries = thirdPartyRegistryCredentialsAndSecrets.registries; - } - } - - // Preserve existing secrets/registries even if new ones haven't been added - context.secrets ??= secrets; - context.registries ??= registries; - - context.image ||= `${getLoginServer(context)}/${context.repositoryName}:${context.tag}`; + context.image = `${getLoginServer(context)}/${context.repositoryName}:${context.tag}`; const { registryName, registryDomain } = parseImageName(context.image); context.telemetry.properties.registryName = registryName; context.telemetry.properties.registryDomain = registryDomain ?? 'other'; } - public shouldExecute(): boolean { - return true; + public shouldExecute(context: ContainerRegistryImageSourceContext): boolean { + return !context.image; } } diff --git a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts index f123ce3cb..89633dc3d 100644 --- a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts +++ b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts @@ -7,7 +7,7 @@ import { type ContainerRegistryManagementClient, type Registry } from "@azure/ar import { type ResourceGroup } from "@azure/arm-resources"; import { LocationListStep, ResourceGroupListStep, getResourceGroupFromId, uiUtils } from "@microsoft/vscode-azext-azureutils"; import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type ISubscriptionActionContext, type IWizardOptions } from "@microsoft/vscode-azext-utils"; -import { ImageSource, acrDomain, currentlyDeployed, noMatchingResources, noMatchingResourcesQp, quickStartImageName } from "../../../../../constants"; +import { acrDomain, currentlyDeployed, noMatchingResources, noMatchingResourcesQp, quickStartImageName } from "../../../../../constants"; import { createContainerRegistryManagementClient } from "../../../../../utils/azureClients"; import { parseImageName } from "../../../../../utils/imageNameUtils"; import { localize } from "../../../../../utils/localize"; @@ -15,13 +15,19 @@ import { type CreateContainerAppBaseContext } from "../../../../createContainerA import { type IManagedEnvironmentContext } from "../../../../createManagedEnvironment/IManagedEnvironmentContext"; import { type ContainerRegistryImageSourceContext } from "../ContainerRegistryImageSourceContext"; import { getLatestContainerAppImage } from "../getLatestContainerImage"; -import { RegistryEnableAdminUserStep } from "./RegistryEnableAdminUserStep"; -import { type CreateAcrContext } from "./createAcr/CreateAcrContext"; import { RegistryCreateStep } from "./createAcr/RegistryCreateStep"; import { RegistryNameStep } from "./createAcr/RegistryNameStep"; import { SkuListStep } from "./createAcr/SkuListStep"; +export interface AcrListStepOptions { + suppressCreate?: boolean; +} + export class AcrListStep extends AzureWizardPromptStep { + constructor(private readonly options?: AcrListStepOptions) { + super(); + } + public async prompt(context: ContainerRegistryImageSourceContext): Promise { const placeHolder: string = localize('selectRegistry', 'Select an Azure Container Registry'); @@ -38,15 +44,15 @@ export class AcrListStep extends AzureWizardPromptStep | undefined> { + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; + if (!context.registry) { - const promptSteps: AzureWizardPromptStep[] = [ + promptSteps.push( new RegistryNameStep(), new SkuListStep() - ]; - - const executeSteps: AzureWizardExecuteStep[] = [ - new RegistryCreateStep() - ]; + ); + executeSteps.push(new RegistryCreateStep()); await tryConfigureResourceGroupForRegistry(context, promptSteps); @@ -55,18 +61,12 @@ export class AcrListStep extends AzureWizardPromptStep[]> { @@ -94,17 +94,13 @@ export class AcrListStep extends AzureWizardPromptStep[] = []; - - // The why of `suppressCreate` in a nutshell: https://github.com/microsoft/vscode-azurecontainerapps/issues/78#issuecomment-1090686282 - const suppressCreate: boolean = context.imageSource !== ImageSource.RemoteAcrBuild; - if (!suppressCreate) { + if (!this.options?.suppressCreate) { picks.push({ label: localize('newContainerRegistry', '$(plus) Create new Azure Container Registry'), description: '', data: undefined }); } - if (!picks.length && !registries.length) { picks.push(noMatchingResourcesQp); } @@ -113,7 +109,7 @@ export class AcrListStep extends AzureWizardPromptStep { return !!suggestedRegistry && r.loginServer === suggestedRegistry ? { label: nonNullProp(r, 'name'), data: r, description: `${r.loginServer} ${currentlyDeployed}`, suppressPersistence: true } : - { label: nonNullProp(r, 'name'), data: r, description: r.loginServer, suppressPersistence: srExists || !suppressCreate }; + { label: nonNullProp(r, 'name'), data: r, description: r.loginServer, suppressPersistence: srExists || !this.options?.suppressCreate }; }) ); } @@ -129,7 +125,7 @@ async function tryConfigureResourceGroupForRegistry( promptSteps: AzureWizardPromptStep[], ): Promise { // No need to pollute the base context with all the potential pre-create typings as they are not otherwise used - const resourceCreationContext = context as Partial & Partial & CreateAcrContext; + const resourceCreationContext = context as Partial & Partial & ContainerRegistryImageSourceContext; if (resourceCreationContext.resourceGroup || resourceCreationContext.newResourceGroupName) { return; } diff --git a/src/commands/image/imageSource/containerRegistry/acr/createAcr/RegistryCreateStep.ts b/src/commands/image/imageSource/containerRegistry/acr/createAcr/RegistryCreateStep.ts index 61fcba766..090113313 100644 --- a/src/commands/image/imageSource/containerRegistry/acr/createAcr/RegistryCreateStep.ts +++ b/src/commands/image/imageSource/containerRegistry/acr/createAcr/RegistryCreateStep.ts @@ -7,8 +7,8 @@ import { type ContainerRegistryManagementClient } from "@azure/arm-containerregi import { LocationListStep } from "@microsoft/vscode-azext-azureutils"; import { GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; import { type Progress } from "vscode"; -import { ExecuteActivityOutputStepBase, type ExecuteActivityOutput } from "../../../../../../utils/activity/ExecuteActivityOutputStepBase"; import { createActivityChildContext } from "../../../../../../utils/activity/activityUtils"; +import { ExecuteActivityOutputStepBase, type ExecuteActivityOutput } from "../../../../../../utils/activity/ExecuteActivityOutputStepBase"; import { createContainerRegistryManagementClient } from "../../../../../../utils/azureClients"; import { localize } from "../../../../../../utils/localize"; import { type CreateAcrContext } from "./CreateAcrContext"; @@ -26,7 +26,6 @@ export class RegistryCreateStep extends ExecuteActivityOutputStepBase = new AzureWizard(wizardContext, { title, promptSteps, diff --git a/src/commands/image/updateImage/UpdateRegistryAndSecretsStep.ts b/src/commands/image/updateImage/UpdateRegistryAndSecretsStep.ts index e1fd8d7ad..56eed3af0 100644 --- a/src/commands/image/updateImage/UpdateRegistryAndSecretsStep.ts +++ b/src/commands/image/updateImage/UpdateRegistryAndSecretsStep.ts @@ -14,7 +14,7 @@ import { updateContainerApp } from "../../updateContainerApp"; import { type UpdateImageContext } from "./updateImage"; export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep { - public priority: number = 480; + public priority: number = 580; public async execute(context: UpdateImageContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp'); @@ -23,7 +23,7 @@ export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep { + private hasExistingRegistry?: boolean; + + public async configureBeforePrompt(context: RegistryCredentialsContext): Promise { + this.hasExistingRegistry = !!context.containerApp?.configuration?.registries?.some(r => { + if (!r.server) { + return false; + } + + const registryDomain: SupportedRegistries | undefined = this.getRegistryDomain(context); + if (registryDomain === acrDomain) { + return r.server === context.registry?.loginServer; + } else { + return r.server === DockerLoginRegistryCredentialsAddConfigurationStep.getThirdPartyLoginServer( + registryDomain, + nonNullProp(context, 'registryName'), + ); + } + }) + } + + public async prompt(context: RegistryCredentialsContext): Promise { + context.newRegistryCredentialType = (await context.ui.showQuickPick(this.getPicks(context), { + placeHolder: localize('selectCredentialType', 'Select a registry connection method'), + suppressPersistence: true, + })).data; + } + + public shouldPrompt(context: RegistryCredentialsContext): boolean { + return !this.hasExistingRegistry && !context.newRegistryCredentialType; + } + + public async getSubWizard(context: RegistryCredentialsContext): Promise | undefined> { + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; + + const registryDomain: SupportedRegistries | undefined = this.getRegistryDomain(context); + switch (context.newRegistryCredentialType) { + // case RegistryCredentialType.SystemAssigned: + // executeSteps.push( + // new ManagedEnvironmentIdentityEnableStep(), + // new AcrPullEnableStep(), + // new ManagedIdentityRegistryCredentialAddConfigurationStep(registryDomain), + // ); + // break; + case RegistryCredentialType.DockerLogin: + promptSteps.push(new AcrEnableAdminUserConfirmStep()); + executeSteps.push( + new AcrEnableAdminUserStep(), + new DockerLoginRegistryCredentialsAddConfigurationStep(registryDomain), + ); + break; + default: + } + + executeSteps.push(new RegistryCredentialsAndSecretsConfigurationStep()); + + return { + promptSteps, + executeSteps, + }; + } + + private getRegistryDomain(context: RegistryCredentialsContext): SupportedRegistries | undefined { + if (context.registry?.loginServer || context.registryName) { + return detectRegistryDomain(context.registry?.loginServer || nonNullProp(context, 'registryName')); + } else { + // If no registries exist, we can assume we're creating a new ACR + return acrDomain; + } + } + + public async getPicks(context: RegistryCredentialsContext): Promise[]> { + const picks: IAzureQuickPickItem[] = []; + const registryDomain = this.getRegistryDomain(context); + + if (registryDomain === acrDomain) { + // picks.push({ + // label: 'Managed Identity', + // description: '(recommended)', + // detail: localize('systemIdentityDetails', 'Setup "{0}" access for container environment resources via a system-assigned identity', 'acrPull'), + // data: RegistryCredentialType.SystemAssigned, + // }); + } + + picks.push({ + label: 'Docker Login Credentials', + detail: localize('dockerLoginDetails', 'Setup docker login access to a registry via username and password.'), + data: RegistryCredentialType.DockerLogin, + }); + + return picks; + } +} diff --git a/src/commands/registryCredentials/RegistryCredentialsAndSecretsConfigurationStep.ts b/src/commands/registryCredentials/RegistryCredentialsAndSecretsConfigurationStep.ts new file mode 100644 index 000000000..8a3ff2479 --- /dev/null +++ b/src/commands/registryCredentials/RegistryCredentialsAndSecretsConfigurationStep.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; +import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { getContainerEnvelopeWithSecrets } from "../../tree/ContainerAppItem"; +import { type RegistryCredentialsContext } from "./RegistryCredentialsContext"; + +export class RegistryCredentialsAndSecretsConfigurationStep extends AzureWizardExecuteStep { + public priority: number = 480; + + public async execute(context: RegistryCredentialsContext): Promise { + let secrets: Secret[] = []; + let registryCredentials: RegistryCredentials[] = []; + + if (context.containerApp) { + // Grab existing app secrets and registry credentials + const containerAppEnvelope = await getContainerEnvelopeWithSecrets(context, context.subscription, context.containerApp); + secrets = containerAppEnvelope.configuration.secrets ?? []; + registryCredentials = containerAppEnvelope.configuration.registries ?? []; + } + + if (context.newRegistryCredential) { + registryCredentials.push(context.newRegistryCredential); + } + + if (context.newRegistrySecret) { + secrets.push(nonNullProp(context, 'newRegistrySecret')); + } + + context.secrets = secrets; + context.registryCredentials = registryCredentials; + } + + public shouldExecute(context: RegistryCredentialsContext): boolean { + return !context.registryCredentials || !context.secrets; + } +} diff --git a/src/commands/registryCredentials/RegistryCredentialsContext.ts b/src/commands/registryCredentials/RegistryCredentialsContext.ts new file mode 100644 index 000000000..433fcee3b --- /dev/null +++ b/src/commands/registryCredentials/RegistryCredentialsContext.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; +import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils"; +import { type DockerLoginRegistryCredentialsContext } from "./dockerLogin/DockerLoginRegistryCredentialsContext"; +import { type RegistryCredentialType } from "./RegistryCredentialsAddConfigurationListStep"; + +export type CredentialTypeContext = DockerLoginRegistryCredentialsContext /** & ManagedIdentityRegistryCredentialsContext */; + +export interface RegistryCredentialsContext extends CredentialTypeContext, ExecuteActivityContext { + newRegistryCredentialType?: RegistryCredentialType; + registryCredentials?: RegistryCredentials[]; + secrets?: Secret[]; +} diff --git a/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts new file mode 100644 index 000000000..40150b3ca --- /dev/null +++ b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * 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 DockerLoginRegistryCredentialsContext } from "./DockerLoginRegistryCredentialsContext"; + +export class AcrEnableAdminUserConfirmStep extends AzureWizardPromptStep { + public async prompt(context: DockerLoginRegistryCredentialsContext): Promise { + const message = localize('enableAdminUser', 'Admin user login is required to continue. If enabled, it will allow docker login access to your ACR using the registry\'s username and password.'); + await context.ui.showWarningMessage(message, { modal: true }, { title: localize('enable', 'Enable') }); + } + + public shouldPrompt(context: DockerLoginRegistryCredentialsContext): boolean { + return !context.registryName && !context.registry?.adminUserEnabled; + } +} diff --git a/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserStep.ts b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserStep.ts new file mode 100644 index 000000000..8fe27e56b --- /dev/null +++ b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserStep.ts @@ -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 { type ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { activityFailIcon, activitySuccessContext, activitySuccessIcon, GenericParentTreeItem, GenericTreeItem, nonNullProp, nonNullValue } from "@microsoft/vscode-azext-utils"; +import { createActivityChildContext } from "../../../utils/activity/activityUtils"; +import { ExecuteActivityOutputStepBase, type ExecuteActivityOutput } from "../../../utils/activity/ExecuteActivityOutputStepBase"; +import { createContainerRegistryManagementClient } from "../../../utils/azureClients"; +import { localize } from "../../../utils/localize"; +import { type DockerLoginRegistryCredentialsContext } from "./DockerLoginRegistryCredentialsContext"; + +export class AcrEnableAdminUserStep extends ExecuteActivityOutputStepBase { + public priority: number = 450; + + public async executeCore(context: DockerLoginRegistryCredentialsContext): Promise { + const registry = nonNullValue(context.registry); + registry.adminUserEnabled = true; + + const client: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); + context.registry = await client.registries.beginUpdateAndWait(getResourceGroupFromId(nonNullProp(registry, 'id')), nonNullProp(registry, 'name'), registry); + + if (!context.registry?.adminUserEnabled) { + throw new Error(localize('failedToUpdate', 'Failed to enable admin user for registry "{0}". Go to the portal to manually update.', context.registry?.name)); + } + } + + public shouldExecute(context: DockerLoginRegistryCredentialsContext): boolean { + return !!context.registry && !context.registry.adminUserEnabled; + } + + protected createSuccessOutput(context: DockerLoginRegistryCredentialsContext): ExecuteActivityOutput { + return { + item: new GenericTreeItem(undefined, { + contextValue: createActivityChildContext(['acrEnableAdminUserStepSuccessItem', activitySuccessContext]), + label: localize('enableAdminUser', 'Enable admin user setting for container registry "{0}"', context.registry?.name), + iconPath: activitySuccessIcon + }), + message: localize('enableAdminUserSuccess', 'Successfully enabled admin user setting for container registry "{0}".', context.registry?.name) + }; + } + + protected createFailOutput(context: DockerLoginRegistryCredentialsContext): ExecuteActivityOutput { + return { + item: new GenericParentTreeItem(undefined, { + contextValue: createActivityChildContext(['acrEnableAdminUserStepFailItem', activitySuccessContext]), + label: localize('enableAdminUser', 'Enable admin user setting for container registry "{0}"', context.registry?.name), + iconPath: activityFailIcon + }), + message: localize('enableAdminUserFail', 'Failed to enable admin user setting for container registry "{0}".', context.registry?.name) + }; + } +} diff --git a/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep.ts b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep.ts new file mode 100644 index 000000000..651fab66f --- /dev/null +++ b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; +import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { acrDomain, dockerHubDomain, dockerHubRegistry, type SupportedRegistries } from "../../../constants"; +import { type DockerLoginRegistryCredentialsContext } from "./DockerLoginRegistryCredentialsContext"; +import { listCredentialsFromAcr } from "./listCredentialsFromAcr"; + +interface RegistryCredentialAndSecret { + registryCredential: RegistryCredentials; + secret: Secret; +} + +export class DockerLoginRegistryCredentialsAddConfigurationStep extends AzureWizardExecuteStep { + public priority: number = 470; + + constructor(private readonly supportedRegistryDomain: SupportedRegistries | undefined) { + super(); + } + + public async execute(context: DockerLoginRegistryCredentialsContext): Promise { + if (this.supportedRegistryDomain === acrDomain) { + // ACR + const acrRegistryCredentialAndSecret: RegistryCredentialAndSecret = await this.getAcrCredentialAndSecret(context); + context.newRegistryCredential = acrRegistryCredentialAndSecret.registryCredential; + context.newRegistrySecret = acrRegistryCredentialAndSecret.secret; + } else { + // Docker Hub or other third party registry... + if (context.registryName && context.username && context.secret) { + const thirdPartyRegistryCredentialAndSecret: RegistryCredentialAndSecret = this.getThirdPartyRegistryCredentialAndSecret(context); + context.newRegistryCredential = thirdPartyRegistryCredentialAndSecret.registryCredential; + context.newRegistrySecret = thirdPartyRegistryCredentialAndSecret.secret; + } + } + } + + public shouldExecute(context: DockerLoginRegistryCredentialsContext): boolean { + return !context.newRegistryCredential || !context.newRegistrySecret; + } + + private async getAcrCredentialAndSecret(context: DockerLoginRegistryCredentialsContext): Promise { + const registry = nonNullProp(context, 'registry'); + const { username, password } = await listCredentialsFromAcr(context); + const passwordName = `${registry.name?.toLocaleLowerCase()}-${password?.name}`; + + return { + registryCredential: { + identity: '', // The server populates an `undefined` identity as ''. Use the same convention so we can do deep copy comparisons later. + server: registry.loginServer, + username: username, + passwordSecretRef: passwordName + }, + secret: { + name: passwordName, + value: password.value, + }, + }; + } + + private getThirdPartyRegistryCredentialAndSecret(context: DockerLoginRegistryCredentialsContext): RegistryCredentialAndSecret { + // If 'docker.io', convert to 'index.docker.io', else use registryName as loginServer + const loginServer: string = DockerLoginRegistryCredentialsAddConfigurationStep.getThirdPartyLoginServer( + this.supportedRegistryDomain as typeof dockerHubDomain | undefined, + nonNullProp(context, 'registryName'), + ); + const passwordSecretRef: string = `${loginServer.replace(/[^a-z0-9-]+/g, '')}-${context.username}`; + + return { + registryCredential: { + identity: '', // The server populates an `undefined` identity as ''. Use the same convention so we can do deep copy comparisons later. + server: loginServer, + username: context.username, + passwordSecretRef + }, + secret: { + name: passwordSecretRef, + value: context.secret + }, + }; + } + + public static getThirdPartyLoginServer(registryDomain: typeof dockerHubDomain | undefined, registryName: string): string { + return (registryDomain === dockerHubDomain) ? dockerHubRegistry : registryName.toLowerCase(); + } +} diff --git a/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts new file mode 100644 index 000000000..6c8755d62 --- /dev/null +++ b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; +import { type IContainerAppContext } from "../../IContainerAppContext"; +import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; + +export interface DockerLoginRegistryCredentialsContext extends CreateAcrContext, IContainerAppContext { + // These values are often populated from the Docker extension via the deployImage API layer + registryName?: string; + username?: string; + secret?: string; + + newRegistrySecret?: Secret; + newRegistryCredential?: RegistryCredentials; +} diff --git a/src/commands/registryCredentials/dockerLogin/listCredentialsFromAcr.ts b/src/commands/registryCredentials/dockerLogin/listCredentialsFromAcr.ts new file mode 100644 index 000000000..f99b1c739 --- /dev/null +++ b/src/commands/registryCredentials/dockerLogin/listCredentialsFromAcr.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ContainerRegistryManagementClient, type RegistryPassword } from "@azure/arm-containerregistry"; +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { nonNullProp, nonNullValue, nonNullValueAndProp, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { createContainerRegistryManagementClient } from "../../../utils/azureClients"; +import { type RegistryCredentialsContext } from "../RegistryCredentialsContext"; + +export async function listCredentialsFromAcr(context: ISubscriptionActionContext & Partial): Promise<{ username: string, password: RegistryPassword }> { + const containerClient: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); + const credentials = await containerClient.registries.listCredentials(getResourceGroupFromId(nonNullValueAndProp(context.registry, 'id')), nonNullValueAndProp(context.registry, 'name')); + const password = credentials.passwords?.find(cred => cred.name === 'password' || cred.name === 'password2'); + return { username: nonNullProp(credentials, 'username'), password: nonNullValue(password) }; +} From 590d35e6d87a94c23729e9c305dafb7ad1a28dc7 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:25:19 -0700 Subject: [PATCH 02/19] Update step priorities --- src/commands/EXECUTE_PRIORITY.md | 41 +++++++++++-------- .../buildImageInAzure/BuildImageStep.ts | 2 +- .../imageSource/buildImageInAzure/RunStep.ts | 2 +- .../buildImageInAzure/TarFileStep.ts | 2 +- .../buildImageInAzure/UploadSourceCodeStep.ts | 2 +- .../image/updateImage/UpdateImageDraftStep.ts | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/commands/EXECUTE_PRIORITY.md b/src/commands/EXECUTE_PRIORITY.md index 79a6e4f7c..2b349989b 100644 --- a/src/commands/EXECUTE_PRIORITY.md +++ b/src/commands/EXECUTE_PRIORITY.md @@ -22,35 +22,42 @@ When creating or updating resources, execute steps should occupy certain priorit - RegistryCreateStep: 350 -### 3. Image +### 3. Registry Credentials Priority Range: 400 - 490 -#### General Steps -##### Build Image in Azure Steps +#### Steps +##### Managed Identity Registry Credential +- Coming soon... -- TarFileStep: 420 -- UploadSourceCodeStep: 430 -- RunStep: 440 -- BuildImageStep: 450 -- ContainerRegistryImageConfigureStep: 470 +##### Admin User Registry Credential +- AcrEnableAdminUserStep: 450 +- DockerLoginRegistryCredentialsAddConfigurationStep: 470 -##### Container Registry Steps +##### Registry Credentials +- RegistryCredentialsAndSecretsConfigurationStep: 480 -- ContainerRegistryImageConfigureStep: 470 +### 4. Image -#### `updateImage` Steps +Priority Range: 500 - 590 -- UpdateRegistryAndSecretsStep: 480 -- UpdateImageDraftStep: 490 (revision draft) +#### General Steps +##### Build Image in Azure Steps -### 4. Unallocated Space +- TarFileStep: 520 +- UploadSourceCodeStep: 530 +- RunStep: 540 +- BuildImageStep: 550 +- ContainerRegistryImageConfigureStep: 570 -Priority Range: 500 - 590 +##### Container Registry Steps -#### Steps +- ContainerRegistryImageConfigureStep: 570 + +#### `updateImage` Steps -Reserved +- UpdateRegistryAndSecretsStep: 580 +- UpdateImageDraftStep: 590 (revision draft) ### 5. Container App diff --git a/src/commands/image/imageSource/buildImageInAzure/BuildImageStep.ts b/src/commands/image/imageSource/buildImageInAzure/BuildImageStep.ts index 2d83ec9e9..038285fd4 100644 --- a/src/commands/image/imageSource/buildImageInAzure/BuildImageStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/BuildImageStep.ts @@ -15,7 +15,7 @@ import { type BuildImageInAzureImageSourceContext } from "./BuildImageInAzureIma import { buildImageInAzure } from "./buildImageInAzure"; export class BuildImageStep extends ExecuteActivityOutputStepBase { - public priority: number = 450; + public priority: number = 550; protected acrBuildError: AcrBuildResults; protected async executeCore(context: BuildImageInAzureImageSourceContext): Promise { diff --git a/src/commands/image/imageSource/buildImageInAzure/RunStep.ts b/src/commands/image/imageSource/buildImageInAzure/RunStep.ts index 604ed2876..eefa1a308 100644 --- a/src/commands/image/imageSource/buildImageInAzure/RunStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/RunStep.ts @@ -15,7 +15,7 @@ import { localize } from "../../../../utils/localize"; import { type BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; export class RunStep extends ExecuteActivityOutputStepBase { - public priority: number = 440; + public priority: number = 540; protected async executeCore(context: BuildImageInAzureImageSourceContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { // Need to keep the additional try wrapper here to execute finally, then we can catch any error that percolates up and display its output diff --git a/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts b/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts index b907ba376..2532bee76 100644 --- a/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts @@ -11,7 +11,7 @@ import { type BuildImageInAzureImageSourceContext } from "./BuildImageInAzureIma const idPrecision = 6; export class TarFileStep extends AzureWizardExecuteStep { - public priority: number = 420; + public priority: number = 520; public async execute(context: BuildImageInAzureImageSourceContext): Promise { const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); diff --git a/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts b/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts index abcfe99b2..5cc684c0c 100644 --- a/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts @@ -20,7 +20,7 @@ import { type BuildImageInAzureImageSourceContext } from './BuildImageInAzureIma const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; export class UploadSourceCodeStep extends ExecuteActivityOutputStepBase { - public priority: number = 430; + public priority: number = 530; /** Path to a directory containing a custom Dockerfile that we sometimes build and upload for the user */ private _customDockerfileDirPath?: string; /** Relative path of src folder from rootFolder and what gets deployed */ diff --git a/src/commands/image/updateImage/UpdateImageDraftStep.ts b/src/commands/image/updateImage/UpdateImageDraftStep.ts index b708d89bc..e06331eb7 100644 --- a/src/commands/image/updateImage/UpdateImageDraftStep.ts +++ b/src/commands/image/updateImage/UpdateImageDraftStep.ts @@ -16,7 +16,7 @@ import { getContainerNameForImage } from "../imageSource/containerRegistry/getCo import { type UpdateImageContext } from "./updateImage"; export class UpdateImageDraftStep extends RevisionDraftUpdateBaseStep { - public priority: number = 490; + public priority: number = 590; constructor(baseItem: ContainerAppItem | RevisionsItemModel) { super(baseItem); From fb87c28219f14e6e30bb835259f29a96ad938efd Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:27:32 -0700 Subject: [PATCH 03/19] Update acrliststep call --- .../containerRegistry/ContainerRegistryListStep.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/image/imageSource/containerRegistry/ContainerRegistryListStep.ts b/src/commands/image/imageSource/containerRegistry/ContainerRegistryListStep.ts index 923769332..6a6a908a3 100644 --- a/src/commands/image/imageSource/containerRegistry/ContainerRegistryListStep.ts +++ b/src/commands/image/imageSource/containerRegistry/ContainerRegistryListStep.ts @@ -7,14 +7,14 @@ import { AzureWizardPromptStep, type IAzureQuickPickItem, type IWizardOptions } import { UIKind, env } from "vscode"; import { acrDomain, dockerHubDomain, type SupportedRegistries } from "../../../../constants"; import { localize } from "../../../../utils/localize"; -import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext"; -import { RegistryImageInputStep } from "./RegistryImageInputStep"; import { AcrListStep } from "./acr/AcrListStep"; import { AcrRepositoriesListStep } from "./acr/AcrRepositoriesListStep"; import { AcrTagListStep } from "./acr/AcrTagListStep"; +import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext"; import { DockerHubContainerRepositoryListStep } from "./dockerHub/DockerHubContainerRepositoryListStep"; import { DockerHubContainerTagListStep } from "./dockerHub/DockerHubContainerTagListStep"; import { DockerHubNamespaceInputStep } from "./dockerHub/DockerHubNamespaceInputStep"; +import { RegistryImageInputStep } from "./RegistryImageInputStep"; export class ContainerRegistryListStep extends AzureWizardPromptStep { public hideStepCount: boolean = true; @@ -46,7 +46,7 @@ export class ContainerRegistryListStep extends AzureWizardPromptStep[] = []; switch (context.registryDomain) { case acrDomain: - promptSteps.push(new AcrListStep(), new AcrRepositoriesListStep(), new AcrTagListStep()); + promptSteps.push(new AcrListStep({ suppressCreate: true }), new AcrRepositoriesListStep(), new AcrTagListStep()); break; case dockerHubDomain: promptSteps.push(new DockerHubNamespaceInputStep(), new DockerHubContainerRepositoryListStep(), new DockerHubContainerTagListStep()); From 8dea143616dc9dcdffe955919b1b5550ffad1aeb Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:32:23 -0700 Subject: [PATCH 04/19] Correct an old priority --- src/commands/image/imageSource/ContainerAppUpdateStep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/image/imageSource/ContainerAppUpdateStep.ts b/src/commands/image/imageSource/ContainerAppUpdateStep.ts index f9f6e89f6..28efeef56 100644 --- a/src/commands/image/imageSource/ContainerAppUpdateStep.ts +++ b/src/commands/image/imageSource/ContainerAppUpdateStep.ts @@ -15,7 +15,7 @@ import { type ImageSourceContext } from "./ImageSourceContext"; import { getContainerNameForImage } from "./containerRegistry/getContainerNameForImage"; export class ContainerAppUpdateStep extends ExecuteActivityOutputStepBase { - public priority: number = 680; + public priority: number = 650; protected async executeCore(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp'); From 97fc630534af2fb68e2091bc6c9b6ad1c17fbca5 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:36:06 -0700 Subject: [PATCH 05/19] Fix typing --- .../image/imageSource/containerRegistry/acr/AcrListStep.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts index 89633dc3d..ac3f76f37 100644 --- a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts +++ b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts @@ -15,6 +15,7 @@ import { type CreateContainerAppBaseContext } from "../../../../createContainerA import { type IManagedEnvironmentContext } from "../../../../createManagedEnvironment/IManagedEnvironmentContext"; import { type ContainerRegistryImageSourceContext } from "../ContainerRegistryImageSourceContext"; import { getLatestContainerAppImage } from "../getLatestContainerImage"; +import { type CreateAcrContext } from "./createAcr/CreateAcrContext"; import { RegistryCreateStep } from "./createAcr/RegistryCreateStep"; import { RegistryNameStep } from "./createAcr/RegistryNameStep"; import { SkuListStep } from "./createAcr/SkuListStep"; @@ -125,7 +126,7 @@ async function tryConfigureResourceGroupForRegistry( promptSteps: AzureWizardPromptStep[], ): Promise { // No need to pollute the base context with all the potential pre-create typings as they are not otherwise used - const resourceCreationContext = context as Partial & Partial & ContainerRegistryImageSourceContext; + const resourceCreationContext = context as Partial & Partial & CreateAcrContext; if (resourceCreationContext.resourceGroup || resourceCreationContext.newResourceGroupName) { return; } From 8733ed454f6abffc60c0400719a1832b7df5b27b Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:12:22 -0700 Subject: [PATCH 06/19] Update comment --- .../dockerLogin/DockerLoginRegistryCredentialsContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts index 6c8755d62..7fed42269 100644 --- a/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts +++ b/src/commands/registryCredentials/dockerLogin/DockerLoginRegistryCredentialsContext.ts @@ -8,7 +8,7 @@ import { type IContainerAppContext } from "../../IContainerAppContext"; import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; export interface DockerLoginRegistryCredentialsContext extends CreateAcrContext, IContainerAppContext { - // These values are often populated from the Docker extension via the deployImage API layer + // These values are often pre-populated from the Docker extension via the deployImage API layer registryName?: string; username?: string; secret?: string; From 73ec21ded56ee90adb7c8edd75988fc84f19c95b Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:25:56 -0700 Subject: [PATCH 07/19] Update shouldPrompt comment with ref link --- .../dockerLogin/AcrEnableAdminUserConfirmStep.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts index 40150b3ca..05b544b6c 100644 --- a/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts +++ b/src/commands/registryCredentials/dockerLogin/AcrEnableAdminUserConfirmStep.ts @@ -14,6 +14,7 @@ export class AcrEnableAdminUserConfirmStep extends AzureWizardPromptStep Date: Mon, 12 Aug 2024 11:20:32 -0700 Subject: [PATCH 08/19] Add managed environment context requirements --- src/commands/ManagedEnvironmentContext.ts | 13 +++++++++++++ .../createContainerApp/ContainerAppCreateStep.ts | 2 +- .../createContainerApp/ContainerAppNameStep.ts | 4 ++-- .../CreateContainerAppContext.ts | 2 -- .../createContainerApp/createContainerApp.ts | 2 +- ...ext.ts => CreateManagedEnvironmentContext.ts} | 4 ++-- .../LogAnalyticsCreateStep.ts | 12 ++++++------ .../LogAnalyticsListStep.ts | 10 +++++----- .../ManagedEnvironmentCreateStep.ts | 12 ++++++------ .../ManagedEnvironmentNameStep.ts | 10 +++++----- .../createManagedEnvironment.ts | 12 ++++++------ .../DeployWorkspaceProjectInternalContext.ts | 4 ++-- src/commands/image/deployImageApi/deployImage.ts | 9 ++++++--- .../containerRegistry/acr/AcrListStep.ts | 4 ++-- src/commands/image/updateImage/updateImage.ts | 7 +++++-- .../RegistryCredentialsContext.ts | 3 ++- .../ManagedIdentityRegistryCredentialsContext.ts | 13 +++++++++++++ src/utils/getResourceUtils.ts | 16 ++++++++++++++++ 18 files changed, 93 insertions(+), 46 deletions(-) create mode 100644 src/commands/ManagedEnvironmentContext.ts rename src/commands/createManagedEnvironment/{IManagedEnvironmentContext.ts => CreateManagedEnvironmentContext.ts} (88%) create mode 100644 src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts create mode 100644 src/utils/getResourceUtils.ts diff --git a/src/commands/ManagedEnvironmentContext.ts b/src/commands/ManagedEnvironmentContext.ts new file mode 100644 index 000000000..dfeae1526 --- /dev/null +++ b/src/commands/ManagedEnvironmentContext.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ManagedEnvironment } from "@azure/arm-appcontainers"; +import { type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; + +export interface ManagedEnvironmentContext extends ISubscriptionActionContext { + subscription: AzureSubscription; + managedEnvironment?: ManagedEnvironment; +} diff --git a/src/commands/createContainerApp/ContainerAppCreateStep.ts b/src/commands/createContainerApp/ContainerAppCreateStep.ts index 9726a051f..cb8769ea1 100644 --- a/src/commands/createContainerApp/ContainerAppCreateStep.ts +++ b/src/commands/createContainerApp/ContainerAppCreateStep.ts @@ -43,7 +43,7 @@ export class ContainerAppCreateStep extends ExecuteActivityOutputStepBase { - const resourceGroupName: string = getResourceGroupFromId(nonNullProp(context, 'managedEnvironmentId')); + const resourceGroupName: string = getResourceGroupFromId(nonNullValueAndProp(context.managedEnvironment, 'id')); if (!await ContainerAppNameStep.isNameAvailable(context, resourceGroupName, name)) { return localize('containerAppExists', 'The container app "{0}" already exists in resource group "{1}".', name, resourceGroupName); } diff --git a/src/commands/createContainerApp/CreateContainerAppContext.ts b/src/commands/createContainerApp/CreateContainerAppContext.ts index 739a50b8a..b4e1e0fd2 100644 --- a/src/commands/createContainerApp/CreateContainerAppContext.ts +++ b/src/commands/createContainerApp/CreateContainerAppContext.ts @@ -14,8 +14,6 @@ import { type IngressBaseContext } from '../ingress/IngressContext'; export interface CreateContainerAppBaseContext extends IResourceGroupWizardContext, ImageSourceBaseContext, IngressBaseContext, IContainerAppContext, ExecuteActivityContext { newContainerAppName?: string; - - managedEnvironmentId?: string; managedEnvironment?: ManagedEnvironment; } diff --git a/src/commands/createContainerApp/createContainerApp.ts b/src/commands/createContainerApp/createContainerApp.ts index 6a3345f39..60d659e7a 100644 --- a/src/commands/createContainerApp/createContainerApp.ts +++ b/src/commands/createContainerApp/createContainerApp.ts @@ -34,7 +34,7 @@ export async function createContainerApp(context: IActionContext, node?: Managed ...createSubscriptionContext(node.subscription), ...await createActivityContext(), subscription: node.subscription, - managedEnvironmentId: node.managedEnvironment.id, + managedEnvironment: node.managedEnvironment, alwaysPromptIngress: true }; diff --git a/src/commands/createManagedEnvironment/IManagedEnvironmentContext.ts b/src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts similarity index 88% rename from src/commands/createManagedEnvironment/IManagedEnvironmentContext.ts rename to src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts index 6bcc1adde..a1fea9097 100644 --- a/src/commands/createManagedEnvironment/IManagedEnvironmentContext.ts +++ b/src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts @@ -9,13 +9,13 @@ import { type IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureu import { type ExecuteActivityContext, type ISubscriptionActionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -export interface IManagedEnvironmentContext extends ISubscriptionActionContext, IResourceGroupWizardContext, ExecuteActivityContext { +export interface CreateManagedEnvironmentContext extends ISubscriptionActionContext, IResourceGroupWizardContext, ExecuteActivityContext { subscription: AzureSubscription; newManagedEnvironmentName?: string; newLogAnalyticsWorkspaceName?: string; // This isn't normally populated by a name step, but we still allow this to be prepopulated - logAnalyticsWorkspace?: Workspace; // created when the wizard is done executing + logAnalyticsWorkspace?: Workspace; managedEnvironment?: ManagedEnvironment; } diff --git a/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts b/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts index 9c4563068..28b060571 100644 --- a/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts +++ b/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts @@ -11,12 +11,12 @@ import { createActivityChildContext } from "../../utils/activity/activityUtils"; import { createOperationalInsightsManagementClient } from "../../utils/azureClients"; import { localize } from "../../utils/localize"; import { nonNullProp } from "../../utils/nonNull"; -import { type IManagedEnvironmentContext } from "./IManagedEnvironmentContext"; +import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; -export class LogAnalyticsCreateStep extends ExecuteActivityOutputStepBase { +export class LogAnalyticsCreateStep extends ExecuteActivityOutputStepBase { public priority: number = 220; - protected async executeCore(context: IManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + protected async executeCore(context: CreateManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const opClient = await createOperationalInsightsManagementClient(context); const resourceGroup = nonNullProp(context, 'resourceGroup'); const workspaceName = context.newLogAnalyticsWorkspaceName || nonNullProp(context, 'newManagedEnvironmentName'); @@ -28,11 +28,11 @@ export class LogAnalyticsCreateStep extends ExecuteActivityOutputStepBase { - public async prompt(context: IManagedEnvironmentContext): Promise { +export class LogAnalyticsListStep extends AzureWizardPromptStep { + public async prompt(context: CreateManagedEnvironmentContext): Promise { const placeHolder: string = localize('selectLogAnalytics', 'Select Log Analytics workspace. Your Log Analytics workspace will contain all your application logs.'); context.logAnalyticsWorkspace = (await context.ui.showQuickPick(this.getQuickPicks(context), { placeHolder })).data; } - public shouldPrompt(context: IManagedEnvironmentContext): boolean { + public shouldPrompt(context: CreateManagedEnvironmentContext): boolean { return !context.logAnalyticsWorkspace; } - private async getQuickPicks(context: IManagedEnvironmentContext): Promise[]> { + private async getQuickPicks(context: CreateManagedEnvironmentContext): Promise[]> { const picks: IAzureQuickPickItem[] = []; picks.push({ diff --git a/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts index 5af41ea45..c2e344a62 100644 --- a/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts +++ b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts @@ -13,12 +13,12 @@ import { ExecuteActivityOutputStepBase, type ExecuteActivityOutput } from "../.. import { createContainerAppsAPIClient, createOperationalInsightsManagementClient } from '../../utils/azureClients'; import { localize } from "../../utils/localize"; import { nonNullProp, nonNullValueAndProp } from "../../utils/nonNull"; -import { type IManagedEnvironmentContext } from "./IManagedEnvironmentContext"; +import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; -export class ManagedEnvironmentCreateStep extends ExecuteActivityOutputStepBase { +export class ManagedEnvironmentCreateStep extends ExecuteActivityOutputStepBase { public priority: number = 250; - protected async executeCore(context: IManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + protected async executeCore(context: CreateManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context); const opClient = await createOperationalInsightsManagementClient(context); @@ -61,11 +61,11 @@ export class ManagedEnvironmentCreateStep extends ExecuteActivityOutputStepBase< } } - public shouldExecute(context: IManagedEnvironmentContext): boolean { + public shouldExecute(context: CreateManagedEnvironmentContext): boolean { return !context.managedEnvironment; } - protected createSuccessOutput(context: IManagedEnvironmentContext): ExecuteActivityOutput { + protected createSuccessOutput(context: CreateManagedEnvironmentContext): ExecuteActivityOutput { return { item: new GenericTreeItem(undefined, { contextValue: createActivityChildContext(['managedEnvironmentCreateStepSuccessItem', activitySuccessContext]), @@ -76,7 +76,7 @@ export class ManagedEnvironmentCreateStep extends ExecuteActivityOutputStepBase< }; } - protected createFailOutput(context: IManagedEnvironmentContext): ExecuteActivityOutput { + protected createFailOutput(context: CreateManagedEnvironmentContext): ExecuteActivityOutput { return { item: new GenericParentTreeItem(undefined, { contextValue: createActivityChildContext(['managedEnvironmentCreateStepFailItem', activityFailContext]), diff --git a/src/commands/createManagedEnvironment/ManagedEnvironmentNameStep.ts b/src/commands/createManagedEnvironment/ManagedEnvironmentNameStep.ts index cf91f3152..bd74abbd0 100644 --- a/src/commands/createManagedEnvironment/ManagedEnvironmentNameStep.ts +++ b/src/commands/createManagedEnvironment/ManagedEnvironmentNameStep.ts @@ -7,10 +7,10 @@ import { type ContainerAppsAPIClient } from "@azure/arm-appcontainers"; import { AzureWizardPromptStep, nonNullValueAndProp, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { createContainerAppsAPIClient } from "../../utils/azureClients"; import { localize } from "../../utils/localize"; -import { type IManagedEnvironmentContext } from './IManagedEnvironmentContext'; +import { type CreateManagedEnvironmentContext } from './CreateManagedEnvironmentContext'; -export class ManagedEnvironmentNameStep extends AzureWizardPromptStep { - public async prompt(context: IManagedEnvironmentContext): Promise { +export class ManagedEnvironmentNameStep extends AzureWizardPromptStep { + public async prompt(context: CreateManagedEnvironmentContext): Promise { const prompt: string = localize('containerAppNamePrompt', 'Enter a container apps environment name.'); context.newManagedEnvironmentName = (await context.ui.showInputBox({ prompt, @@ -21,7 +21,7 @@ export class ManagedEnvironmentNameStep extends AzureWizardPromptStep { + private async validateNameAvailable(context: CreateManagedEnvironmentContext, name: string): Promise { if (!context.resourceGroup) { // If a new resource group will house the managed environment, we can skip the name check return undefined; diff --git a/src/commands/createManagedEnvironment/createManagedEnvironment.ts b/src/commands/createManagedEnvironment/createManagedEnvironment.ts index dbd2523d5..8f455413d 100644 --- a/src/commands/createManagedEnvironment/createManagedEnvironment.ts +++ b/src/commands/createManagedEnvironment/createManagedEnvironment.ts @@ -11,7 +11,7 @@ import { ext } from "../../extensionVariables"; import { createActivityContext } from "../../utils/activity/activityUtils"; import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep"; import { localize } from "../../utils/localize"; -import { type IManagedEnvironmentContext } from "./IManagedEnvironmentContext"; +import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; import { LogAnalyticsCreateStep } from "./LogAnalyticsCreateStep"; import { ManagedEnvironmentCreateStep } from "./ManagedEnvironmentCreateStep"; import { ManagedEnvironmentNameStep } from "./ManagedEnvironmentNameStep"; @@ -19,7 +19,7 @@ import { ManagedEnvironmentNameStep } from "./ManagedEnvironmentNameStep"; export async function createManagedEnvironment(context: IActionContext, node?: { subscription: AzureSubscription }): Promise { const subscription = node?.subscription ?? await subscriptionExperience(context, ext.rgApiV2.resources.azureResourceTreeDataProvider); - const wizardContext: IManagedEnvironmentContext = { + const wizardContext: CreateManagedEnvironmentContext = { ...context, ...createSubscriptionContext(subscription), ...await createActivityContext(), @@ -27,12 +27,12 @@ export async function createManagedEnvironment(context: IActionContext, node?: { }; const title: string = localize('createManagedEnv', 'Create container apps environment'); - const promptSteps: AzureWizardPromptStep[] = []; - const executeSteps: AzureWizardExecuteStep[] = []; + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; promptSteps.push(new ManagedEnvironmentNameStep()); executeSteps.push( - getVerifyProvidersStep(), + getVerifyProvidersStep(), new ResourceGroupCreateStep(), new LogAnalyticsCreateStep(), new ManagedEnvironmentCreateStep() @@ -40,7 +40,7 @@ export async function createManagedEnvironment(context: IActionContext, node?: { LocationListStep.addProviderForFiltering(wizardContext, appProvider, managedEnvironmentsId); LocationListStep.addStep(wizardContext, promptSteps); - const wizard: AzureWizard = new AzureWizard(wizardContext, { + const wizard: AzureWizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps, diff --git a/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts b/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts index 665c59d12..cbea6c3fd 100644 --- a/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts +++ b/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts @@ -8,7 +8,7 @@ import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps"; import { type DeployWorkspaceProjectInternalTelemetryProps as TelemetryProps } from "../../../telemetry/deployWorkspaceProjectTelemetryProps"; import { type IContainerAppContext } from "../../IContainerAppContext"; import { type CreateContainerAppBaseContext } from "../../createContainerApp/CreateContainerAppContext"; -import { type IManagedEnvironmentContext } from "../../createManagedEnvironment/IManagedEnvironmentContext"; +import { type CreateManagedEnvironmentContext } from "../../createManagedEnvironment/CreateManagedEnvironmentContext"; import { type BuildImageInAzureImageSourceBaseContext } from "../../image/imageSource/buildImageInAzure/BuildImageInAzureImageSourceContext"; import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; import { type DeploymentConfiguration } from "../deploymentConfiguration/DeploymentConfiguration"; @@ -16,7 +16,7 @@ import { type DeploymentConfiguration } from "../deploymentConfiguration/Deploym // Use intersection typing instead of an interface here to bypass some minor (relatively trivial) type mismatch issues introduced by having to use the 'Partial' utility export type DeployWorkspaceProjectInternalBaseContext = IContainerAppContext & - Partial & + Partial & Partial & Partial & Partial & diff --git a/src/commands/image/deployImageApi/deployImage.ts b/src/commands/image/deployImageApi/deployImage.ts index 3d82447b8..0d7158380 100644 --- a/src/commands/image/deployImageApi/deployImage.ts +++ b/src/commands/image/deployImageApi/deployImage.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizard, createSubscriptionContext, type AzureWizardExecuteStep, type AzureWizardPromptStep, type IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, createSubscriptionContext, type AzureWizardExecuteStep, type AzureWizardPromptStep, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { type ContainerAppItem } from "../../../tree/ContainerAppItem"; import { createActivityContext } from "../../../utils/activity/activityUtils"; +import { getManagedEnvironmentFromContainerApp } from "../../../utils/getResourceUtils"; import { getVerifyProvidersStep } from "../../../utils/getVerifyProvidersStep"; import { localize } from "../../../utils/localize"; import { ContainerAppOverwriteConfirmStep } from "../../ContainerAppOverwriteConfirmStep"; @@ -18,13 +19,15 @@ import { type DeployImageApiContext } from "./deployImageApi"; export async function deployImage(context: IActionContext & Partial, node: ContainerAppItem): Promise { const { subscription, containerApp } = node; + const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription); const wizardContext: DeployImageApiContext = { ...context, - ...createSubscriptionContext(subscription), + ...subscriptionContext, ...await createActivityContext(), subscription, - containerApp + managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp), + containerApp, }; wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode; diff --git a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts index ac3f76f37..d2abb63ed 100644 --- a/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts +++ b/src/commands/image/imageSource/containerRegistry/acr/AcrListStep.ts @@ -12,7 +12,7 @@ import { createContainerRegistryManagementClient } from "../../../../../utils/az import { parseImageName } from "../../../../../utils/imageNameUtils"; import { localize } from "../../../../../utils/localize"; import { type CreateContainerAppBaseContext } from "../../../../createContainerApp/CreateContainerAppContext"; -import { type IManagedEnvironmentContext } from "../../../../createManagedEnvironment/IManagedEnvironmentContext"; +import { type CreateManagedEnvironmentContext } from "../../../../createManagedEnvironment/CreateManagedEnvironmentContext"; import { type ContainerRegistryImageSourceContext } from "../ContainerRegistryImageSourceContext"; import { getLatestContainerAppImage } from "../getLatestContainerImage"; import { type CreateAcrContext } from "./createAcr/CreateAcrContext"; @@ -126,7 +126,7 @@ async function tryConfigureResourceGroupForRegistry( promptSteps: AzureWizardPromptStep[], ): Promise { // No need to pollute the base context with all the potential pre-create typings as they are not otherwise used - const resourceCreationContext = context as Partial & Partial & CreateAcrContext; + const resourceCreationContext = context as Partial & Partial & CreateAcrContext; if (resourceCreationContext.resourceGroup || resourceCreationContext.newResourceGroupName) { return; } diff --git a/src/commands/image/updateImage/updateImage.ts b/src/commands/image/updateImage/updateImage.ts index 1b802284f..ae1c5d622 100644 --- a/src/commands/image/updateImage/updateImage.ts +++ b/src/commands/image/updateImage/updateImage.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KnownActiveRevisionsMode, type Revision } from "@azure/arm-appcontainers"; -import { AzureWizard, createSubscriptionContext, type AzureWizardExecuteStep, type AzureWizardPromptStep, type ExecuteActivityContext, type IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, createSubscriptionContext, type AzureWizardExecuteStep, type AzureWizardPromptStep, type ExecuteActivityContext, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { ext } from "../../../extensionVariables"; import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps"; import { type UpdateImageTelemetryProps as TelemetryProps } from "../../../telemetry/commandTelemetryProps"; @@ -12,6 +12,7 @@ import { type ContainerAppItem, type ContainerAppModel } from "../../../tree/Con import { type RevisionDraftItem } from "../../../tree/revisionManagement/RevisionDraftItem"; import { type RevisionItem } from "../../../tree/revisionManagement/RevisionItem"; import { createActivityContext } from "../../../utils/activity/activityUtils"; +import { getManagedEnvironmentFromContainerApp } from "../../../utils/getResourceUtils"; import { getVerifyProvidersStep } from "../../../utils/getVerifyProvidersStep"; import { localize } from "../../../utils/localize"; import { pickContainerApp } from "../../../utils/pickItem/pickContainerApp"; @@ -45,12 +46,14 @@ export async function updateImage(context: IActionContext, node?: ContainerAppIt } const { subscription, containerApp } = item; + const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription); const wizardContext: UpdateImageContext = { ...context, - ...createSubscriptionContext(subscription), + ...subscriptionContext, ...await createActivityContext(), subscription, + managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp), containerApp }; diff --git a/src/commands/registryCredentials/RegistryCredentialsContext.ts b/src/commands/registryCredentials/RegistryCredentialsContext.ts index 433fcee3b..2d276247e 100644 --- a/src/commands/registryCredentials/RegistryCredentialsContext.ts +++ b/src/commands/registryCredentials/RegistryCredentialsContext.ts @@ -6,9 +6,10 @@ import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils"; import { type DockerLoginRegistryCredentialsContext } from "./dockerLogin/DockerLoginRegistryCredentialsContext"; +import { type ManagedIdentityRegistryCredentialsContext } from "./identity/ManagedIdentityRegistryCredentialsContext"; import { type RegistryCredentialType } from "./RegistryCredentialsAddConfigurationListStep"; -export type CredentialTypeContext = DockerLoginRegistryCredentialsContext /** & ManagedIdentityRegistryCredentialsContext */; +export type CredentialTypeContext = DockerLoginRegistryCredentialsContext & ManagedIdentityRegistryCredentialsContext; export interface RegistryCredentialsContext extends CredentialTypeContext, ExecuteActivityContext { newRegistryCredentialType?: RegistryCredentialType; diff --git a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts new file mode 100644 index 000000000..72ddf5adc --- /dev/null +++ b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type RegistryCredentials } from "@azure/arm-appcontainers"; +import { type IContainerAppContext } from "../../IContainerAppContext"; +import { type ManagedEnvironmentContext } from "../../ManagedEnvironmentContext"; +import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; + +export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentContext, IContainerAppContext { + newRegistryCredential?: RegistryCredentials; +} diff --git a/src/utils/getResourceUtils.ts b/src/utils/getResourceUtils.ts new file mode 100644 index 000000000..0018ab4d2 --- /dev/null +++ b/src/utils/getResourceUtils.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type ContainerAppsAPIClient, type ManagedEnvironment } from "@azure/arm-appcontainers"; +import { uiUtils } from "@microsoft/vscode-azext-azureutils"; +import { nonNullValue, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; +import { type ContainerAppModel } from "../tree/ContainerAppItem"; +import { createContainerAppsAPIClient } from "./azureClients"; + +export async function getManagedEnvironmentFromContainerApp(context: ISubscriptionActionContext, containerApp: ContainerAppModel): Promise { + const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context); + const managedEnvironments: ManagedEnvironment[] = await uiUtils.listAllIterator(client.managedEnvironments.listBySubscription()); + return nonNullValue(managedEnvironments.find(m => m.id === containerApp.managedEnvironmentId)); +} From 011cc6b0d244f497ad00a53e66274c96334001ee Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:01:44 -0700 Subject: [PATCH 09/19] Add Required context --- src/commands/ManagedEnvironmentContext.ts | 4 ++-- src/commands/createContainerApp/CreateContainerAppContext.ts | 2 -- .../identity/ManagedIdentityRegistryCredentialsContext.ts | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/commands/ManagedEnvironmentContext.ts b/src/commands/ManagedEnvironmentContext.ts index dfeae1526..2cf450ae6 100644 --- a/src/commands/ManagedEnvironmentContext.ts +++ b/src/commands/ManagedEnvironmentContext.ts @@ -7,7 +7,7 @@ import { type ManagedEnvironment } from "@azure/arm-appcontainers"; import { type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -export interface ManagedEnvironmentContext extends ISubscriptionActionContext { +export interface RequiredManagedEnvironmentContext extends ISubscriptionActionContext { subscription: AzureSubscription; - managedEnvironment?: ManagedEnvironment; + managedEnvironment: ManagedEnvironment; } diff --git a/src/commands/createContainerApp/CreateContainerAppContext.ts b/src/commands/createContainerApp/CreateContainerAppContext.ts index b4e1e0fd2..2bbbce593 100644 --- a/src/commands/createContainerApp/CreateContainerAppContext.ts +++ b/src/commands/createContainerApp/CreateContainerAppContext.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type ManagedEnvironment } from '@azure/arm-appcontainers'; import { type IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils'; import { type ExecuteActivityContext } from '@microsoft/vscode-azext-utils'; import { type SetTelemetryProps } from '../../telemetry/SetTelemetryProps'; @@ -14,7 +13,6 @@ import { type IngressBaseContext } from '../ingress/IngressContext'; export interface CreateContainerAppBaseContext extends IResourceGroupWizardContext, ImageSourceBaseContext, IngressBaseContext, IContainerAppContext, ExecuteActivityContext { newContainerAppName?: string; - managedEnvironment?: ManagedEnvironment; } export type CreateContainerAppContext = CreateContainerAppBaseContext & SetTelemetryProps; diff --git a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts index 72ddf5adc..836fd8d7d 100644 --- a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts +++ b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts @@ -5,9 +5,9 @@ import { type RegistryCredentials } from "@azure/arm-appcontainers"; import { type IContainerAppContext } from "../../IContainerAppContext"; -import { type ManagedEnvironmentContext } from "../../ManagedEnvironmentContext"; import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; +import { type RequiredManagedEnvironmentContext } from "../../ManagedEnvironmentContext"; -export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentContext, IContainerAppContext { +export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, RequiredManagedEnvironmentContext, IContainerAppContext { newRegistryCredential?: RegistryCredentials; } From 7df970260dce0f2ce4247afd0c9844794e805967 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:57:48 -0700 Subject: [PATCH 10/19] PR feedback --- src/commands/ManagedEnvironmentContext.ts | 2 +- .../identity/ManagedIdentityRegistryCredentialsContext.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/ManagedEnvironmentContext.ts b/src/commands/ManagedEnvironmentContext.ts index 2cf450ae6..7214cb7c5 100644 --- a/src/commands/ManagedEnvironmentContext.ts +++ b/src/commands/ManagedEnvironmentContext.ts @@ -7,7 +7,7 @@ import { type ManagedEnvironment } from "@azure/arm-appcontainers"; import { type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -export interface RequiredManagedEnvironmentContext extends ISubscriptionActionContext { +export interface ManagedEnvironmentRequiredContext extends ISubscriptionActionContext { subscription: AzureSubscription; managedEnvironment: ManagedEnvironment; } diff --git a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts index 836fd8d7d..376e7e352 100644 --- a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts +++ b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts @@ -6,8 +6,8 @@ import { type RegistryCredentials } from "@azure/arm-appcontainers"; import { type IContainerAppContext } from "../../IContainerAppContext"; import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; -import { type RequiredManagedEnvironmentContext } from "../../ManagedEnvironmentContext"; +import { type ManagedEnvironmentRequiredContext } from "../../ManagedEnvironmentContext"; -export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, RequiredManagedEnvironmentContext, IContainerAppContext { +export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentRequiredContext, IContainerAppContext { newRegistryCredential?: RegistryCredentials; } From d93d25b06c767eb984624e2bcd6a93d25eca67c5 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:56:50 -0700 Subject: [PATCH 11/19] Feedback to rename contexts --- ...nerAppContext.ts => ContainerAppCreateContext.ts} | 4 ++-- .../createContainerApp/ContainerAppCreateStep.ts | 12 ++++++------ .../createContainerApp/ContainerAppNameStep.ts | 10 +++++----- .../createContainerApp/createContainerApp.ts | 12 ++++++------ .../createContainerApp/setQuickStartImage.ts | 4 ++-- .../LogAnalyticsCreateStep.ts | 12 ++++++------ .../createManagedEnvironment/LogAnalyticsListStep.ts | 10 +++++----- ...Context.ts => ManagedEnvironmentCreateContext.ts} | 2 +- .../ManagedEnvironmentCreateStep.ts | 12 ++++++------ .../ManagedEnvironmentNameStep.ts | 10 +++++----- .../createManagedEnvironment.ts | 12 ++++++------ .../DeployWorkspaceProjectInternalContext.ts | 8 ++++---- .../imageSource/buildImageInAzure/ImageNameStep.ts | 4 ++-- .../imageSource/containerRegistry/acr/AcrListStep.ts | 6 +++--- 14 files changed, 59 insertions(+), 59 deletions(-) rename src/commands/createContainerApp/{CreateContainerAppContext.ts => ContainerAppCreateContext.ts} (89%) rename src/commands/createManagedEnvironment/{CreateManagedEnvironmentContext.ts => ManagedEnvironmentCreateContext.ts} (94%) diff --git a/src/commands/createContainerApp/CreateContainerAppContext.ts b/src/commands/createContainerApp/ContainerAppCreateContext.ts similarity index 89% rename from src/commands/createContainerApp/CreateContainerAppContext.ts rename to src/commands/createContainerApp/ContainerAppCreateContext.ts index 2bbbce593..d87f60ac0 100644 --- a/src/commands/createContainerApp/CreateContainerAppContext.ts +++ b/src/commands/createContainerApp/ContainerAppCreateContext.ts @@ -11,8 +11,8 @@ import { type IContainerAppContext } from '../IContainerAppContext'; import { type ImageSourceBaseContext } from '../image/imageSource/ImageSourceContext'; import { type IngressBaseContext } from '../ingress/IngressContext'; -export interface CreateContainerAppBaseContext extends IResourceGroupWizardContext, ImageSourceBaseContext, IngressBaseContext, IContainerAppContext, ExecuteActivityContext { +export interface ContainerAppCreateBaseContext extends IResourceGroupWizardContext, ImageSourceBaseContext, IngressBaseContext, IContainerAppContext, ExecuteActivityContext { newContainerAppName?: string; } -export type CreateContainerAppContext = CreateContainerAppBaseContext & SetTelemetryProps; +export type ContainerAppCreateContext = ContainerAppCreateBaseContext & SetTelemetryProps; diff --git a/src/commands/createContainerApp/ContainerAppCreateStep.ts b/src/commands/createContainerApp/ContainerAppCreateStep.ts index c13b700f1..78df494ed 100644 --- a/src/commands/createContainerApp/ContainerAppCreateStep.ts +++ b/src/commands/createContainerApp/ContainerAppCreateStep.ts @@ -12,12 +12,12 @@ import { ContainerAppItem } from "../../tree/ContainerAppItem"; import { createContainerAppsAPIClient } from "../../utils/azureClients"; import { localize } from "../../utils/localize"; import { getContainerNameForImage } from "../image/imageSource/containerRegistry/getContainerNameForImage"; -import { type CreateContainerAppContext } from "./CreateContainerAppContext"; +import { type ContainerAppCreateContext } from "./ContainerAppCreateContext"; -export class ContainerAppCreateStep extends AzureWizardExecuteStep { +export class ContainerAppCreateStep extends AzureWizardExecuteStep { public priority: number = 620; - public async execute(context: CreateContainerAppContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + public async execute(context: ContainerAppCreateContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const appClient: ContainerAppsAPIClient = await createContainerAppsAPIClient(context); const resourceGroupName: string = nonNullValueAndProp(context.resourceGroup, 'name'); @@ -60,11 +60,11 @@ export class ContainerAppCreateStep extends AzureWizardExecuteStep { +export class ContainerAppNameStep extends AzureWizardPromptStep { public hideStepCount: boolean = true; - public async prompt(context: CreateContainerAppContext): Promise { + public async prompt(context: ContainerAppCreateContext): Promise { const prompt: string = localize('containerAppNamePrompt', 'Enter a container app name.'); context.newContainerAppName = (await context.ui.showInputBox({ prompt, @@ -24,7 +24,7 @@ export class ContainerAppNameStep extends AzureWizardPromptStep { + private async validateNameAvailable(context: ContainerAppCreateContext, name: string): Promise { const resourceGroupName: string = getResourceGroupFromId(nonNullValueAndProp(context.managedEnvironment, 'id')); if (!await ContainerAppNameStep.isNameAvailable(context, resourceGroupName, name)) { return localize('containerAppExists', 'The container app "{0}" already exists in resource group "{1}".', name, resourceGroupName); diff --git a/src/commands/createContainerApp/createContainerApp.ts b/src/commands/createContainerApp/createContainerApp.ts index 51c6ab166..0a1e3627e 100644 --- a/src/commands/createContainerApp/createContainerApp.ts +++ b/src/commands/createContainerApp/createContainerApp.ts @@ -16,9 +16,9 @@ import { localize } from "../../utils/localize"; import { pickEnvironment } from "../../utils/pickItem/pickEnvironment"; import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep"; import { IngressPromptStep } from "../ingress/IngressPromptStep"; +import { type ContainerAppCreateContext } from "./ContainerAppCreateContext"; import { ContainerAppCreateStep } from "./ContainerAppCreateStep"; import { ContainerAppNameStep } from "./ContainerAppNameStep"; -import { type CreateContainerAppContext } from "./CreateContainerAppContext"; import { showContainerAppNotification } from "./showContainerAppNotification"; export async function createContainerApp(context: IActionContext, node?: ManagedEnvironmentItem): Promise { @@ -29,7 +29,7 @@ export async function createContainerApp(context: IActionContext, node?: Managed node ??= await pickEnvironment(context); - const wizardContext: CreateContainerAppContext = { + const wizardContext: ContainerAppCreateContext = { ...context, ...createSubscriptionContext(node.subscription), ...await createActivityContext(), @@ -40,14 +40,14 @@ export async function createContainerApp(context: IActionContext, node?: Managed const title: string = localize('createContainerApp', 'Create container app'); - const promptSteps: AzureWizardPromptStep[] = [ + const promptSteps: AzureWizardPromptStep[] = [ new ContainerAppNameStep(), new ImageSourceListStep(), new IngressPromptStep(), ]; - const executeSteps: AzureWizardExecuteStep[] = [ - getVerifyProvidersStep(), + const executeSteps: AzureWizardExecuteStep[] = [ + getVerifyProvidersStep(), new ContainerAppCreateStep(), ]; @@ -62,7 +62,7 @@ export async function createContainerApp(context: IActionContext, node?: Managed await LocationListStep.setLocation(wizardContext, nonNullProp(node.resource, 'location')); - const wizard: AzureWizard = new AzureWizard(wizardContext, { + const wizard: AzureWizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps, diff --git a/src/commands/createContainerApp/setQuickStartImage.ts b/src/commands/createContainerApp/setQuickStartImage.ts index 962fb153a..c64755643 100644 --- a/src/commands/createContainerApp/setQuickStartImage.ts +++ b/src/commands/createContainerApp/setQuickStartImage.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { quickStartImageName } from "../../constants"; -import { type CreateContainerAppContext } from "./CreateContainerAppContext"; +import { type ContainerAppCreateContext } from "./ContainerAppCreateContext"; -export function setQuickStartImage(context: Partial): void { +export function setQuickStartImage(context: Partial): void { context.image = quickStartImageName; context.enableIngress = true; context.enableExternal = true; diff --git a/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts b/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts index 99a058c90..1eb8fcb71 100644 --- a/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts +++ b/src/commands/createManagedEnvironment/LogAnalyticsCreateStep.ts @@ -9,12 +9,12 @@ import { type Progress } from "vscode"; import { createOperationalInsightsManagementClient } from "../../utils/azureClients"; import { localize } from "../../utils/localize"; import { nonNullProp } from "../../utils/nonNull"; -import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; +import { type ManagedEnvironmentCreateContext } from "./ManagedEnvironmentCreateContext"; -export class LogAnalyticsCreateStep extends AzureWizardExecuteStep { +export class LogAnalyticsCreateStep extends AzureWizardExecuteStep { public priority: number = 220; - public async execute(context: CreateManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + public async execute(context: ManagedEnvironmentCreateContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const opClient = await createOperationalInsightsManagementClient(context); const resourceGroup = nonNullProp(context, 'resourceGroup'); const workspaceName = context.newLogAnalyticsWorkspaceName || nonNullProp(context, 'newManagedEnvironmentName'); @@ -26,11 +26,11 @@ export class LogAnalyticsCreateStep extends AzureWizardExecuteStep { - public async prompt(context: CreateManagedEnvironmentContext): Promise { +export class LogAnalyticsListStep extends AzureWizardPromptStep { + public async prompt(context: ManagedEnvironmentCreateContext): Promise { const placeHolder: string = localize('selectLogAnalytics', 'Select Log Analytics workspace. Your Log Analytics workspace will contain all your application logs.'); context.logAnalyticsWorkspace = (await context.ui.showQuickPick(this.getQuickPicks(context), { placeHolder })).data; } - public shouldPrompt(context: CreateManagedEnvironmentContext): boolean { + public shouldPrompt(context: ManagedEnvironmentCreateContext): boolean { return !context.logAnalyticsWorkspace; } - private async getQuickPicks(context: CreateManagedEnvironmentContext): Promise[]> { + private async getQuickPicks(context: ManagedEnvironmentCreateContext): Promise[]> { const picks: IAzureQuickPickItem[] = []; picks.push({ diff --git a/src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateContext.ts similarity index 94% rename from src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts rename to src/commands/createManagedEnvironment/ManagedEnvironmentCreateContext.ts index a1fea9097..434517251 100644 --- a/src/commands/createManagedEnvironment/CreateManagedEnvironmentContext.ts +++ b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateContext.ts @@ -9,7 +9,7 @@ import { type IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureu import { type ExecuteActivityContext, type ISubscriptionActionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; -export interface CreateManagedEnvironmentContext extends ISubscriptionActionContext, IResourceGroupWizardContext, ExecuteActivityContext { +export interface ManagedEnvironmentCreateContext extends ISubscriptionActionContext, IResourceGroupWizardContext, ExecuteActivityContext { subscription: AzureSubscription; newManagedEnvironmentName?: string; diff --git a/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts index e0306a10a..0a81ab345 100644 --- a/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts +++ b/src/commands/createManagedEnvironment/ManagedEnvironmentCreateStep.ts @@ -11,12 +11,12 @@ import { managedEnvironmentsAppProvider } from "../../constants"; import { createContainerAppsAPIClient, createOperationalInsightsManagementClient } from '../../utils/azureClients'; import { localize } from "../../utils/localize"; import { nonNullProp, nonNullValueAndProp } from "../../utils/nonNull"; -import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; +import { type ManagedEnvironmentCreateContext } from "./ManagedEnvironmentCreateContext"; -export class ManagedEnvironmentCreateStep extends AzureWizardExecuteStep { +export class ManagedEnvironmentCreateStep extends AzureWizardExecuteStep { public priority: number = 250; - public async execute(context: CreateManagedEnvironmentContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + public async execute(context: ManagedEnvironmentCreateContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context); const opClient = await createOperationalInsightsManagementClient(context); @@ -59,11 +59,11 @@ export class ManagedEnvironmentCreateStep extends AzureWizardExecuteStep { - public async prompt(context: CreateManagedEnvironmentContext): Promise { +export class ManagedEnvironmentNameStep extends AzureWizardPromptStep { + public async prompt(context: ManagedEnvironmentCreateContext): Promise { const prompt: string = localize('containerAppNamePrompt', 'Enter a container apps environment name.'); context.newManagedEnvironmentName = (await context.ui.showInputBox({ prompt, @@ -21,7 +21,7 @@ export class ManagedEnvironmentNameStep extends AzureWizardPromptStep { + private async validateNameAvailable(context: ManagedEnvironmentCreateContext, name: string): Promise { if (!context.resourceGroup) { // If a new resource group will house the managed environment, we can skip the name check return undefined; diff --git a/src/commands/createManagedEnvironment/createManagedEnvironment.ts b/src/commands/createManagedEnvironment/createManagedEnvironment.ts index e1a700216..9e15f12b1 100644 --- a/src/commands/createManagedEnvironment/createManagedEnvironment.ts +++ b/src/commands/createManagedEnvironment/createManagedEnvironment.ts @@ -11,15 +11,15 @@ import { ext } from "../../extensionVariables"; import { createActivityContext } from "../../utils/activityUtils"; import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep"; import { localize } from "../../utils/localize"; -import { type CreateManagedEnvironmentContext } from "./CreateManagedEnvironmentContext"; import { LogAnalyticsCreateStep } from "./LogAnalyticsCreateStep"; +import { type ManagedEnvironmentCreateContext } from "./ManagedEnvironmentCreateContext"; import { ManagedEnvironmentCreateStep } from "./ManagedEnvironmentCreateStep"; import { ManagedEnvironmentNameStep } from "./ManagedEnvironmentNameStep"; export async function createManagedEnvironment(context: IActionContext, node?: { subscription: AzureSubscription }): Promise { const subscription = node?.subscription ?? await subscriptionExperience(context, ext.rgApiV2.resources.azureResourceTreeDataProvider); - const wizardContext: CreateManagedEnvironmentContext = { + const wizardContext: ManagedEnvironmentCreateContext = { ...context, ...createSubscriptionContext(subscription), ...await createActivityContext(), @@ -27,12 +27,12 @@ export async function createManagedEnvironment(context: IActionContext, node?: { }; const title: string = localize('createManagedEnv', 'Create container apps environment'); - const promptSteps: AzureWizardPromptStep[] = []; - const executeSteps: AzureWizardExecuteStep[] = []; + const promptSteps: AzureWizardPromptStep[] = []; + const executeSteps: AzureWizardExecuteStep[] = []; promptSteps.push(new ManagedEnvironmentNameStep()); executeSteps.push( - getVerifyProvidersStep(), + getVerifyProvidersStep(), new ResourceGroupCreateStep(), new LogAnalyticsCreateStep(), new ManagedEnvironmentCreateStep() @@ -40,7 +40,7 @@ export async function createManagedEnvironment(context: IActionContext, node?: { LocationListStep.addProviderForFiltering(wizardContext, appProvider, managedEnvironmentsId); LocationListStep.addStep(wizardContext, promptSteps); - const wizard: AzureWizard = new AzureWizard(wizardContext, { + const wizard: AzureWizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps, diff --git a/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts b/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts index cbea6c3fd..6bd356ee8 100644 --- a/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts +++ b/src/commands/deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext.ts @@ -7,8 +7,8 @@ import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils"; import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps"; import { type DeployWorkspaceProjectInternalTelemetryProps as TelemetryProps } from "../../../telemetry/deployWorkspaceProjectTelemetryProps"; import { type IContainerAppContext } from "../../IContainerAppContext"; -import { type CreateContainerAppBaseContext } from "../../createContainerApp/CreateContainerAppContext"; -import { type CreateManagedEnvironmentContext } from "../../createManagedEnvironment/CreateManagedEnvironmentContext"; +import { type ContainerAppCreateBaseContext } from "../../createContainerApp/ContainerAppCreateContext"; +import { type ManagedEnvironmentCreateContext } from "../../createManagedEnvironment/ManagedEnvironmentCreateContext"; import { type BuildImageInAzureImageSourceBaseContext } from "../../image/imageSource/buildImageInAzure/BuildImageInAzureImageSourceContext"; import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; import { type DeploymentConfiguration } from "../deploymentConfiguration/DeploymentConfiguration"; @@ -16,8 +16,8 @@ import { type DeploymentConfiguration } from "../deploymentConfiguration/Deploym // Use intersection typing instead of an interface here to bypass some minor (relatively trivial) type mismatch issues introduced by having to use the 'Partial' utility export type DeployWorkspaceProjectInternalBaseContext = IContainerAppContext & - Partial & - Partial & + Partial & + Partial & Partial & Partial & Pick & diff --git a/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts b/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts index 4b6d6371e..a7d54c8a9 100644 --- a/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts @@ -6,7 +6,7 @@ import { AzureWizardPromptStep, nonNullProp } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../utils/localize"; import { validateUtils } from "../../../../utils/validateUtils"; -import { type CreateContainerAppContext } from "../../../createContainerApp/CreateContainerAppContext"; +import { type ContainerAppCreateContext } from "../../../createContainerApp/ContainerAppCreateContext"; import { type BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; const maxImageNameLength: number = 46; @@ -16,7 +16,7 @@ export class ImageNameStep extends AzureWizardPromptStep[], ): Promise { // No need to pollute the base context with all the potential pre-create typings as they are not otherwise used - const resourceCreationContext = context as Partial & Partial & CreateAcrContext; + const resourceCreationContext = context as Partial & Partial & CreateAcrContext; if (resourceCreationContext.resourceGroup || resourceCreationContext.newResourceGroupName) { return; } From ad4745d3130e2d07c172611472b64817c859f48d Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:29:10 -0700 Subject: [PATCH 12/19] Add/update dependencies --- package-lock.json | 187 +++++++++++++++++++++++++++++++++++++++------- package.json | 3 +- 2 files changed, 163 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42b5a62c9..e1887a32a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.7.2-alpha.0", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@azure/arm-appcontainers": "^2.0.0", + "@azure/arm-appcontainers": "^2.1.0-beta.1", + "@azure/arm-authorization": "^9.0.0", "@azure/arm-containerregistry": "^10.0.0", "@azure/arm-operationalinsights": "^8.0.0", "@azure/arm-resources": "^5.2.0", @@ -81,26 +82,101 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/@azure/arm-appcontainers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-appcontainers/-/arm-appcontainers-2.0.0.tgz", - "integrity": "sha512-o7ICeuEiovGht8CsO/xECP4C1P8LNeCYzhcYAhx1nr/AQWrF9mTPFT/sZV8W/rFDkjJSMq9JbDm/riSRRGt6rA==", + "version": "2.1.0-beta.1", + "resolved": "https://registry.npmjs.org/@azure/arm-appcontainers/-/arm-appcontainers-2.1.0-beta.1.tgz", + "integrity": "sha512-Okd8lPy0ubz2SBpDEeFdE2uOCQNhOqiYFeqhP5u2IKTQD/lv3K80mlMQ/WIk7xfUbQ3SmMhLMpxQBmyVMnFWPA==", "dependencies": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", + "@azure/core-auth": "^1.6.0", "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.3", + "@azure/core-lro": "^2.5.4", "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-rest-pipeline": "^1.14.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/@azure/core-rest-pipeline": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz", + "integrity": "sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.9.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/@azure/core-tracing": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", + "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@azure/arm-appcontainers/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@azure/arm-appcontainers/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/@azure/arm-authorization": { "version": "9.0.0", @@ -417,9 +493,9 @@ "integrity": "sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==" }, "node_modules/@azure/core-auth": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz", - "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.8.0.tgz", + "integrity": "sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g==", "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.1.0", @@ -9362,23 +9438,82 @@ } }, "@azure/arm-appcontainers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@azure/arm-appcontainers/-/arm-appcontainers-2.0.0.tgz", - "integrity": "sha512-o7ICeuEiovGht8CsO/xECP4C1P8LNeCYzhcYAhx1nr/AQWrF9mTPFT/sZV8W/rFDkjJSMq9JbDm/riSRRGt6rA==", + "version": "2.1.0-beta.1", + "resolved": "https://registry.npmjs.org/@azure/arm-appcontainers/-/arm-appcontainers-2.1.0-beta.1.tgz", + "integrity": "sha512-Okd8lPy0ubz2SBpDEeFdE2uOCQNhOqiYFeqhP5u2IKTQD/lv3K80mlMQ/WIk7xfUbQ3SmMhLMpxQBmyVMnFWPA==", "requires": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", + "@azure/core-auth": "^1.6.0", "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.3", + "@azure/core-lro": "^2.5.4", "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-rest-pipeline": "^1.14.0", "tslib": "^2.2.0" }, "dependencies": { + "@azure/core-rest-pipeline": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz", + "integrity": "sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA==", + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.9.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "@azure/core-tracing": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", + "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" } } }, @@ -9670,9 +9805,9 @@ "integrity": "sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==" }, "@azure/core-auth": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz", - "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.8.0.tgz", + "integrity": "sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g==", "requires": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.1.0", diff --git a/package.json b/package.json index 441f4476e..52d6fd151 100644 --- a/package.json +++ b/package.json @@ -778,7 +778,8 @@ "webpack-cli": "^4.6.0" }, "dependencies": { - "@azure/arm-appcontainers": "^2.0.0", + "@azure/arm-appcontainers": "^2.1.0-beta.1", + "@azure/arm-authorization": "^9.0.0", "@azure/arm-containerregistry": "^10.0.0", "@azure/arm-operationalinsights": "^8.0.0", "@azure/arm-resources": "^5.2.0", From ce1794d77de0e890415f4147629c3bbd1889728e Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:45:23 -0700 Subject: [PATCH 13/19] First pass core implementation --- src/commands/EXECUTE_PRIORITY.md | 4 +- .../getDeployWorkspaceProjectResults.ts | 4 +- .../image/deployImageApi/deployImage.ts | 2 - .../imageSource/ContainerAppUpdateStep.ts | 2 +- .../acr/RegistryEnableAdminUserStep.ts | 34 -------- .../acr/listCredentialsFromRegistry.ts | 20 ----- .../getRegistryCredentialsAndSecrets.ts | 66 --------------- ...stryCredentialsAddConfigurationListStep.ts | 29 ++++--- .../identity/AcrPullEnableStep.ts | 82 +++++++++++++++++++ .../ManagedEnvironmentIdentityEnableStep.ts | 58 +++++++++++++ ...yRegistryCredentialAddConfigurationStep.ts | 35 ++++++++ src/utils/azureClients.ts | 5 ++ 12 files changed, 202 insertions(+), 139 deletions(-) delete mode 100644 src/commands/image/imageSource/containerRegistry/acr/RegistryEnableAdminUserStep.ts delete mode 100644 src/commands/image/imageSource/containerRegistry/acr/listCredentialsFromRegistry.ts delete mode 100644 src/commands/image/imageSource/containerRegistry/getRegistryCredentialsAndSecrets.ts create mode 100644 src/commands/registryCredentials/identity/AcrPullEnableStep.ts create mode 100644 src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts create mode 100644 src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialAddConfigurationStep.ts diff --git a/src/commands/EXECUTE_PRIORITY.md b/src/commands/EXECUTE_PRIORITY.md index 2b349989b..7f6e10155 100644 --- a/src/commands/EXECUTE_PRIORITY.md +++ b/src/commands/EXECUTE_PRIORITY.md @@ -28,7 +28,9 @@ When creating or updating resources, execute steps should occupy certain priorit #### Steps ##### Managed Identity Registry Credential -- Coming soon... +- ManagedEnvironmentIdentityEnableStep: 450 +- AcrPullEnableStep: 460 +- ManagedIdentityRegistryCredentialAddConfigurationStep: 470 ##### Admin User Registry Credential - AcrEnableAdminUserStep: 450 diff --git a/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts b/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts index 00195e9bf..268a58036 100644 --- a/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts +++ b/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts @@ -8,14 +8,14 @@ import { type Workspace } from "@azure/arm-operationalinsights"; import { uiUtils } from "@microsoft/vscode-azext-azureutils"; import { createOperationalInsightsManagementClient } from "../../utils/azureClients"; import type * as api from "../api/vscode-azurecontainerapps.api"; -import { listCredentialsFromRegistry } from "../image/imageSource/containerRegistry/acr/listCredentialsFromRegistry"; +import { listCredentialsFromAcr } from "../registryCredentials/dockerLogin/listCredentialsFromAcr"; import { type DeployWorkspaceProjectContext } from "./DeployWorkspaceProjectContext"; export type DeployWorkspaceProjectResults = api.DeployWorkspaceProjectResults; export async function getDeployWorkspaceProjectResults(context: DeployWorkspaceProjectContext): Promise { const registryCredentials: { username: string, password: RegistryPassword } | undefined = context.registry ? - await listCredentialsFromRegistry(context, context.registry) : undefined; + await listCredentialsFromAcr(context) : undefined; context.logAnalyticsWorkspace ??= await tryGetLogAnalyticsWorkspace(context); diff --git a/src/commands/image/deployImageApi/deployImage.ts b/src/commands/image/deployImageApi/deployImage.ts index b684c5f16..33432a520 100644 --- a/src/commands/image/deployImageApi/deployImage.ts +++ b/src/commands/image/deployImageApi/deployImage.ts @@ -14,7 +14,6 @@ import { showContainerAppNotification } from "../../createContainerApp/showConta import { ContainerAppUpdateStep } from "../imageSource/ContainerAppUpdateStep"; import { ImageSourceListStep } from "../imageSource/ImageSourceListStep"; import { type ContainerRegistryImageSourceContext } from "../imageSource/containerRegistry/ContainerRegistryImageSourceContext"; -import { RegistryEnableAdminUserStep } from "../imageSource/containerRegistry/acr/RegistryEnableAdminUserStep"; import { type DeployImageApiContext } from "./deployImageApi"; export async function deployImage(context: IActionContext & Partial, node: ContainerAppItem): Promise { @@ -33,7 +32,6 @@ export async function deployImage(context: IActionContext & Partial[] = [ - new RegistryEnableAdminUserStep(), new ImageSourceListStep(), new ContainerAppOverwriteConfirmStep(), ]; diff --git a/src/commands/image/imageSource/ContainerAppUpdateStep.ts b/src/commands/image/imageSource/ContainerAppUpdateStep.ts index 911b4ffb2..32b240cbd 100644 --- a/src/commands/image/imageSource/ContainerAppUpdateStep.ts +++ b/src/commands/image/imageSource/ContainerAppUpdateStep.ts @@ -13,7 +13,7 @@ import { type ImageSourceContext } from "./ImageSourceContext"; import { getContainerNameForImage } from "./containerRegistry/getContainerNameForImage"; export class ContainerAppUpdateStep extends AzureWizardExecuteStep { - public priority: number = 650; + public priority: number = 680; public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp'); diff --git a/src/commands/image/imageSource/containerRegistry/acr/RegistryEnableAdminUserStep.ts b/src/commands/image/imageSource/containerRegistry/acr/RegistryEnableAdminUserStep.ts deleted file mode 100644 index fb926d666..000000000 --- a/src/commands/image/imageSource/containerRegistry/acr/RegistryEnableAdminUserStep.ts +++ /dev/null @@ -1,34 +0,0 @@ - - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; -import { AzureWizardPromptStep, nonNullProp, nonNullValue } from "@microsoft/vscode-azext-utils"; -import { createContainerRegistryManagementClient } from "../../../../../utils/azureClients"; -import { localize } from "../../../../../utils/localize"; -import { type ContainerRegistryImageSourceContext } from "../ContainerRegistryImageSourceContext"; - -export class RegistryEnableAdminUserStep extends AzureWizardPromptStep { - public async prompt(context: ContainerRegistryImageSourceContext): Promise { - const message = localize('enableAdminUser', 'An admin user is required to continue. If enabled, you can use the registry name as username and admin user access key as password to docker login to your container registry.'); - await context.ui.showWarningMessage(message, { modal: true }, { title: localize('enable', 'Enable') }); - - const registry = nonNullValue(context.registry); - registry.adminUserEnabled = true; - - const client: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); - const updatedRegistry = await client.registries.beginUpdateAndWait(getResourceGroupFromId(nonNullProp(registry, 'id')), nonNullProp(registry, 'name'), registry); - - if (!updatedRegistry.adminUserEnabled) { - throw new Error(localize('failedToUpdate', 'Failed to enable admin user for registry "{0}". Go to the portal to manually update.', registry.name)); - } - } - - public shouldPrompt(context: ContainerRegistryImageSourceContext): boolean { - return !!context.registry && !context.registry.adminUserEnabled; - } -} diff --git a/src/commands/image/imageSource/containerRegistry/acr/listCredentialsFromRegistry.ts b/src/commands/image/imageSource/containerRegistry/acr/listCredentialsFromRegistry.ts deleted file mode 100644 index 85f04e748..000000000 --- a/src/commands/image/imageSource/containerRegistry/acr/listCredentialsFromRegistry.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type ContainerRegistryManagementClient, type Registry, type RegistryPassword } from "@azure/arm-containerregistry"; -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; -import { nonNullProp, nonNullValue, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils"; -import { createContainerRegistryManagementClient } from "../../../../../utils/azureClients"; -import { type ContainerRegistryImageSourceContext } from "../ContainerRegistryImageSourceContext"; - -export async function listCredentialsFromRegistry(context: ISubscriptionActionContext & Partial, registry: Registry): - Promise<{ username: string, password: RegistryPassword }> { - - const containerClient: ContainerRegistryManagementClient = await createContainerRegistryManagementClient(context); - const credentials = await containerClient.registries.listCredentials(getResourceGroupFromId(nonNullProp(registry, 'id')), nonNullProp(registry, 'name')); - const password = credentials.passwords?.find(cred => cred.name === 'password' || cred.name === 'password2'); - return { username: nonNullProp(credentials, 'username'), password: nonNullValue(password) }; -} - diff --git a/src/commands/image/imageSource/containerRegistry/getRegistryCredentialsAndSecrets.ts b/src/commands/image/imageSource/containerRegistry/getRegistryCredentialsAndSecrets.ts deleted file mode 100644 index 59a1aad9c..000000000 --- a/src/commands/image/imageSource/containerRegistry/getRegistryCredentialsAndSecrets.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"; -import { nonNullProp } from "@microsoft/vscode-azext-utils"; -import { dockerHubDomain, dockerHubRegistry } from "../../../../constants"; -import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext"; -import { listCredentialsFromRegistry } from "./acr/listCredentialsFromRegistry"; - -interface RegistryCredentialsAndSecrets { - registries?: RegistryCredentials[]; - secrets?: Secret[]; -} - -export async function getAcrCredentialsAndSecrets(context: ContainerRegistryImageSourceContext, containerAppSettings?: RegistryCredentialsAndSecrets): Promise { - const registry = nonNullProp(context, 'registry'); - const { username, password } = await listCredentialsFromRegistry(context, registry); - const passwordName = `${registry.name?.toLocaleLowerCase()}-${password?.name}`; - - // Remove duplicate registries - const registries: RegistryCredentials[] = containerAppSettings?.registries?.filter(r => r.server !== registry.loginServer) ?? []; - registries?.push( - { - identity: '', // The server populates an `undefined` identity as ''. Use the same convention so we can do deep copy comparisons later. - server: registry.loginServer, - username: username, - passwordSecretRef: passwordName - } - ); - - // Remove duplicate secrets - const secrets: Secret[] = containerAppSettings?.secrets?.filter(s => s.name !== passwordName) ?? []; - secrets?.push({ name: passwordName, value: password.value }); - - return { registries, secrets }; -} - -export function getThirdPartyCredentialsAndSecrets(context: ContainerRegistryImageSourceContext, containerAppSettings?: RegistryCredentialsAndSecrets): RegistryCredentialsAndSecrets { - // If 'docker.io', convert to 'index.docker.io', else use registryName as loginServer - const loginServer: string = (context.registryDomain === dockerHubDomain) ? dockerHubRegistry : nonNullProp(context, 'registryName').toLowerCase(); - const passwordSecretRef: string = `${loginServer.replace(/[^a-z0-9-]+/g, '')}-${context.username}`; - - // Remove duplicate registries - const registries: RegistryCredentials[] = containerAppSettings?.registries?.filter(r => r.server !== loginServer) ?? []; - registries?.push( - { - identity: '', // The server populates an `undefined` identity as ''. Use the same convention so we can do deep copy comparisons later. - server: loginServer, - username: context.username, - passwordSecretRef - } - ); - - // Remove duplicate secrets - const secrets: Secret[] = containerAppSettings?.secrets?.filter(s => s.name !== passwordSecretRef) ?? []; - secrets?.push( - { - name: passwordSecretRef, - value: context.secret - } - ); - - return { registries, secrets }; -} diff --git a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts index 649370eba..9e3af178b 100644 --- a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts +++ b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts @@ -10,6 +10,9 @@ import { localize } from "../../utils/localize"; import { AcrEnableAdminUserConfirmStep } from "./dockerLogin/AcrEnableAdminUserConfirmStep"; import { AcrEnableAdminUserStep } from "./dockerLogin/AcrEnableAdminUserStep"; import { DockerLoginRegistryCredentialsAddConfigurationStep } from "./dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep"; +import { AcrPullEnableStep } from "./identity/AcrPullEnableStep"; +import { ManagedEnvironmentIdentityEnableStep } from "./identity/ManagedEnvironmentIdentityEnableStep"; +import { ManagedIdentityRegistryCredentialAddConfigurationStep } from "./identity/ManagedIdentityRegistryCredentialAddConfigurationStep"; import { RegistryCredentialsAndSecretsConfigurationStep } from "./RegistryCredentialsAndSecretsConfigurationStep"; import { type RegistryCredentialsContext } from "./RegistryCredentialsContext"; @@ -56,13 +59,13 @@ export class RegistryCredentialsAddConfigurationListStep extends AzureWizardProm const registryDomain: SupportedRegistries | undefined = this.getRegistryDomain(context); switch (context.newRegistryCredentialType) { - // case RegistryCredentialType.SystemAssigned: - // executeSteps.push( - // new ManagedEnvironmentIdentityEnableStep(), - // new AcrPullEnableStep(), - // new ManagedIdentityRegistryCredentialAddConfigurationStep(registryDomain), - // ); - // break; + case RegistryCredentialType.SystemAssigned: + executeSteps.push( + new ManagedEnvironmentIdentityEnableStep(), + new AcrPullEnableStep(), + new ManagedIdentityRegistryCredentialAddConfigurationStep(registryDomain), + ); + break; case RegistryCredentialType.DockerLogin: promptSteps.push(new AcrEnableAdminUserConfirmStep()); executeSteps.push( @@ -95,12 +98,12 @@ export class RegistryCredentialsAddConfigurationListStep extends AzureWizardProm const registryDomain = this.getRegistryDomain(context); if (registryDomain === acrDomain) { - // picks.push({ - // label: 'Managed Identity', - // description: '(recommended)', - // detail: localize('systemIdentityDetails', 'Setup "{0}" access for container environment resources via a system-assigned identity', 'acrPull'), - // data: RegistryCredentialType.SystemAssigned, - // }); + picks.push({ + label: 'Managed Identity', + description: '(recommended)', + detail: localize('systemIdentityDetails', 'Setup "{0}" access for container environment resources via a system-assigned identity', 'acrPull'), + data: RegistryCredentialType.SystemAssigned, + }); } picks.push({ diff --git a/src/commands/registryCredentials/identity/AcrPullEnableStep.ts b/src/commands/registryCredentials/identity/AcrPullEnableStep.ts new file mode 100644 index 000000000..3a90acc40 --- /dev/null +++ b/src/commands/registryCredentials/identity/AcrPullEnableStep.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KnownPrincipalType, type AuthorizationManagementClient, type RoleAssignment, type RoleAssignmentCreateParameters } from "@azure/arm-authorization"; +import { uiUtils } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizardExecuteStep, GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, nonNullValueAndProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils"; +import * as crypto from "crypto"; +import { type Progress } from "vscode"; +import { createAuthorizationManagementClient } from "../../../utils/azureClients"; +import { localize } from "../../../utils/localize"; +import { type ManagedIdentityRegistryCredentialsContext } from "./ManagedIdentityRegistryCredentialsContext"; + +const acrPullRoleId: string = '7f951dda-4ed3-4680-a7ca-43fe172d538d'; + +export class AcrPullEnableStep extends AzureWizardExecuteStep { + public priority: number = 460; + + // Add a configureBeforeExecute + + public async execute(context: ManagedIdentityRegistryCredentialsContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + const client: AuthorizationManagementClient = await createAuthorizationManagementClient(context); + const registryId: string = nonNullValueAndProp(context.registry, 'id'); + const managedEnvironmentIdentity: string = nonNullValueAndProp(context.managedEnvironment?.identity, 'principalId'); + + if (await this.hasAcrPullAssignment(client, registryId, managedEnvironmentIdentity)) { + return; + } + + const roleCreateParams: RoleAssignmentCreateParameters = { + description: 'acr pull', + roleDefinitionId: `/providers/Microsoft.Authorization/roleDefinitions/${acrPullRoleId}`, + principalId: nonNullValueAndProp(context.managedEnvironment?.identity, 'principalId'), + principalType: KnownPrincipalType.ServicePrincipal, + }; + + progress.report({ message: localize('updatingRegistryCredentials', 'Updating registry credentials...') }); + await client.roleAssignments.create( + nonNullValueAndProp(context.registry, 'id'), + crypto.randomUUID(), + roleCreateParams, + ); + } + + public shouldExecute(context: ManagedIdentityRegistryCredentialsContext): boolean { + return !!context.registry; + } + + private async hasAcrPullAssignment(client: AuthorizationManagementClient, registryId: string, managedEnvironmentIdentity: string): Promise { + const roleAssignments: RoleAssignment[] = await uiUtils.listAllIterator(client.roleAssignments.listForScope( + registryId, + { + // $filter=principalId eq {id} + filter: `principalId eq '{${managedEnvironmentIdentity}}'`, + } + )); + return roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(acrPullRoleId)); + } + + public createSuccessOutput(): ExecuteActivityOutput { + return { + item: new GenericTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['containerRegistryAcrPullEnableStepSuccessItem', activitySuccessContext]), + label: localize('enableAcrPull', 'Grant "{0}" access to container environment identity', 'acrPull'), + iconPath: activitySuccessIcon + }), + message: localize('enableAcrPullSuccess', 'Successfully granted "{0}" access to container environment identity.', 'acrPull'), + }; + } + + public createFailOutput(): ExecuteActivityOutput { + return { + item: new GenericParentTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['containerRegistryAcrPullEnableStepFailItem', activityFailContext]), + label: localize('enableAcrPull', 'Grant "{0}" access to container environment identity"', 'acrPull'), + iconPath: activityFailIcon + }), + message: localize('enableAcrPullFail', 'Failed to grant "{0}" access to container environment identity.', 'acrPull'), + }; + } +} diff --git a/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts b/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts new file mode 100644 index 000000000..44853a2ff --- /dev/null +++ b/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts @@ -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 { KnownManagedServiceIdentityType, type ContainerAppsAPIClient, type ManagedEnvironment } from "@azure/arm-appcontainers"; +import { parseAzureResourceId, type ParsedAzureResourceId } from "@microsoft/vscode-azext-azureutils"; +import { activityFailIcon, activitySuccessContext, activitySuccessIcon, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericParentTreeItem, GenericTreeItem, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils"; +import { type Progress } from "vscode"; +import { createContainerAppsAPIClient } from "../../../utils/azureClients"; +import { localize } from "../../../utils/localize"; +import { type ManagedIdentityRegistryCredentialsContext } from "./ManagedIdentityRegistryCredentialsContext"; + +export class ManagedEnvironmentIdentityEnableStep extends AzureWizardExecuteStep { + public priority: number = 450; + + public async execute(context: ManagedIdentityRegistryCredentialsContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context); + const managedEnvironment: ManagedEnvironment = nonNullProp(context, 'managedEnvironment'); + const parsedResourceId: ParsedAzureResourceId = parseAzureResourceId(nonNullProp(managedEnvironment, 'id')); + + progress.report({ message: localize('enablingIdentity', 'Enabling managed identity...') }) + context.managedEnvironment = await client.managedEnvironments.beginUpdateAndWait(parsedResourceId.resourceGroup, parsedResourceId.resourceName, + { + location: managedEnvironment.location, + identity: { + type: KnownManagedServiceIdentityType.SystemAssigned, + }, + } + ); + } + + public shouldExecute(context: ManagedIdentityRegistryCredentialsContext): boolean { + return !!context.managedEnvironment && !context.managedEnvironment.identity?.principalId; + } + + public createSuccessOutput(context: ManagedIdentityRegistryCredentialsContext): ExecuteActivityOutput { + return { + item: new GenericTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['managedEnvironmentIdentityEnableStepSuccessItem', activitySuccessContext]), + label: localize('enableIdentity', 'Enable system-assigned identity for environment "{0}"', context.managedEnvironment?.name), + iconPath: activitySuccessIcon + }), + message: localize('enableIdentitySuccess', 'Enabled system-assigned identity for environment "{0}"', context.managedEnvironment?.name) + }; + } + + public createFailOutput(context: ManagedIdentityRegistryCredentialsContext): ExecuteActivityOutput { + return { + item: new GenericParentTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['managedEnvironmentIdentityEnableStepFailItem', activitySuccessContext]), + label: localize('enableIdentity', 'Enable system-assigned identity for environment "{0}"', context.managedEnvironment?.name), + iconPath: activityFailIcon + }), + message: localize('enableIdentityFail', 'Failed to enable system-assigned identity for environment "{0}"', context.managedEnvironment?.name) + }; + } +} diff --git a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialAddConfigurationStep.ts b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialAddConfigurationStep.ts new file mode 100644 index 000000000..98d0c36c3 --- /dev/null +++ b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialAddConfigurationStep.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { acrDomain, type SupportedRegistries } from "../../../constants"; +import { localize } from "../../../utils/localize"; +import { type ManagedIdentityRegistryCredentialsContext } from "./ManagedIdentityRegistryCredentialsContext"; + +export class ManagedIdentityRegistryCredentialAddConfigurationStep extends AzureWizardExecuteStep { + public priority: number = 470; + + constructor(private readonly supportedRegistryDomain: SupportedRegistries | undefined) { + super(); + } + + public async execute(context: ManagedIdentityRegistryCredentialsContext): Promise { + if (this.supportedRegistryDomain !== acrDomain) { + throw new Error(localize('domainNotSupported', 'The provided registry domain does not have managed identity connection support.')); + } + + const registry = nonNullProp(context, 'registry'); + context.newRegistryCredential = { + identity: 'system-environment', + server: registry.loginServer, + username: '', + passwordSecretRef: '', + }; + } + + public shouldExecute(context: ManagedIdentityRegistryCredentialsContext): boolean { + return !context.newRegistryCredential; + } +} diff --git a/src/utils/azureClients.ts b/src/utils/azureClients.ts index c165294b4..edb55f00e 100644 --- a/src/utils/azureClients.ts +++ b/src/utils/azureClients.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { type ContainerAppsAPIClient } from "@azure/arm-appcontainers"; +import { type AuthorizationManagementClient } from "@azure/arm-authorization"; import { type ContainerRegistryManagementClient, type Registry } from '@azure/arm-containerregistry'; import { type OperationalInsightsManagementClient } from '@azure/arm-operationalinsights'; import { ContainerRegistryClient, KnownContainerRegistryAudience } from '@azure/container-registry'; @@ -34,3 +35,7 @@ export function createContainerRegistryClient(context: AzExtClientContext, regis export async function createOperationalInsightsManagementClient(context: AzExtClientContext): Promise { return createAzureClient(context, (await import('@azure/arm-operationalinsights')).OperationalInsightsManagementClient); } + +export async function createAuthorizationManagementClient(context: AzExtClientContext): Promise { + return createAzureClient(context, (await import('@azure/arm-authorization')).AuthorizationManagementClient); +} From 55a8019e38dfe02df7e8fa877121ed87f3ebb222 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:17:12 -0700 Subject: [PATCH 14/19] Add AcrPullVerifyStep --- src/commands/EXECUTE_PRIORITY.md | 3 +- .../identity/AcrPullEnableStep.ts | 32 ++------- .../identity/AcrPullVerifyStep.ts | 66 +++++++++++++++++++ ...nagedIdentityRegistryCredentialsContext.ts | 1 + 4 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 src/commands/registryCredentials/identity/AcrPullVerifyStep.ts diff --git a/src/commands/EXECUTE_PRIORITY.md b/src/commands/EXECUTE_PRIORITY.md index 7f6e10155..d750c8a30 100644 --- a/src/commands/EXECUTE_PRIORITY.md +++ b/src/commands/EXECUTE_PRIORITY.md @@ -29,7 +29,8 @@ When creating or updating resources, execute steps should occupy certain priorit #### Steps ##### Managed Identity Registry Credential - ManagedEnvironmentIdentityEnableStep: 450 -- AcrPullEnableStep: 460 +- AcrPullVerifyStep: 460 +- AcrPullEnableStep: 461 - ManagedIdentityRegistryCredentialAddConfigurationStep: 470 ##### Admin User Registry Credential diff --git a/src/commands/registryCredentials/identity/AcrPullEnableStep.ts b/src/commands/registryCredentials/identity/AcrPullEnableStep.ts index 3a90acc40..977c048d5 100644 --- a/src/commands/registryCredentials/identity/AcrPullEnableStep.ts +++ b/src/commands/registryCredentials/identity/AcrPullEnableStep.ts @@ -3,31 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KnownPrincipalType, type AuthorizationManagementClient, type RoleAssignment, type RoleAssignmentCreateParameters } from "@azure/arm-authorization"; -import { uiUtils } from "@microsoft/vscode-azext-azureutils"; +import { KnownPrincipalType, type AuthorizationManagementClient, type RoleAssignmentCreateParameters } from "@azure/arm-authorization"; import { AzureWizardExecuteStep, GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, nonNullValueAndProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils"; import * as crypto from "crypto"; import { type Progress } from "vscode"; import { createAuthorizationManagementClient } from "../../../utils/azureClients"; import { localize } from "../../../utils/localize"; +import { acrPullRoleId } from "./AcrPullVerifyStep"; import { type ManagedIdentityRegistryCredentialsContext } from "./ManagedIdentityRegistryCredentialsContext"; -const acrPullRoleId: string = '7f951dda-4ed3-4680-a7ca-43fe172d538d'; - export class AcrPullEnableStep extends AzureWizardExecuteStep { - public priority: number = 460; - - // Add a configureBeforeExecute + public priority: number = 461; public async execute(context: ManagedIdentityRegistryCredentialsContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { const client: AuthorizationManagementClient = await createAuthorizationManagementClient(context); - const registryId: string = nonNullValueAndProp(context.registry, 'id'); - const managedEnvironmentIdentity: string = nonNullValueAndProp(context.managedEnvironment?.identity, 'principalId'); - - if (await this.hasAcrPullAssignment(client, registryId, managedEnvironmentIdentity)) { - return; - } - const roleCreateParams: RoleAssignmentCreateParameters = { description: 'acr pull', roleDefinitionId: `/providers/Microsoft.Authorization/roleDefinitions/${acrPullRoleId}`, @@ -35,7 +24,7 @@ export class AcrPullEnableStep extends AzureWizardExecuteStep { - const roleAssignments: RoleAssignment[] = await uiUtils.listAllIterator(client.roleAssignments.listForScope( - registryId, - { - // $filter=principalId eq {id} - filter: `principalId eq '{${managedEnvironmentIdentity}}'`, - } - )); - return roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(acrPullRoleId)); + return !!context.registry && !context.hasAcrPullRole; } public createSuccessOutput(): ExecuteActivityOutput { diff --git a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts new file mode 100644 index 000000000..8d010dd19 --- /dev/null +++ b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type AuthorizationManagementClient, type RoleAssignment } from "@azure/arm-authorization"; +import { uiUtils } from "@microsoft/vscode-azext-azureutils"; +import { AzureWizardExecuteStep, GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, nonNullValueAndProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils"; +import { type Progress } from "vscode"; +import { createAuthorizationManagementClient } from "../../../utils/azureClients"; +import { localize } from "../../../utils/localize"; +import { type ManagedIdentityRegistryCredentialsContext } from "./ManagedIdentityRegistryCredentialsContext"; + +export const acrPullRoleId: string = '7f951dda-4ed3-4680-a7ca-43fe172d538d'; + +export class AcrPullVerifyStep extends AzureWizardExecuteStep { + public priority: number = 460; + + public async execute(context: ManagedIdentityRegistryCredentialsContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise { + const client: AuthorizationManagementClient = await createAuthorizationManagementClient(context); + const registryId: string = nonNullValueAndProp(context.registry, 'id'); + const managedEnvironmentIdentity: string = nonNullValueAndProp(context.managedEnvironment?.identity, 'principalId'); + + progress.report({ message: localize('verifyingAcrPull', 'Verifying ACR pull role...') }) + const roleAssignments: RoleAssignment[] = await uiUtils.listAllIterator(client.roleAssignments.listForScope( + registryId, + { + // $filter=principalId eq {id} + filter: `principalId eq '{${managedEnvironmentIdentity}}'`, + } + )); + + context.hasAcrPullRole = roleAssignments.some(r => !!r.roleDefinitionId?.endsWith(acrPullRoleId)); + } + + public shouldExecute(context: ManagedIdentityRegistryCredentialsContext): boolean { + return !!context.registry; + } + + public createSuccessOutput(context: ManagedIdentityRegistryCredentialsContext): ExecuteActivityOutput { + if (context.hasAcrPullRole) { + return { + item: new GenericTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['containerRegistryAcrPullVerifyStepSuccessItem', activitySuccessContext]), + label: localize('verifyAcrPull', 'Verify "{0}" access for container environment identity', 'acrPull'), + iconPath: activitySuccessIcon + }), + message: localize('verifyAcrPullSuccess', 'Successfully verified "{0}" access for container environment identity.', 'acrPull'), + }; + } else { + // 'AcrPullEnableStep' will cover showing this output + return {}; + } + } + + public createFailOutput(): ExecuteActivityOutput { + return { + item: new GenericParentTreeItem(undefined, { + contextValue: createUniversallyUniqueContextValue(['containerRegistryAcrPullVerifyStepFailItem', activityFailContext]), + label: localize('verifyAcrPull', 'Verify "{0}" access for container environment identity"', 'acrPull'), + iconPath: activityFailIcon + }), + message: localize('verifyAcrPullFail', 'Failed to verify "{0}" access for container environment identity.', 'acrPull'), + }; + } +} diff --git a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts index 376e7e352..5ca16bd1c 100644 --- a/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts +++ b/src/commands/registryCredentials/identity/ManagedIdentityRegistryCredentialsContext.ts @@ -9,5 +9,6 @@ import { type CreateAcrContext } from "../../image/imageSource/containerRegistry import { type ManagedEnvironmentRequiredContext } from "../../ManagedEnvironmentContext"; export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentRequiredContext, IContainerAppContext { + hasAcrPullRole?: boolean; newRegistryCredential?: RegistryCredentials; } From 2c926dd1e81a345a3293ce9d1b8c03a8f8237d95 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:09:51 -0700 Subject: [PATCH 15/19] Updates to AcrPullVerifyStep --- .../RegistryCredentialsAddConfigurationListStep.ts | 6 ++++-- .../registryCredentials/identity/AcrPullVerifyStep.ts | 10 +++++----- ...IdentityRegistryCredentialsAddConfigurationStep.ts} | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) rename src/commands/registryCredentials/identity/{ManagedIdentityRegistryCredentialAddConfigurationStep.ts => ManagedIdentityRegistryCredentialsAddConfigurationStep.ts} (91%) diff --git a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts index 9e3af178b..f0485ce78 100644 --- a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts +++ b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts @@ -11,8 +11,9 @@ import { AcrEnableAdminUserConfirmStep } from "./dockerLogin/AcrEnableAdminUserC import { AcrEnableAdminUserStep } from "./dockerLogin/AcrEnableAdminUserStep"; import { DockerLoginRegistryCredentialsAddConfigurationStep } from "./dockerLogin/DockerLoginRegistryCredentialsAddConfigurationStep"; import { AcrPullEnableStep } from "./identity/AcrPullEnableStep"; +import { AcrPullVerifyStep } from "./identity/AcrPullVerifyStep"; import { ManagedEnvironmentIdentityEnableStep } from "./identity/ManagedEnvironmentIdentityEnableStep"; -import { ManagedIdentityRegistryCredentialAddConfigurationStep } from "./identity/ManagedIdentityRegistryCredentialAddConfigurationStep"; +import { ManagedIdentityRegistryCredentialsAddConfigurationStep } from "./identity/ManagedIdentityRegistryCredentialsAddConfigurationStep"; import { RegistryCredentialsAndSecretsConfigurationStep } from "./RegistryCredentialsAndSecretsConfigurationStep"; import { type RegistryCredentialsContext } from "./RegistryCredentialsContext"; @@ -62,8 +63,9 @@ export class RegistryCredentialsAddConfigurationListStep extends AzureWizardProm case RegistryCredentialType.SystemAssigned: executeSteps.push( new ManagedEnvironmentIdentityEnableStep(), + new AcrPullVerifyStep(), new AcrPullEnableStep(), - new ManagedIdentityRegistryCredentialAddConfigurationStep(registryDomain), + new ManagedIdentityRegistryCredentialsAddConfigurationStep(registryDomain), ); break; case RegistryCredentialType.DockerLogin: diff --git a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts index 8d010dd19..c39e24304 100644 --- a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts +++ b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts @@ -42,10 +42,10 @@ export class AcrPullVerifyStep extends AzureWizardExecuteStep { +export class ManagedIdentityRegistryCredentialsAddConfigurationStep extends AzureWizardExecuteStep { public priority: number = 470; constructor(private readonly supportedRegistryDomain: SupportedRegistries | undefined) { From 5a1246fa96cb801e966f9c0f135e7efbc23069c3 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:12:07 -0700 Subject: [PATCH 16/19] Update comment --- src/commands/registryCredentials/identity/AcrPullVerifyStep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts index c39e24304..e6bdcc844 100644 --- a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts +++ b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts @@ -48,7 +48,7 @@ export class AcrPullVerifyStep extends AzureWizardExecuteStep Date: Tue, 17 Sep 2024 20:11:41 -0700 Subject: [PATCH 17/19] Don't call list creds if identity --- .../getDeployWorkspaceProjectResults.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts b/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts index 268a58036..f48109537 100644 --- a/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts +++ b/src/commands/deployWorkspaceProject/getDeployWorkspaceProjectResults.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { type RegistryCredentials } from "@azure/arm-appcontainers"; import { type RegistryPassword } from "@azure/arm-containerregistry"; import { type Workspace } from "@azure/arm-operationalinsights"; import { uiUtils } from "@microsoft/vscode-azext-azureutils"; @@ -14,8 +15,12 @@ import { type DeployWorkspaceProjectContext } from "./DeployWorkspaceProjectCont export type DeployWorkspaceProjectResults = api.DeployWorkspaceProjectResults; export async function getDeployWorkspaceProjectResults(context: DeployWorkspaceProjectContext): Promise { - const registryCredentials: { username: string, password: RegistryPassword } | undefined = context.registry ? - await listCredentialsFromAcr(context) : undefined; + const registryCredentials: RegistryCredentials | undefined = context.containerApp?.configuration?.registries?.find(r => r.server === context.registry?.loginServer); + + let listedCredentials: { username: string, password: RegistryPassword } | undefined; + if (!registryCredentials?.identity) { + listedCredentials = await listCredentialsFromAcr(context); + } context.logAnalyticsWorkspace ??= await tryGetLogAnalyticsWorkspace(context); @@ -26,8 +31,8 @@ export async function getDeployWorkspaceProjectResults(context: DeployWorkspaceP containerAppId: context.containerApp?.id, registryId: context.registry?.id, registryLoginServer: context.registry?.loginServer, - registryUsername: registryCredentials?.username, - registryPassword: registryCredentials?.password.value, + registryUsername: listedCredentials?.username, + registryPassword: listedCredentials?.password.value, imageName: context.imageName }; } From c50163a20c06c440df0b09df868dbae9db19f624 Mon Sep 17 00:00:00 2001 From: MicroFish91 <40250218+MicroFish91@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:06:30 -0700 Subject: [PATCH 18/19] Update should execute condition --- src/commands/registryCredentials/identity/AcrPullVerifyStep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts index e6bdcc844..10f7b9d2d 100644 --- a/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts +++ b/src/commands/registryCredentials/identity/AcrPullVerifyStep.ts @@ -34,7 +34,7 @@ export class AcrPullVerifyStep extends AzureWizardExecuteStep Date: Fri, 4 Oct 2024 12:41:04 -0700 Subject: [PATCH 19/19] Feedback --- .../RegistryCredentialsAddConfigurationListStep.ts | 2 +- .../identity/ManagedEnvironmentIdentityEnableStep.ts | 2 +- src/utils/azureClients.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts index f0485ce78..074c8b0d6 100644 --- a/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts +++ b/src/commands/registryCredentials/RegistryCredentialsAddConfigurationListStep.ts @@ -102,7 +102,7 @@ export class RegistryCredentialsAddConfigurationListStep extends AzureWizardProm if (registryDomain === acrDomain) { picks.push({ label: 'Managed Identity', - description: '(recommended)', + description: localize('recommended', '(recommended)'), detail: localize('systemIdentityDetails', 'Setup "{0}" access for container environment resources via a system-assigned identity', 'acrPull'), data: RegistryCredentialType.SystemAssigned, }); diff --git a/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts b/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts index 44853a2ff..b28117366 100644 --- a/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts +++ b/src/commands/registryCredentials/identity/ManagedEnvironmentIdentityEnableStep.ts @@ -19,7 +19,7 @@ export class ManagedEnvironmentIdentityEnableStep extends AzureWizardExecuteStep const managedEnvironment: ManagedEnvironment = nonNullProp(context, 'managedEnvironment'); const parsedResourceId: ParsedAzureResourceId = parseAzureResourceId(nonNullProp(managedEnvironment, 'id')); - progress.report({ message: localize('enablingIdentity', 'Enabling managed identity...') }) + progress.report({ message: localize('enablingIdentity', 'Enabling managed identity...') }); context.managedEnvironment = await client.managedEnvironments.beginUpdateAndWait(parsedResourceId.resourceGroup, parsedResourceId.resourceName, { location: managedEnvironment.location, diff --git a/src/utils/azureClients.ts b/src/utils/azureClients.ts index edb55f00e..d284e4b06 100644 --- a/src/utils/azureClients.ts +++ b/src/utils/azureClients.ts @@ -37,5 +37,9 @@ export async function createOperationalInsightsManagementClient(context: AzExtCl } export async function createAuthorizationManagementClient(context: AzExtClientContext): Promise { - return createAzureClient(context, (await import('@azure/arm-authorization')).AuthorizationManagementClient); + if (parseClientContext(context).isCustomCloud) { + return createAzureClient(context, (await import('@azure/arm-authorization-profile-2020-09-01-hybrid')).AuthorizationManagementClient); + } else { + return createAzureClient(context, (await import('@azure/arm-authorization')).AuthorizationManagementClient); + } }