Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
437 changes: 437 additions & 0 deletions azure/api-extractor.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this file

Large diffs are not rendered by default.

712 changes: 0 additions & 712 deletions azure/index.d.ts

This file was deleted.

592 changes: 592 additions & 0 deletions azure/package-lock.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert changes to this file

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion azure/package.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Probably version this as an alpha

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"module": "dist/esm/src/index.js",
"main": "dist/cjs/src/index.js",
"types": "index.d.ts",
"types": "dist/esm/src/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ import type { Environment } from '@azure/ms-rest-azure-env';
import type { ReadStream } from 'fs';
import type { CancellationToken, Event, Progress, Terminal } from 'vscode';

/**
* @deprecated The Azure Account extension is deprecated.
*/
export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut';

/**
* @deprecated The Azure Account extension is deprecated.
*/
export interface AzureAccountExtensionApi {
readonly apiVersion: string;
readonly status: AzureLoginStatus;
Expand All @@ -27,6 +33,9 @@ export interface AzureAccountExtensionApi {
createCloudShell(os: 'Linux' | 'Windows'): CloudShell;
}

/**
* @deprecated The Azure Account extension is deprecated.
*/
export interface AzureSession {
readonly environment: Environment;
readonly userId: string;
Expand All @@ -38,21 +47,36 @@ export interface AzureSession {
readonly credentials2: TokenCredential;
}

/**
* @deprecated The Azure Account extension is deprecated.
*/
export interface AzureSubscription {
readonly session: AzureSession;
readonly subscription: Subscription;
}

/**
* @deprecated The Azure Account extension is deprecated.
*/
export type AzureResourceFilter = AzureSubscription;

/**
* @deprecated The Azure Account extension is deprecated.
*/
export type CloudShellStatus = 'Connecting' | 'Connected' | 'Disconnected';

/**
* @deprecated The Azure Account extension is deprecated.
*/
export interface UploadOptions {
contentLength?: number;
progress?: Progress<{ message?: string; increment?: number }>;
token?: CancellationToken;
}

/**
* @deprecated The Azure Account extension is deprecated.
*/
export interface CloudShell {
readonly status: CloudShellStatus;
readonly onStatusChanged: Event<CloudShellStatus>;
Expand Down
21 changes: 14 additions & 7 deletions azure/src/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,47 @@ import type { ManagedServiceIdentityClient } from '@azure/arm-msi';
import type { ResourceManagementClient } from '@azure/arm-resources';
import type { SubscriptionClient } from '@azure/arm-resources-subscriptions';
import type { StorageManagementClient } from '@azure/arm-storage';
import type { AzExtClientType } from '../index';
import { createAzureClient, createAzureSubscriptionClient, InternalAzExtClientContext, parseClientContext } from './createAzureClient';
import type { AzExtClientType } from './createAzureClient';
import { createAzureClient, createAzureSubscriptionClient, parseClientContext } from './createAzureClient';
import type { AzExtClientContext } from './createAzureClient';

// Lazy-load @azure packages to improve startup performance.
// NOTE: The client is the only import that matters, the rest of the types disappear when compiled to JavaScript

export async function createStorageClient(context: InternalAzExtClientContext): Promise<StorageManagementClient> {
export async function createStorageClient(context: AzExtClientContext): Promise<StorageManagementClient> {
if (parseClientContext(context).isCustomCloud) {
return <StorageManagementClient><unknown>createAzureClient(context, (await import('@azure/arm-storage-profile-2020-09-01-hybrid')).StorageManagementClient);
} else {
return createAzureClient(context, (await import('@azure/arm-storage')).StorageManagementClient as unknown as AzExtClientType<StorageManagementClient>);
}
}

export async function createResourcesClient(context: InternalAzExtClientContext): Promise<ResourceManagementClient> {
export async function createResourcesClient(context: AzExtClientContext): Promise<ResourceManagementClient> {
if (parseClientContext(context).isCustomCloud) {
return <ResourceManagementClient><unknown>createAzureClient(context, (await import('@azure/arm-resources-profile-2020-09-01-hybrid')).ResourceManagementClient);
} else {
return createAzureClient(context, (await import('@azure/arm-resources')).ResourceManagementClient);
}
}

export async function createManagedServiceIdentityClient(context: InternalAzExtClientContext): Promise<ManagedServiceIdentityClient> {
/**
* Used to create Azure clients for managed identity without having to install the sdk into client extension package.json
*/
export async function createManagedServiceIdentityClient(context: AzExtClientContext): Promise<ManagedServiceIdentityClient> {
return createAzureClient(context, (await import('@azure/arm-msi')).ManagedServiceIdentityClient as unknown as AzExtClientType<ManagedServiceIdentityClient>);
}

export async function createAuthorizationManagementClient(context: InternalAzExtClientContext): Promise<AuthorizationManagementClient> {
/**
* Used to create Azure clients for managed identity without having to install the sdk into client extension package.json
*/
export async function createAuthorizationManagementClient(context: AzExtClientContext): Promise<AuthorizationManagementClient> {
if (parseClientContext(context).isCustomCloud) {
return <AuthorizationManagementClient><unknown>createAzureClient(context, (await import('@azure/arm-authorization-profile-2020-09-01-hybrid')).AuthorizationManagementClient);
} else {
return createAzureClient(context, (await import('@azure/arm-authorization')).AuthorizationManagementClient);
}
}

export async function createSubscriptionsClient(context: InternalAzExtClientContext): Promise<SubscriptionClient> {
export async function createSubscriptionsClient(context: AzExtClientContext): Promise<SubscriptionClient> {
return createAzureSubscriptionClient(context, (await import('@azure/arm-resources-subscriptions')).SubscriptionClient);
}
32 changes: 21 additions & 11 deletions azure/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,79 +12,89 @@ export const storageProviderType = "Microsoft.Storage/storageAccounts";
export const IdentityProvider: string = 'Microsoft.ManagedIdentity';
export const UserAssignedIdentityResourceType: string = 'userAssignedIdentities';

/**
* Common Roles that should be used to assign permissions to resources
* The role definitions can be found here: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
*/
export const CommonRoleDefinitions = {
storageBlobDataContributor: {
name: "ba92f5b4-2d11-453d-a403-e96b0029c9fe",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Storage Blob Data Contributor",
description: "Allows for read, write and delete access to Azure Storage blob containers and data",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes make it so the full values and everything show up in the declarations. Otherwise it just does export declare const ....

storageBlobDataOwner: {
name: "b7e6dc6d-f1e8-4753-8033-0f276bb0955b",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Storage Blob Data Owner",
description: "Allows for full access to Azure Storage blob containers and data, including assigning POSIX access control.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
storageQueueDataContributor: {
name: "974c5e8b-45b9-4653-ba55-5f855dd0fb88",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Storage Queue Data Contributor",
description: "Read, write, and delete Azure Storage queues and queue messages.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
azureServiceBusDataReceiver: {
name: "4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Azure Service Bus Data Receiver",
description: "Allows for receive access to Azure Service Bus resources.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
azureServiceBusDataOwner: {
name: "090c5cfd-751d-490a-894a-3ce6f1109419",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Azure Service Bus Data Owner",
description: "Allows for full access to Azure Service Bus resources.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
azureEventHubsDataReceiver: {
name: "a638d3c7-ab3a-418d-83e6-5f17a39d4fde",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Azure Event Hubs Data Receiver",
description: "Allows receive access to Azure Event Hubs resources.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
azureEventHubsDataOwner: {
name: "f526a384-b230-433a-b45c-95f59c4a2dec",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Azure Event Hubs Data Owner",
description: "Allows for full access to Azure Event Hubs resources.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
cosmosDBAccountReader: {
name: "fbdf93bf-df7d-467e-a4d2-9458aa1360c8",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Cosmos DB Account Reader",
description: "Can read Azure Cosmos DB account data.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
documentDBAccountContributor: {
name: "5bd9cd88-fe45-4216-938b-f97437e15450",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "DocumentDB Account Contributor",
description: "Can manage Azure Cosmos DB accounts.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const,
durableTaskDataContributor: {
name: "0ad04412-c4d5-4796-b79c-f76d14c8d402",
type: "Microsoft.Authorization/roleDefinitions",
roleName: "Durable Task Data Contributor",
description: "Durable Task role for all data access operations.",
roleType: "BuiltInRole"
} as RoleDefinition,
} as const;
} as const,
} as const satisfies Record<string, RoleDefinition>;

/**
* Constructs the role id for a given subscription and role name id
*
* @param subscriptionId - Id for the subscription
* @param roleId - Name id for the role to be assigned (i.e CommonRoleDefinitions.storageBlobDataContributor.name)
*/
export function createRoleId(subscriptionId: string, roleDefinition: RoleDefinition): string {
return `/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${roleDefinition.name}`;
}
83 changes: 69 additions & 14 deletions azure/src/createAzureClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,45 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ServiceClient } from '@azure/core-client';
import { createHttpHeaders, createPipelineRequest, defaultRetryPolicy, Pipeline, PipelineOptions, PipelinePolicy, PipelineRequest, PipelineResponse, RestError, RetryPolicyOptions, SendRequest, userAgentPolicy } from '@azure/core-rest-pipeline';
import { appendExtensionUserAgent, AzExtServiceClientCredentialsT2, AzExtTreeItem, IActionContext, ISubscriptionActionContext, ISubscriptionContext, parseError } from '@microsoft/vscode-azext-utils';
import { ServiceClient, ServiceClientOptions } from '@azure/core-client';
import { createHttpHeaders, createPipelineRequest, defaultRetryPolicy, Pipeline, PipelineOptions, PipelinePolicy, PipelineRequest, PipelineRequestOptions, PipelineResponse, RestError, RetryPolicyOptions, SendRequest, userAgentPolicy } from '@azure/core-rest-pipeline';
import type { Environment } from '@azure/ms-rest-azure-env';
import { appendExtensionUserAgent, AzExtServiceClientCredentials, AzExtServiceClientCredentialsT2, AzExtTreeItem, IActionContext, ISubscriptionActionContext, ISubscriptionContext, parseError } from '@microsoft/vscode-azext-utils';
import { randomUUID } from 'crypto';
import { Agent as HttpsAgent } from 'https';
import * as vscode from "vscode";
import * as types from '../index';
import { parseJson, removeBom } from './utils/parseJson';

export type InternalAzExtClientContext = ISubscriptionActionContext | [IActionContext, ISubscriptionContext | AzExtTreeItem];
export type AzExtClientType<T extends ServiceClient> = new (credentials: AzExtServiceClientCredentials, subscriptionId: string, options?: ServiceClientOptions) => T;

export function parseClientContext(clientContext: InternalAzExtClientContext): ISubscriptionActionContext {
/**
* Convenience type to give us multiple ways to specify subscription info and action context depending on the scenario
*/
export type AzExtClientContext = ISubscriptionActionContext | [IActionContext, ISubscriptionContext | AzExtTreeItem];

/**
* Credential type to be used for creating generic http rest clients
*/
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
export type AzExtGenericCredentials = AzExtServiceClientCredentials | AzExtServiceClientCredentialsT2;
export type AzExtGenericClientInfo = AzExtGenericCredentials | { credentials: AzExtGenericCredentials; environment: Environment; } | undefined;

export interface IGenericClientOptions {
noRetryPolicy?: boolean;
addStatusCodePolicy?: boolean;
endpoint?: string;
}

export type AzExtRequestPrepareOptions = PipelineRequestOptions & { rejectUnauthorized?: boolean }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AzExtPipelineResponse = PipelineResponse & { parsedBody?: any }

export type AzExtSubscriptionClientType<T> = new (credentials: AzExtServiceClientCredentials, options?: ServiceClientOptions) => T;

/**
* Converts `AzExtClientContext` into a single object: `ISubscriptionActionContext`
*/
export function parseClientContext(clientContext: AzExtClientContext): ISubscriptionActionContext {
if (Array.isArray(clientContext)) {
const subscription = clientContext[1] instanceof AzExtTreeItem ? clientContext[1].subscription : clientContext[1];
// Make sure to copy over just the subscription info and not any other extraneous properties
Expand Down Expand Up @@ -44,7 +71,12 @@ function getChallengeHandlerFromCredential(createCredentialsForScopes: (request:
return getTokenForChallenge;
}

export function createAzureClient<T extends ServiceClient>(clientContext: InternalAzExtClientContext, clientType: types.AzExtClientType<T>): T {
/**
* Creates an Azure client, ensuring best practices are followed. For example:
* 1. Adds extension-specific user agent
* 2. Uses resourceManagerEndpointUrl to support sovereigns
*/
export function createAzureClient<T extends ServiceClient>(clientContext: AzExtClientContext, clientType: AzExtClientType<T>): T {
const context = parseClientContext(clientContext);
const client = new clientType(context.credentials, context.subscriptionId, {
endpoint: context.environment.resourceManagerEndpointUrl,
Expand All @@ -66,7 +98,12 @@ export function createAzureClient<T extends ServiceClient>(clientContext: Intern
return client;
}

export function createAzureSubscriptionClient<T extends ServiceClient>(clientContext: InternalAzExtClientContext, clientType: types.AzExtSubscriptionClientType<T>): T {
/**
* Creates an Azure subscription client, ensuring best practices are followed. For example:
* 1. Adds extension-specific user agent
* 2. Uses resourceManagerEndpointUrl to support sovereigns
*/
export function createAzureSubscriptionClient<T extends ServiceClient>(clientContext: AzExtClientContext, clientType: AzExtSubscriptionClientType<T>): T {
const context = parseClientContext(clientContext);
const client = new clientType(context.credentials, {
endpoint: context.environment.resourceManagerEndpointUrl
Expand All @@ -88,7 +125,12 @@ export function createAzureSubscriptionClient<T extends ServiceClient>(clientCon
return client;
}

export async function sendRequestWithTimeout(context: IActionContext, options: types.AzExtRequestPrepareOptions, timeout: number, clientInfo: types.AzExtGenericClientInfo): Promise<types.AzExtPipelineResponse> {
/**
* Send request with a timeout specified and turn off retry policy (because retrying could take a lot longer)
* @param timeout The timeout in milliseconds
* @param clientInfo The client/credentials info or `undefined` if no credentials are needed
*/
export async function sendRequestWithTimeout(context: IActionContext, options: AzExtRequestPrepareOptions, timeout: number, clientInfo: AzExtGenericClientInfo): Promise<AzExtPipelineResponse> {
const request: PipelineRequest = createPipelineRequest({
...options,
timeout
Expand All @@ -103,8 +145,14 @@ export async function sendRequestWithTimeout(context: IActionContext, options: t
}


export async function createGenericClient(context: IActionContext, clientInfo: types.AzExtGenericClientInfo | undefined, options?: types.IGenericClientOptions): Promise<ServiceClient> {
let credentials: types.AzExtGenericCredentials | undefined;
/**
* Creates a generic http rest client (i.e. for non-Azure calls or for Azure calls that the available sdks don't support), ensuring best practices are followed. For example:
* 1. Adds extension-specific user agent
* 2. Uses resourceManagerEndpointUrl to support sovereigns (if clientInfo corresponds to an Azure environment)
* @param clientInfo The client/credentials info or `undefined` if no credentials are needed
*/
export async function createGenericClient(context: IActionContext, clientInfo: AzExtGenericClientInfo | undefined, options?: IGenericClientOptions): Promise<ServiceClient> {
let credentials: AzExtGenericCredentials | undefined;
let endpoint: string | undefined;
if (clientInfo && 'credentials' in clientInfo) {
credentials = clientInfo.credentials;
Expand Down Expand Up @@ -168,14 +216,21 @@ function addAzExtPipeline(context: IActionContext, pipeline: Pipeline, endpoint?
return pipeline;
}

/**
* Replaces the usage of BasicAuthenticationCredentials for ServiceClients imported from @azure/core-pipelines
*
* @param client - The service client. This will typically be a generalClient
* @param userName - Username to be used with basic authentication login
* @param password - Password. Gets encoded before being set in the header
*/
export function addBasicAuthenticationCredentialsToClient(client: ServiceClient, userName: string, password: string): void {
client.pipeline.addPolicy(new BasicAuthenticationCredentialsPolicy(userName, password), { phase: 'Serialize' });
}

/**
* Automatically add id to correlate our telemetry with the platform team's telemetry
*/
export class CorrelationIdPolicy implements PipelinePolicy {
class CorrelationIdPolicy implements PipelinePolicy {
public readonly name = 'CorrelationIdPolicy';

constructor(private readonly context: IActionContext) {
Expand Down Expand Up @@ -266,8 +321,8 @@ class AddEndpointPolicy implements PipelinePolicy {
class StatusCodePolicy implements PipelinePolicy {
public readonly name = 'StatusCodePolicy';

public async sendRequest(request: PipelineRequest, next: SendRequest): Promise<types.AzExtPipelineResponse> {
const response: types.AzExtPipelineResponse = await next(request);
public async sendRequest(request: PipelineRequest, next: SendRequest): Promise<AzExtPipelineResponse> {
const response: AzExtPipelineResponse = await next(request);
if (response.status < 200 || response.status >= 300) {
const errorMessage: string = response.bodyAsText ?
parseError(response.parsedBody || response.bodyAsText).message :
Expand Down
Loading