diff --git a/extensions/arc/notebooks/arcDeployment/deploy.arc.data.controller.ipynb b/extensions/arc/notebooks/arcDeployment/deploy.arc.data.controller.ipynb index 84f30b687f..26526dd590 100644 --- a/extensions/arc/notebooks/arcDeployment/deploy.arc.data.controller.ipynb +++ b/extensions/arc/notebooks/arcDeployment/deploy.arc.data.controller.ipynb @@ -185,21 +185,24 @@ "print (f'Creating Azure Arc Data Controller: {arc_data_controller_name} using configuration {arc_cluster_context}')\n", "os.environ[\"AZDATA_USERNAME\"] = arc_admin_username\n", "os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n", + "os.environ[\"LOG_WORKSPACE_ID\"] = log_analytics_workspace_id\n", + "os.environ[\"LOG_SHARED_KEY\"] = log_analytics_primary_key\n", "\n", + "# If connection mode is indirect\n", "namespace = f' --k8s-namespace {arc_data_controller_namespace}' if is_indirect else ''\n", "use_k8s = ' --use-k8s' if is_indirect else ''\n", "\n", + "# If connection mode is direct\n", "custom_location = f' --custom-location {arc_data_controller_custom_location}' if not is_indirect else ''\n", - "\n", + "cluster_name = f' --cluster-name {arc_cluster_context}' if not is_indirect else ''\n", "auto_upload_metrics_value = 'true' if arc_data_controller_auto_upload_metrics == 'true' else 'false'\n", "auto_upload_logs_value = 'true' if arc_data_controller_auto_upload_logs == 'true' else 'false'\n", - "\n", "auto_upload_metrics = f' --auto-upload-metrics {auto_upload_metrics_value}' if not is_indirect else ''\n", "auto_upload_logs = f' --auto-upload-logs {auto_upload_logs_value}' if not is_indirect else ''\n", "\n", "if os.name == 'nt':\n", " print(f'If you don\\'t see output produced by az, you can run the following command in a terminal window to check the deployment status:\\n\\t {os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {arc_data_controller_namespace}')\n", - "run_command(f'az arcdata dc create --connectivity-mode {arc_data_controller_connectivity_mode} --name {arc_data_controller_name}{namespace} --subscription {arc_subscription} --resource-group {arc_resource_group} --location {arc_data_controller_location} --storage-class {arc_data_controller_storage_class} --profile-name {arc_profile} --infrastructure {arc_infrastructure}{custom_location}{auto_upload_metrics}{auto_upload_logs}{use_k8s}')\n", + "run_command(f'az arcdata dc create --connectivity-mode {arc_data_controller_connectivity_mode} --name {arc_data_controller_name}{namespace} --subscription {arc_subscription} --resource-group {arc_resource_group} --location {arc_data_controller_location} --storage-class {arc_data_controller_storage_class} --profile-name {arc_profile} --infrastructure {arc_infrastructure}{custom_location}{cluster_name}{auto_upload_metrics}{auto_upload_logs}{use_k8s}')\n", "print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') " ] }, diff --git a/extensions/arc/package.json b/extensions/arc/package.json index cfd01ad63a..74d7feee27 100644 --- a/extensions/arc/package.json +++ b/extensions/arc/package.json @@ -360,6 +360,49 @@ "target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE", "value": "Direct" } + }, + { + "type": "options", + "label": "%arc.data.controller.log.analytics.workspace.names%", + "variableName": "AZDATA_NB_VAR_LOG_ANALYTICS_WORKSPACE_NAMES", + "required": true, + "options": { + "source": { + "providerId": "arc.logAnalyticsWorkspaceNames" + }, + "optionsType": "dropdown" + }, + "enabled": { + "target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_AUTO_UPLOAD_LOGS", + "value": "true" + } + }, + { + "type": "text", + "label": "%arc.data.controller.log.analytics.workspace.id%", + "required": true, + "isEvaluated": true, + "variableName": "AZDATA_NB_VAR_LOG_ANALYTICS_WORKSPACE_ID", + "valueProvider": { + "providerId": "workspace-name-to-id", + "triggerFields": [ + "AZDATA_NB_VAR_LOG_ANALYTICS_WORKSPACE_NAMES" + ] + }, + "enabled": { + "target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_AUTO_UPLOAD_LOGS", + "value": "true" + } + }, + { + "type": "text", + "label": "%arc.data.controller.log.analytics.primary.key%", + "required": true, + "variableName": "AZDATA_NB_VAR_LOG_ANALYTICS_PRIMARY_KEY", + "enabled": { + "target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_AUTO_UPLOAD_LOGS", + "value": "true" + } } ] }, @@ -1390,6 +1433,9 @@ "resourceDeploymentOptionsSources": [ { "id": "arc.controllers" + }, + { + "id": "arc.logAnalyticsWorkspaceNames" } ] }, diff --git a/extensions/arc/package.nls.json b/extensions/arc/package.nls.json index e0d1b17f57..2c99b5b11b 100644 --- a/extensions/arc/package.nls.json +++ b/extensions/arc/package.nls.json @@ -43,6 +43,9 @@ "arc.data.controller.auto.upload.metrics.description": "Enable the automatic upload of metrics. Direct mode only.", "arc.data.controller.auto.upload.logs": "Auto-upload Logs", "arc.data.controller.auto.upload.logs.description": "Enable the automatic upload of logs. Direct mode only.", + "arc.data.controller.log.analytics.workspace.names": "Log Analytics workspace", + "arc.data.controller.log.analytics.workspace.id": "Log Analytics workspace ID", + "arc.data.controller.log.analytics.primary.key": "Log Analytics primary key", "arc.data.controller.metrics.and.logs.dashboard.credentials.title": "Metrics and Logs Dashboard Credentials", "arc.data.controller.metrics.and.logs.dashboard.credentials.username": "Username", diff --git a/extensions/arc/src/common/workspaceUtils.ts b/extensions/arc/src/common/workspaceUtils.ts new file mode 100644 index 0000000000..a3c93a9fcb --- /dev/null +++ b/extensions/arc/src/common/workspaceUtils.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { InputValueType } from 'resource-deployment'; +import * as azExt from 'az-ext'; +import * as vscode from 'vscode'; +import { errorListingLogAnalyticsWorkspaces } from '../localizedConstants'; + +export const licenseTypeVarName = 'AZDATA_NB_VAR_LOG_ANALYTICS_WORKSPACE_NAMES'; + +// Gets the Log Analytics workspace id from the workspace name. +export async function getWorkspaceIdFromName(triggerFields: { [key: string]: InputValueType }): Promise { + try { + const _azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports; + const workspaces = await _azApi.az.monitor.logAnalytics.workspace.list(); + const targetWorkspace = workspaces.stdout.find(workspace => workspace.name === triggerFields[licenseTypeVarName]); + if (targetWorkspace) { + return targetWorkspace.customerId; + } else { + return undefined; + } + } catch (e) { + vscode.window.showErrorMessage(errorListingLogAnalyticsWorkspaces(e)); + throw e; + } +} diff --git a/extensions/arc/src/extension.ts b/extensions/arc/src/extension.ts index 6544898fc7..1977fcc0b7 100644 --- a/extensions/arc/src/extension.ts +++ b/extensions/arc/src/extension.ts @@ -15,6 +15,8 @@ import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider'; import { ControllerTreeNode } from './ui/tree/controllerTreeNode'; import { TreeNode } from './ui/tree/treeNode'; import * as pricing from './common/pricingUtils'; +import * as workspace from './common/workspaceUtils'; +import { LogAnalyticsWorkspaceOptionsSourceProvider } from './providers/logAnalyticsWorkspaceOptionsSourceProvider'; export async function activate(context: vscode.ExtensionContext): Promise { IconPathHelper.setExtensionContext(context); @@ -61,6 +63,15 @@ export async function activate(context: vscode.ExtensionContext): Promisevscode.extensions.getExtension(rd.extension.name)?.exports; context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider))); + context.subscriptions.push(rdApi.registerOptionsSourceProvider(new LogAnalyticsWorkspaceOptionsSourceProvider())); + + // Register valueprovider for getting the Log Analytics workspace id from the workspace name. + context.subscriptions.push(rdApi.registerValueProvider({ + id: 'workspace-name-to-id', + getValue: async (triggerFields: { [key: string]: rd.InputValueType }) => { + return workspace.getWorkspaceIdFromName(triggerFields); + } + })); // Register valueprovider for getting the calculated cost per VCore. context.subscriptions.push(rdApi.registerValueProvider({ diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index 8c6bf48cdb..2ec423d93c 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -334,3 +334,4 @@ export const userCancelledError = localize('arc.userCancelledError', "User cance export const clusterContextConfigNoLongerValid = (configFile: string, clusterContext: string, error: any) => localize('clusterContextConfigNoLongerValid', "The cluster context information specified by config file: {0} and cluster context: {1} is no longer valid. Error is:\n\t{2}\n Do you want to update this information?", configFile, clusterContext, getErrorMessage(error)); export const invalidConfigPath = localize('arc.invalidConfigPath', "Invalid config path"); export const loadingClusterContextsError = (error: any): string => localize('arc.loadingClusterContextsError', "Error loading cluster contexts. {0}", getErrorMessage(error)); +export function errorListingLogAnalyticsWorkspaces(error: any): string { return localize('arc.errorListingLogAnalyticsWorkspaces', "Error listing Log Analytics workspaces {0}", getErrorMessage(error, true)); } diff --git a/extensions/arc/src/providers/logAnalyticsWorkspaceOptionsSourceProvider.ts b/extensions/arc/src/providers/logAnalyticsWorkspaceOptionsSourceProvider.ts new file mode 100644 index 0000000000..bc46463c62 --- /dev/null +++ b/extensions/arc/src/providers/logAnalyticsWorkspaceOptionsSourceProvider.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azExt from 'az-ext'; +import * as rd from 'resource-deployment'; +import * as vscode from 'vscode'; +import { errorListingLogAnalyticsWorkspaces } from '../localizedConstants'; + +/** + * Class that provides options sources for Log Analytics workspace names + */ +export class LogAnalyticsWorkspaceOptionsSourceProvider implements rd.IOptionsSourceProvider { + readonly id = 'arc.logAnalyticsWorkspaceNames'; + private readonly _azApi: azExt.IExtension; + + constructor() { + this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports; + } + + public async getOptions(): Promise { + try { + const workspacesListResult = await this._azApi.az.monitor.logAnalytics.workspace.list(); + return workspacesListResult.stdout.map(workspace => workspace.name); + } catch (err) { + vscode.window.showErrorMessage(errorListingLogAnalyticsWorkspaces(err)); + throw err; + } + } +} diff --git a/extensions/azcli/src/api.ts b/extensions/azcli/src/api.ts index 87369249f3..74ccdfaec3 100644 --- a/extensions/azcli/src/api.ts +++ b/extensions/azcli/src/api.ts @@ -154,6 +154,17 @@ export function getAzApi(localAzDiscovered: Promise, azTool } } }, + monitor: { + logAnalytics: { + workspace: { + list: async (resourceGroup?: string, subscription?: string, additionalEnvVars?: azExt.AdditionalEnvVars) => { + await localAzDiscovered; + validateAz(azToolService.localAz); + return azToolService.localAz!.monitor.logAnalytics.workspace.list(resourceGroup, subscription, additionalEnvVars); + } + } + } + }, getPath: async () => { await localAzDiscovered; throwIfNoAz(azToolService.localAz); diff --git a/extensions/azcli/src/az.ts b/extensions/azcli/src/az.ts index d071546b25..f363726e68 100644 --- a/extensions/azcli/src/az.ts +++ b/extensions/azcli/src/az.ts @@ -209,6 +209,19 @@ export class AzTool implements azExt.IAzApi { } }; + public monitor = { + logAnalytics: { + workspace: { + list: (resourceGroup?: string, subscription?: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise> => { + const argsArray = ['monitor', 'log-analytics', 'workspace', 'list']; + if (resourceGroup) { argsArray.push('--resource-group', resourceGroup); } + if (subscription) { argsArray.push('--subscription', subscription); } + return this.executeCommand(argsArray, additionalEnvVars); + } + } + } + }; + /** * Gets the output of running '--version' command on the az tool. diff --git a/extensions/azcli/src/typings/az-ext.d.ts b/extensions/azcli/src/typings/az-ext.d.ts index f786784017..e1c04343a9 100644 --- a/extensions/azcli/src/typings/az-ext.d.ts +++ b/extensions/azcli/src/typings/az-ext.d.ts @@ -187,6 +187,45 @@ declare module 'az-ext' { state: string //Completed } + export interface LogAnalyticsWorkspaceListResult { + createdDate: string, // "2020-02-25T16:59:38Z" + customerId: string, // "7e136a79-c0b6-4878-86bf-7bf7a6a7e6f6", + eTag: string, // null, + etag: string, // "\"00006df1-0000-0700-0000-61ee552f0000\"", + features: { + clusterResourceId: string, // null, + disableLocalAuth: boolean, // null, + enableDataExport: boolean, // null, + enableLogAccessUsingOnlyResourcePermissions: boolean, //true, + immediatePurgeDataOn30Days: boolean, // null, + legacy: number, // 0, + searchVersion: number // 1 + }, + forceCmkForQuery: boolean, // null, + id: string, // "/subscriptions/a5082b19-8a6e-4bc5-8fdd-8ef39dfebc39/resourcegroups/bugbash/providers/microsoft.operationalinsights/workspaces/bugbash-logs", + location: string, // "westus", + modifiedDate: string, // "2022-02-21T09:18:22.3906451Z", + name: string, // "bugbash-logs", + privateLinkScopedResources: string, // null, + provisioningState: string, // "Succeeded", + publicNetworkAccessForIngestion: string, // "Enabled", + publicNetworkAccessForQuery: string, // "Enabled", + resourceGroup: string, // "bugbash", + retentionInDays: number, // 30, + sku: { + capacityReservationLevel: number, // null, + lastSkuUpdate: string, // "2020-02-25T16:59:38Z", + name: string, // "pergb2018" + }, + tags: string[], //null, + type: string, //"Microsoft.OperationalInsights/workspaces", + workspaceCapping: { + dailyQuotaGb: number, //-1.0, + dataIngestionStatus: string, // "RespectQuota", + quotaNextResetTime: string, // "2022-02-21T19:00:00Z" + } + } + export interface PostgresServerShowResult { apiVersion: string, // "arcdata.microsoft.com/v1alpha1" kind: string, // "postgresql" @@ -362,6 +401,17 @@ declare module 'az-ext' { ): Promise> } }, + monitor: { + logAnalytics: { + workspace: { + list( + resourceGroup?: string, // test-rg + subscription?: string, // 122c121a-095a-4f5d-22e4-cc6b238490a3 + additionalEnvVars?: AdditionalEnvVars + ): Promise> + } + } + }, getPath(): Promise, /** * The semVersion corresponding to this installation of the Azure CLI. version() method should have been run