mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
controller dropdown field to SQL MIAA and Postgres deployment. (#12217)
* saving first draft * throw if no controllers * cleanup * bug fixes * bug fixes and caching controller access * pr comments and bug fixes. * fixes * fixes * comment fix * remove debug prints * comment fixes * remove debug logs * inputValueTransformer returns string|Promise * PR feedback * pr fixes * remove _ from protected fields * anonymous to full methods * small fixes
This commit is contained in:
@@ -85,16 +85,36 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Required Values\n",
|
"# Required Values\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" server_group_name = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\"]\n",
|
" server_group_name = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME was not defined. Exiting\\n')\n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" postgres_password = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
" postgres_password = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD was not defined. Exiting\\n') \n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" postgres_storage_class_data = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\"]\n",
|
" postgres_storage_class_data = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\"]\n",
|
||||||
@@ -159,6 +179,21 @@
|
|||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"# Login to the data controller.\n",
|
||||||
|
"#\n",
|
||||||
|
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
||||||
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
|
"out=run_command()"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "71366399-5963-4e24-b2f2-6bb5bffba4ec"
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
|
|||||||
@@ -85,21 +85,42 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Required Values\n",
|
"# Required Values\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_INSTANCE_NAME\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_INSTANCE_NAME\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_instance_name = os.environ[\"AZDATA_NB_VAR_SQL_INSTANCE_NAME\"]\n",
|
" mssql_instance_name = os.environ[\"AZDATA_NB_VAR_SQL_INSTANCE_NAME\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_INSTANCE_NAME was not defined. Exiting\\n')\n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_INSTANCE_NAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_PASSWORD\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_PASSWORD\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
" mssql_password = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_PASSWORD was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_storage_class_data = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\"]\n",
|
" mssql_storage_class_data = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_storage_class_logs = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\"]\n",
|
" mssql_storage_class_logs = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\"]\n",
|
||||||
@@ -123,6 +144,21 @@
|
|||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"# Login to the data controller.\n",
|
||||||
|
"#\n",
|
||||||
|
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
||||||
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
|
"out=run_command()"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "1437c536-17e8-4a7f-80c1-aa43ad02686c"
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
|
|||||||
@@ -599,6 +599,25 @@
|
|||||||
{
|
{
|
||||||
"title": "%arc.sql.settings.section.title%",
|
"title": "%arc.sql.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%arc.controller%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
||||||
|
"type": "options",
|
||||||
|
"editable": false,
|
||||||
|
"required": true,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
},
|
||||||
|
"labelWidth": "100%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.sql.instance.name%",
|
"label": "%arc.sql.instance.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
||||||
@@ -711,6 +730,25 @@
|
|||||||
{
|
{
|
||||||
"title": "%arc.postgres.settings.section.title%",
|
"title": "%arc.postgres.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%arc.controller%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
||||||
|
"type": "options",
|
||||||
|
"editable": false,
|
||||||
|
"required": true,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
},
|
||||||
|
"labelWidth": "100%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.name%",
|
"label": "%arc.postgres.server.group.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
||||||
|
|||||||
@@ -76,6 +76,9 @@
|
|||||||
"resource.type.picker.display.name": "Resource Type",
|
"resource.type.picker.display.name": "Resource Type",
|
||||||
"sql.managed.instance.display.name": "Azure SQL managed instance - Azure Arc",
|
"sql.managed.instance.display.name": "Azure SQL managed instance - Azure Arc",
|
||||||
"postgres.server.group.display.name": "PostgreSQL server groups - Azure Arc",
|
"postgres.server.group.display.name": "PostgreSQL server groups - Azure Arc",
|
||||||
|
|
||||||
|
"arc.controller": "Target Azure Arc Controller",
|
||||||
|
|
||||||
"arc.sql.new.dialog.title": "Deploy Azure SQL managed instance - Azure Arc (preview)",
|
"arc.sql.new.dialog.title": "Deploy Azure SQL managed instance - Azure Arc (preview)",
|
||||||
"arc.sql.settings.section.title": "SQL Connection information",
|
"arc.sql.settings.section.title": "SQL Connection information",
|
||||||
"arc.azure.section.title": "Azure information",
|
"arc.azure.section.title": "Azure information",
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
|
|
||||||
import * as arc from 'arc';
|
import * as arc from 'arc';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { UserCancelledError } from './common/utils';
|
||||||
import { IconPathHelper, refreshActionId } from './constants';
|
import { IconPathHelper, refreshActionId } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
import { ConnectToControllerDialog } from './ui/dialogs/connectControllerDialog';
|
import { ConnectToControllerDialog, PasswordToControllerDialog } from './ui/dialogs/connectControllerDialog';
|
||||||
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
||||||
import { TreeNode } from './ui/tree/treeNode';
|
import { TreeNode } from './ui/tree/treeNode';
|
||||||
@@ -57,11 +58,24 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
|||||||
await checkArcDeploymentExtension();
|
await checkArcDeploymentExtension();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRegisteredDataControllers: async () => {
|
getRegisteredDataControllers: async () => (await treeDataProvider.getChildren())
|
||||||
return (await treeDataProvider.getChildren())
|
|
||||||
.filter(node => node instanceof ControllerTreeNode)
|
.filter(node => node instanceof ControllerTreeNode)
|
||||||
.map(node => (node as ControllerTreeNode).model.info);
|
.map(node => ({
|
||||||
|
label: (node as ControllerTreeNode).model.label,
|
||||||
|
info: (node as ControllerTreeNode).model.info
|
||||||
|
})),
|
||||||
|
getControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
|
||||||
|
return await treeDataProvider.getPassword(controllerInfo);
|
||||||
|
},
|
||||||
|
reacquireControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
|
||||||
|
let model;
|
||||||
|
const dialog = new PasswordToControllerDialog(treeDataProvider);
|
||||||
|
dialog.showDialog(controllerInfo);
|
||||||
|
model = await dialog.waitForClose();
|
||||||
|
if (!model) {
|
||||||
|
throw new UserCancelledError();
|
||||||
|
}
|
||||||
|
return model.password;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const indirect = localize('arc.indirect', "Indirect");
|
|||||||
export const loading = localize('arc.loading', "Loading...");
|
export const loading = localize('arc.loading', "Loading...");
|
||||||
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
||||||
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
||||||
|
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
||||||
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
||||||
export const controllerName = localize('arc.controllerName', "Name");
|
export const controllerName = localize('arc.controllerName', "Name");
|
||||||
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
||||||
@@ -81,6 +82,7 @@ export const password = localize('arc.password', "Password");
|
|||||||
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
||||||
export const connect = localize('arc.connect', "Connect");
|
export const connect = localize('arc.connect', "Connect");
|
||||||
export const cancel = localize('arc.cancel', "Cancel");
|
export const cancel = localize('arc.cancel', "Cancel");
|
||||||
|
export const ok = localize('arc.ok', "Ok");
|
||||||
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
||||||
|
|
||||||
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
||||||
@@ -156,3 +158,6 @@ export function invalidResourceDeletionName(name: string): string { return local
|
|||||||
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
||||||
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
||||||
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error)); }
|
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error)); }
|
||||||
|
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
|
||||||
|
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again.");
|
||||||
|
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
|
||||||
|
|||||||
8
extensions/arc/src/typings/arc.d.ts
vendored
8
extensions/arc/src/typings/arc.d.ts
vendored
@@ -35,7 +35,13 @@ declare module 'arc' {
|
|||||||
resources: ResourceInfo[]
|
resources: ResourceInfo[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface DataController {
|
||||||
|
label: string,
|
||||||
|
info: ControllerInfo
|
||||||
|
}
|
||||||
export interface IExtension {
|
export interface IExtension {
|
||||||
getRegisteredDataControllers(): Promise<ControllerInfo[]>;
|
getRegisteredDataControllers(): Promise<DataController[]>;
|
||||||
|
getControllerPassword(controllerInfo: ControllerInfo): Promise<string>;
|
||||||
|
reacquireControllerPassword(controllerInfo: ControllerInfo, password: string, retryCount?: number): Promise<string>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { ControllerInfo } from 'arc';
|
import { ControllerInfo } from 'arc';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { Deferred } from '../../common/promise';
|
import { Deferred } from '../../common/promise';
|
||||||
@@ -12,32 +13,44 @@ import * as loc from '../../localizedConstants';
|
|||||||
import { ControllerModel } from '../../models/controllerModel';
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
import { InitializingComponent } from '../components/initializingComponent';
|
import { InitializingComponent } from '../components/initializingComponent';
|
||||||
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
||||||
|
import { getErrorMessage } from '../../common/utils';
|
||||||
|
|
||||||
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
||||||
export class ConnectToControllerDialog extends InitializingComponent {
|
abstract class ControllerDialogBase extends InitializingComponent {
|
||||||
private modelBuilder!: azdata.ModelBuilder;
|
protected modelBuilder!: azdata.ModelBuilder;
|
||||||
|
protected dialog: azdata.window.Dialog;
|
||||||
|
|
||||||
private urlInputBox!: azdata.InputBoxComponent;
|
protected urlInputBox!: azdata.InputBoxComponent;
|
||||||
private nameInputBox!: azdata.InputBoxComponent;
|
protected nameInputBox!: azdata.InputBoxComponent;
|
||||||
private usernameInputBox!: azdata.InputBoxComponent;
|
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||||
private passwordInputBox!: azdata.InputBoxComponent;
|
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||||
private rememberPwCheckBox!: azdata.CheckBoxComponent;
|
|
||||||
|
|
||||||
private _completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
|
||||||
|
return [
|
||||||
private _id!: string;
|
{
|
||||||
|
component: this.urlInputBox,
|
||||||
constructor(private _treeDataProvider: AzureArcTreeDataProvider) {
|
title: loc.controllerUrl,
|
||||||
super();
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.nameInputBox,
|
||||||
|
title: loc.controllerName,
|
||||||
|
required: false
|
||||||
|
}, {
|
||||||
|
component: this.usernameInputBox,
|
||||||
|
title: loc.username,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.passwordInputBox,
|
||||||
|
title: loc.password,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public showDialog(controllerInfo?: ControllerInfo, password?: string): azdata.window.Dialog {
|
protected abstract fieldToFocusOn(): azdata.Component;
|
||||||
this._id = controllerInfo?.id ?? uuid();
|
protected readonlyFields(): azdata.InputBoxComponent[] { return []; }
|
||||||
const dialog = azdata.window.createModelViewDialog(loc.connectToController);
|
|
||||||
dialog.cancelButton.onClick(() => this.handleCancel());
|
|
||||||
dialog.registerContent(async view => {
|
|
||||||
this.modelBuilder = view.modelBuilder;
|
|
||||||
|
|
||||||
|
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||||
this.urlInputBox = this.modelBuilder.inputBox()
|
this.urlInputBox = this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
value: controllerInfo?.url,
|
value: controllerInfo?.url,
|
||||||
@@ -56,50 +69,80 @@ export class ConnectToControllerDialog extends InitializingComponent {
|
|||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
value: password
|
value: password
|
||||||
})
|
}).component();
|
||||||
.component();
|
}
|
||||||
|
|
||||||
|
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
||||||
|
protected id!: string;
|
||||||
|
|
||||||
|
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
||||||
|
super();
|
||||||
|
this.dialog = azdata.window.createModelViewDialog(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||||
|
this.id = controllerInfo?.id ?? uuid();
|
||||||
|
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
|
this.dialog.registerContent(async (view) => {
|
||||||
|
this.modelBuilder = view.modelBuilder;
|
||||||
|
this.initializeFields(controllerInfo, password);
|
||||||
|
|
||||||
|
let formModel = this.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
components: this.getComponents(),
|
||||||
|
title: ''
|
||||||
|
}]).withLayout({ width: '100%' }).component();
|
||||||
|
await view.initializeModel(formModel);
|
||||||
|
await this.fieldToFocusOn().focus();
|
||||||
|
this.readonlyFields().forEach(f => f.readOnly = true);
|
||||||
|
this.initialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.registerCloseValidator(async () => await this.validate());
|
||||||
|
this.dialog.okButton.label = loc.connect;
|
||||||
|
this.dialog.cancelButton.label = loc.cancel;
|
||||||
|
azdata.window.openDialog(this.dialog);
|
||||||
|
return this.dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract async validate(): Promise<boolean>;
|
||||||
|
|
||||||
|
private handleCancel(): void {
|
||||||
|
this.completionPromise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
|
||||||
|
return this.completionPromise.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectToControllerDialog extends ControllerDialogBase {
|
||||||
|
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
|
||||||
|
protected fieldToFocusOn() {
|
||||||
|
return this.urlInputBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getComponents() {
|
||||||
|
return [
|
||||||
|
...super.getComponents(),
|
||||||
|
{
|
||||||
|
component: this.rememberPwCheckBox,
|
||||||
|
title: ''
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||||
|
super.initializeFields(controllerInfo, password);
|
||||||
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||||
.withProperties<azdata.CheckBoxProperties>({
|
.withProperties<azdata.CheckBoxProperties>({
|
||||||
label: loc.rememberPassword,
|
label: loc.rememberPassword,
|
||||||
checked: controllerInfo?.rememberPassword
|
checked: controllerInfo?.rememberPassword
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
let formModel = this.modelBuilder.formContainer()
|
|
||||||
.withFormItems([{
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
component: this.urlInputBox,
|
|
||||||
title: loc.controllerUrl,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.nameInputBox,
|
|
||||||
title: loc.controllerName,
|
|
||||||
required: false
|
|
||||||
}, {
|
|
||||||
component: this.usernameInputBox,
|
|
||||||
title: loc.username,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.passwordInputBox,
|
|
||||||
title: loc.password,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.rememberPwCheckBox,
|
|
||||||
title: ''
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
title: ''
|
|
||||||
}]).withLayout({ width: '100%' }).component();
|
|
||||||
await view.initializeModel(formModel);
|
|
||||||
this.urlInputBox.focus();
|
|
||||||
this.initialized = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.registerCloseValidator(async () => await this.validate());
|
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
dialog.okButton.label = loc.connect;
|
super(treeDataProvider, loc.connectToController);
|
||||||
dialog.cancelButton.label = loc.cancel;
|
|
||||||
azdata.window.openDialog(dialog);
|
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(): Promise<boolean> {
|
public async validate(): Promise<boolean> {
|
||||||
@@ -120,32 +163,86 @@ export class ConnectToControllerDialog extends InitializingComponent {
|
|||||||
url = `${url}:30080`;
|
url = `${url}:30080`;
|
||||||
}
|
}
|
||||||
const controllerInfo: ControllerInfo = {
|
const controllerInfo: ControllerInfo = {
|
||||||
id: this._id,
|
id: this.id,
|
||||||
url: url,
|
url: url,
|
||||||
name: this.nameInputBox.value ?? '',
|
name: this.nameInputBox.value ?? '',
|
||||||
username: this.usernameInputBox.value,
|
username: this.usernameInputBox.value,
|
||||||
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
||||||
resources: []
|
resources: []
|
||||||
};
|
};
|
||||||
const controllerModel = new ControllerModel(this._treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
try {
|
try {
|
||||||
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
||||||
await controllerModel.refresh(false);
|
await controllerModel.refresh(false);
|
||||||
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
||||||
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vscode.window.showErrorMessage(loc.connectToControllerFailed(this.urlInputBox.value, err));
|
this.dialog.message = {
|
||||||
|
text: loc.connectToControllerFailed(this.urlInputBox.value, err),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this._completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class PasswordToControllerDialog extends ControllerDialogBase {
|
||||||
|
|
||||||
|
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
|
super(treeDataProvider, loc.passwordToController);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fieldToFocusOn() {
|
||||||
|
return this.passwordInputBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonlyFields() {
|
||||||
|
return [
|
||||||
|
this.urlInputBox,
|
||||||
|
this.nameInputBox,
|
||||||
|
this.usernameInputBox
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(): Promise<boolean> {
|
||||||
|
if (!this.passwordInputBox.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
try {
|
||||||
|
await azdataApi.azdata.login(this.urlInputBox.value!, this.usernameInputBox.value!, this.passwordInputBox.value);
|
||||||
|
} catch (e) {
|
||||||
|
if (getErrorMessage(e).match(/Wrong username or password/i)) {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: loc.invalidPassword,
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: loc.errorVerifyingPassword(e),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const controllerInfo: ControllerInfo = {
|
||||||
|
id: this.id,
|
||||||
|
url: this.urlInputBox.value!,
|
||||||
|
name: this.nameInputBox.value!,
|
||||||
|
username: this.usernameInputBox.value!,
|
||||||
|
rememberPassword: false,
|
||||||
|
resources: []
|
||||||
|
};
|
||||||
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
|
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleCancel(): void {
|
public showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
|
||||||
this._completionPromise.resolve(undefined);
|
const dialog = super.showDialog(controllerInfo);
|
||||||
}
|
dialog.okButton.label = loc.ok;
|
||||||
|
return dialog;
|
||||||
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
|
|
||||||
return this._completionPromise.promise;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { SemVer } from 'semver';
|
import { SemVer } from 'semver';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
@@ -13,7 +14,6 @@ import Logger from './common/logger';
|
|||||||
import { getErrorMessage, searchForCmd } from './common/utils';
|
import { getErrorMessage, searchForCmd } from './common/utils';
|
||||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataHostname, azdataInstallKey, azdataReleaseJson, azdataUpdateKey, azdataUri, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataHostname, azdataInstallKey, azdataReleaseJson, azdataUpdateKey, azdataUri, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const enum AzdataDeployOption {
|
const enum AzdataDeployOption {
|
||||||
dontPrompt = 'dontPrompt',
|
dontPrompt = 'dontPrompt',
|
||||||
@@ -136,7 +136,7 @@ export class AzdataTool implements IAzdataTool {
|
|||||||
// to get the correct stderr out. The actual value we get is something like
|
// to get the correct stderr out. The actual value we get is something like
|
||||||
// ERROR: { stderr: '...' }
|
// ERROR: { stderr: '...' }
|
||||||
// so we also need to trim off the start that isn't a valid JSON blob
|
// so we also need to trim off the start that isn't a valid JSON blob
|
||||||
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'))).stderr;
|
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'), err.stderr.indexOf('}') + 1)).stderr;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// it means this was probably some other generic error (such as command not being found)
|
// it means this was probably some other generic error (such as command not being found)
|
||||||
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const decline = localize('azdata.decline', "Decline");
|
|||||||
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
||||||
export const askLater = localize('azdata.askLater', "Ask Later");
|
export const askLater = localize('azdata.askLater', "Ask Later");
|
||||||
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
||||||
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command \"{0} {1}\"", command, args?.join(' '));
|
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
|
||||||
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
||||||
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
||||||
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of azdata");
|
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of azdata");
|
||||||
|
|||||||
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Deferred } from './promise';
|
||||||
|
|
||||||
|
const enum Status {
|
||||||
|
notStarted,
|
||||||
|
inProgress,
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State<T> {
|
||||||
|
entry?: T,
|
||||||
|
error?: Error,
|
||||||
|
status: Status,
|
||||||
|
id: number,
|
||||||
|
pendingOperation: Deferred<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of Cache Manager which ensures that only one call to populate cache miss is pending at a given time.
|
||||||
|
* All remaining calls for retrieval are awaited until the one in progress finishes and then all awaited calls are resolved with the value
|
||||||
|
* from the cache.
|
||||||
|
*/
|
||||||
|
export class CacheManager<K, T> {
|
||||||
|
private _cache = new Map<K, State<T>>();
|
||||||
|
private _id = 0;
|
||||||
|
|
||||||
|
public async getCacheEntry(key: K, retrieveEntry: (key: K) => Promise<T>): Promise<T> {
|
||||||
|
const cacheHit: State<T> | undefined = this._cache.get(key);
|
||||||
|
// each branch either throws or returns the password.
|
||||||
|
if (cacheHit === undefined) {
|
||||||
|
// populate a new state entry and add it to the cache
|
||||||
|
const state: State<T> = {
|
||||||
|
status: Status.notStarted,
|
||||||
|
id: this._id++,
|
||||||
|
pendingOperation: new Deferred<void>()
|
||||||
|
};
|
||||||
|
this._cache.set(key, state);
|
||||||
|
// now that we have the state entry initialized, retry to fetch the cacheEntry
|
||||||
|
let returnValue: T = await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
await state.pendingOperation;
|
||||||
|
return returnValue!;
|
||||||
|
} else {
|
||||||
|
switch (cacheHit.status) {
|
||||||
|
case Status.notStarted: {
|
||||||
|
cacheHit.status = Status.inProgress;
|
||||||
|
// retrieve and populate the missed cache hit.
|
||||||
|
try {
|
||||||
|
cacheHit.entry = await retrieveEntry(key);
|
||||||
|
} catch (error) {
|
||||||
|
cacheHit.error = error;
|
||||||
|
} finally {
|
||||||
|
cacheHit.status = Status.done;
|
||||||
|
// we do not reject here even in error case because we do not want our awaits on pendingOperation to throw
|
||||||
|
// We track our own error state and when all done we throw if an error had happened. This results
|
||||||
|
// in the rejection of the promised returned by this method.
|
||||||
|
cacheHit.pendingOperation.resolve();
|
||||||
|
}
|
||||||
|
return await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Status.inProgress: {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
return await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Status.done: {
|
||||||
|
if (cacheHit.error !== undefined) {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
throw cacheHit.error;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
return cacheHit.entry!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
100
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as arc from 'arc';
|
||||||
|
import { CategoryValue } from 'azdata';
|
||||||
|
import { IOptionsSource } from '../interfaces';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
import { apiService } from '../services/apiService';
|
||||||
|
import { throwUnless } from '../utils';
|
||||||
|
import { CacheManager } from './cacheManager';
|
||||||
|
|
||||||
|
|
||||||
|
export type OptionsSourceType = 'ArcControllersOptionsSource';
|
||||||
|
|
||||||
|
const OptionsSources = new Map<OptionsSourceType, new () => OptionsSource>();
|
||||||
|
export abstract class OptionsSource implements IOptionsSource {
|
||||||
|
|
||||||
|
private _variableNames!: { [index: string]: string; };
|
||||||
|
private _type!: OptionsSourceType;
|
||||||
|
|
||||||
|
get type(): OptionsSourceType { return this._type; }
|
||||||
|
get variableNames(): { [index: string]: string; } { return this._variableNames; }
|
||||||
|
|
||||||
|
abstract async getOptions(): Promise<string[] | CategoryValue[]>;
|
||||||
|
abstract async getVariableValue(variableName: string, input: string): Promise<string>;
|
||||||
|
abstract getIsPassword(variableName: string): boolean;
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static construct(optionsSourceType: OptionsSourceType, variableNames: { [index: string]: string }): OptionsSource {
|
||||||
|
const sourceConstructor = OptionsSources.get(optionsSourceType);
|
||||||
|
throwUnless(sourceConstructor !== undefined, loc.noOptionsSourceDefined(optionsSourceType));
|
||||||
|
const obj = new sourceConstructor();
|
||||||
|
obj._type = optionsSourceType;
|
||||||
|
obj._variableNames = variableNames;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArcControllersOptionsSource extends OptionsSource {
|
||||||
|
private _cacheManager = new CacheManager<string, string>();
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOptions(): Promise<string[] | CategoryValue[]> {
|
||||||
|
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||||
|
throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected);
|
||||||
|
return controllers.map(ci => {
|
||||||
|
return ci.label;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
||||||
|
const retrieveVariable = async (key: string) => {
|
||||||
|
const [variableName, controllerLabel] = JSON.parse(key);
|
||||||
|
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||||
|
const controller = controllers!.find(ci => ci.label === controllerLabel);
|
||||||
|
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
||||||
|
switch (variableName) {
|
||||||
|
case 'endpoint':
|
||||||
|
return controller.info.url;
|
||||||
|
case 'username':
|
||||||
|
return controller.info.username;
|
||||||
|
case 'password':
|
||||||
|
const fetchedPassword = await this.getPassword(controller);
|
||||||
|
return fetchedPassword;
|
||||||
|
default:
|
||||||
|
throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const variableValue = await this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), retrieveVariable);
|
||||||
|
return variableValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPassword(controller: arc.DataController): Promise<string> {
|
||||||
|
let password = await apiService.arcApi.getControllerPassword(controller.info);
|
||||||
|
if (!password) {
|
||||||
|
password = await apiService.arcApi.reacquireControllerPassword(controller.info, password);
|
||||||
|
}
|
||||||
|
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsPassword(variableName: string): boolean {
|
||||||
|
switch (variableName) {
|
||||||
|
case 'endpoint':
|
||||||
|
case 'username':
|
||||||
|
return false;
|
||||||
|
case 'password':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OptionsSources.set(<OptionsSourceType>ArcControllersOptionsSource.name, ArcControllersOptionsSource);
|
||||||
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deferred promise
|
||||||
|
*/
|
||||||
|
export class Deferred<T> {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve!: (value?: T | PromiseLike<T>) => void;
|
||||||
|
reject!: (reason?: any) => void;
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { OptionsSource, OptionsSourceType } from './helpers/optionSources';
|
||||||
|
|
||||||
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
||||||
|
|
||||||
@@ -172,9 +173,18 @@ export type ComponentCSSStyles = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IOptionsSource {
|
||||||
|
readonly type: OptionsSourceType,
|
||||||
|
readonly variableNames: { [index: string]: string; },
|
||||||
|
getOptions(): Promise<string[] | azdata.CategoryValue[]>,
|
||||||
|
getVariableValue(variableName: string, input: string): Promise<string>;
|
||||||
|
getIsPassword(variableName: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface OptionsInfo {
|
export interface OptionsInfo {
|
||||||
values: string[] | azdata.CategoryValue[],
|
values?: string[] | azdata.CategoryValue[],
|
||||||
|
source?: OptionsSource,
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
optionsType?: OptionsType
|
optionsType?: OptionsType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
|
import { FieldType, OptionsType } from './interfaces';
|
||||||
|
import { OptionsSourceType } from './helpers/optionSources';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -13,12 +15,22 @@ export const resourceGroup = localize('azure.account.resourceGroup', "Resource G
|
|||||||
export const location = localize('azure.account.location', "Azure Location");
|
export const location = localize('azure.account.location', "Azure Location");
|
||||||
export const browse = localize('filePicker.browse', "Browse");
|
export const browse = localize('filePicker.browse', "Browse");
|
||||||
export const select = localize('filePicker.select', "Select");
|
export const select = localize('filePicker.select', "Select");
|
||||||
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePatht', "Kube config file path");
|
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePath', "Kube config file path");
|
||||||
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
|
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
|
||||||
export const signIn = localize('azure.signin', "Sign in…");
|
export const signIn = localize('azure.signin', "Sign in…");
|
||||||
export const refresh = localize('azure.refresh', "Refresh");
|
export const refresh = localize('azure.refresh', "Refresh");
|
||||||
export const createNewResourceGroup = localize('azure.resourceGroup.createNewResourceGroup', "Create a new resource group");
|
export const createNewResourceGroup = localize('azure.resourceGroup.createNewResourceGroup', "Create a new resource group");
|
||||||
export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResourceGroupAriaLabel', "New resource group name");
|
export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResourceGroupAriaLabel', "New resource group name");
|
||||||
export const realm = localize('deployCluster.Realm', "Realm");
|
export const realm = localize('deployCluster.Realm', "Realm");
|
||||||
|
export const unexpectedOptionsSourceType = (type: OptionsSourceType) => localize('optionsSourceType.Invalid', "Invalid options source type:{0}", type);
|
||||||
|
export const unknownFieldTypeError = (type: FieldType) => localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", type);
|
||||||
|
export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName);
|
||||||
|
export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName);
|
||||||
|
export const noControllersConnected = localize('noControllersConnected', "No Azure ARC controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again");
|
||||||
|
export const noOptionsSourceDefined = (optionsSourceType: OptionsSourceType) => localize('noOptionsSourceDefined', "No OptionsSource defined for type: {0}", optionsSourceType);
|
||||||
|
export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "controllerInfo could not be found with name: {0}", name);
|
||||||
|
export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName);
|
||||||
|
export const optionsNotDefined = (fieldType: FieldType) => localize('optionsNotDefined', "FieldInfo.options was not defined for field type: {0}", fieldType);
|
||||||
|
export const optionsNotObjectOrArray = localize('optionsNotObjectOrArray', "FieldInfo.options must be an object if it is not an array");
|
||||||
|
export const optionsTypeNotFound = localize('optionsTypeNotFound', "When FieldInfo.options is an object it must have 'optionsType' property");
|
||||||
|
export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown', "When optionsType is not {0} then it must be {1}", OptionsType.Radio, OptionsType.Dropdown);
|
||||||
|
|||||||
@@ -5,26 +5,17 @@
|
|||||||
|
|
||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as arc from 'arc';
|
||||||
|
|
||||||
export interface IApiService {
|
export interface IApiService {
|
||||||
getAzurecoreApi(): Promise<azurecore.IExtension>;
|
readonly azurecoreApi: azurecore.IExtension;
|
||||||
|
readonly arcApi: arc.IExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiService implements IApiService {
|
class ApiService implements IApiService {
|
||||||
|
|
||||||
private azurecoreApi: azurecore.IExtension | undefined;
|
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
public get azurecoreApi() { return vscode.extensions.getExtension(azurecore.extension.name)?.exports; }
|
||||||
public async getAzurecoreApi(): Promise<azurecore.IExtension> {
|
public get arcApi() { return vscode.extensions.getExtension(arc.extension.name)?.exports; }
|
||||||
if (!this.azurecoreApi) {
|
|
||||||
this.azurecoreApi = <azurecore.IExtension>(await vscode.extensions.getExtension(azurecore.extension.name)?.activate());
|
|
||||||
if (!this.azurecoreApi) {
|
|
||||||
throw new Error('Unable to retrieve azurecore API');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.azurecoreApi;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiService: IApiService = new ApiService();
|
export const apiService: IApiService = new ApiService();
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
import * as arc from 'arc';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
export class ArcService {
|
|
||||||
private _arcApi: arc.IExtension;
|
|
||||||
constructor() {
|
|
||||||
this._arcApi = vscode.extensions.getExtension(arc.extension.name)?.exports;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRegisteredDataControllers(): Promise<arc.ControllerInfo[]> {
|
|
||||||
return await this._arcApi.getRegisteredDataControllers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,8 @@ import { apiService } from '../services/apiService';
|
|||||||
|
|
||||||
suite('API Service Tests', function (): void {
|
suite('API Service Tests', function (): void {
|
||||||
|
|
||||||
test('getAzurecoreApi returns azure api', async () => {
|
test('getAzurecoreApi returns azure api', () => {
|
||||||
const api = await apiService.getAzurecoreApi();
|
const api = apiService.azurecoreApi;
|
||||||
assert(api !== undefined);
|
assert(api !== undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -176,11 +176,11 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,8 +289,8 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||||
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -400,8 +400,8 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
this.form.addFormItems(this.formItems);
|
this.form.addFormItems(this.formItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.hideCustomButtons();
|
this.wizard.hideCustomButtons();
|
||||||
this.wizard.wizardObject.message = { text: '' };
|
this.wizard.wizardObject.message = { text: '' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class TargetClusterContextPage extends WizardPageBase<DeployClusterWizard
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export class DeploymentInputDialog extends DialogBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
const model: Model = new Model();
|
const model: Model = new Model();
|
||||||
setModelValues(this.inputComponents, model);
|
await setModelValues(this.inputComponents, model);
|
||||||
if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) {
|
if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) {
|
||||||
model.setEnvironmentVariables();
|
model.setEnvironmentVariables();
|
||||||
if (this.dialogInfo.runNotebook) {
|
if (this.dialogInfo.runNotebook) {
|
||||||
@@ -110,7 +110,7 @@ export class DeploymentInputDialog extends DialogBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vscode.commands.executeCommand(this.dialogInfo.command, model);
|
await vscode.commands.executeCommand(this.dialogInfo.command, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export abstract class DialogBase {
|
|||||||
this.dispose();
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onOkButtonClicked(): void {
|
private async onOkButtonClicked(): Promise<void> {
|
||||||
this.onComplete();
|
await this.onComplete();
|
||||||
this.dispose();
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected dispose(): void {
|
protected dispose(): void {
|
||||||
|
|||||||
@@ -3,27 +3,28 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import { azureResource } from 'azureResource';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { EOL, homedir as os_homedir } from 'os';
|
import { EOL, homedir as os_homedir } from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { azureResource } from 'azureResource';
|
import { OptionsSource } from '../helpers/optionSources';
|
||||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
import { apiService } from '../services/apiService';
|
import { apiService } from '../services/apiService';
|
||||||
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
||||||
import { assert, getDateTimeString, getErrorMessage } from '../utils';
|
import { KubeCtlTool, KubeCtlToolName } from '../services/tools/kubeCtlTool';
|
||||||
|
import { IToolsService } from '../services/toolsService';
|
||||||
|
import { getDateTimeString, getErrorMessage, throwUnless } from '../utils';
|
||||||
import { WizardInfoBase } from './../interfaces';
|
import { WizardInfoBase } from './../interfaces';
|
||||||
import { Model } from './model';
|
import { Model } from './model';
|
||||||
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
||||||
import { IToolsService } from '../services/toolsService';
|
|
||||||
import { KubeCtlToolName, KubeCtlTool } from '../services/tools/kubeCtlTool';
|
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export type Validator = () => { valid: boolean, message: string };
|
export type Validator = () => { valid: boolean, message: string };
|
||||||
export type InputValueTransformer = (inputValue: string) => string;
|
export type InputValueTransformer = (inputValue: string) => string | Promise<string>;
|
||||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||||
export type InputComponentInfo = {
|
export type InputComponentInfo = {
|
||||||
component: InputComponent;
|
component: InputComponent;
|
||||||
@@ -353,7 +354,7 @@ function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata
|
|||||||
async function processField(context: FieldContext): Promise<void> {
|
async function processField(context: FieldContext): Promise<void> {
|
||||||
switch (context.fieldInfo.type) {
|
switch (context.fieldInfo.type) {
|
||||||
case FieldType.Options:
|
case FieldType.Options:
|
||||||
processOptionsTypeField(context);
|
await processOptionsTypeField(context);
|
||||||
break;
|
break;
|
||||||
case FieldType.DateTimeText:
|
case FieldType.DateTimeText:
|
||||||
processDateTimeTextField(context);
|
processDateTimeTextField(context);
|
||||||
@@ -390,12 +391,23 @@ async function processField(context: FieldContext): Promise<void> {
|
|||||||
await processKubeStorageClassField(context);
|
await processKubeStorageClassField(context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", context.fieldInfo.type));
|
throw new Error(loc.unknownFieldTypeError(context.fieldInfo.type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processOptionsTypeField(context: FieldContext): void {
|
function disableControlButtons(container: azdata.window.Dialog | azdata.window.Wizard): void {
|
||||||
assert(context.fieldInfo.options !== undefined, `FieldInfo.options must be defined for FieldType:${FieldType.Options}`);
|
if ('okButton' in container) {
|
||||||
|
container.okButton.enabled = false;
|
||||||
|
} else {
|
||||||
|
container.doneButton.enabled = false;
|
||||||
|
container.nextButton.enabled = false;
|
||||||
|
container.backButton.enabled = false;
|
||||||
|
container.customButtons.forEach(b => b.enabled = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processOptionsTypeField(context: FieldContext): Promise<void> {
|
||||||
|
throwUnless(context.fieldInfo.options !== undefined, loc.optionsNotDefined(context.fieldInfo.type));
|
||||||
if (Array.isArray(context.fieldInfo.options)) {
|
if (Array.isArray(context.fieldInfo.options)) {
|
||||||
context.fieldInfo.options = <OptionsInfo>{
|
context.fieldInfo.options = <OptionsInfo>{
|
||||||
values: context.fieldInfo.options,
|
values: context.fieldInfo.options,
|
||||||
@@ -403,17 +415,62 @@ function processOptionsTypeField(context: FieldContext): void {
|
|||||||
optionsType: OptionsType.Dropdown
|
optionsType: OptionsType.Dropdown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
assert(typeof context.fieldInfo.options === 'object', `FieldInfo.options must be an object if it is not an array`);
|
throwUnless(typeof context.fieldInfo.options === 'object', loc.optionsNotObjectOrArray);
|
||||||
assert('optionsType' in context.fieldInfo.options, `When FieldInfo.options is an object it must have 'optionsType' property`);
|
throwUnless('optionsType' in context.fieldInfo.options, loc.optionsTypeNotFound);
|
||||||
|
if (context.fieldInfo.options.source) {
|
||||||
|
try {
|
||||||
|
// if options.source still points to the IOptionsSource interface make it to point to the implementation
|
||||||
|
context.fieldInfo.options.source = OptionsSource.construct(context.fieldInfo.options.source.type, context.fieldInfo.options.source.variableNames);
|
||||||
|
context.fieldInfo.options.values = await context.fieldInfo.options.source.getOptions();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
disableControlButtons(context.container);
|
||||||
|
context.container.message = {
|
||||||
|
text: getErrorMessage(e),
|
||||||
|
description: '',
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
context.fieldInfo.options.values = [];
|
||||||
|
}
|
||||||
|
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||||
|
}
|
||||||
|
let optionsComponent: InputComponent;
|
||||||
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
||||||
processRadioOptionsTypeField(context);
|
optionsComponent = await processRadioOptionsTypeField(context);
|
||||||
} else {
|
} else {
|
||||||
assert(context.fieldInfo.options.optionsType === OptionsType.Dropdown, `When optionsType is not ${OptionsType.Radio} then it must be ${OptionsType.Dropdown}`);
|
throwUnless(context.fieldInfo.options.optionsType === OptionsType.Dropdown, loc.optionsTypeRadioOrDropdown);
|
||||||
processDropdownOptionsTypeField(context);
|
optionsComponent = processDropdownOptionsTypeField(context);
|
||||||
|
}
|
||||||
|
if (context.fieldInfo.options.source) {
|
||||||
|
const optionsSource = context.fieldInfo.options.source;
|
||||||
|
for (const key of Object.keys(optionsSource.variableNames ?? {})) {
|
||||||
|
context.fieldInfo.subFields!.push({
|
||||||
|
label: context.fieldInfo.label,
|
||||||
|
variableName: optionsSource.variableNames[key]
|
||||||
|
});
|
||||||
|
context.onNewInputComponentCreated(optionsSource.variableNames[key], {
|
||||||
|
component: optionsComponent,
|
||||||
|
inputValueTransformer: async (controllerName: string) => {
|
||||||
|
try {
|
||||||
|
const variableValue = await optionsSource.getVariableValue(key, controllerName);
|
||||||
|
return variableValue;
|
||||||
|
} catch (e) {
|
||||||
|
disableControlButtons(context.container);
|
||||||
|
context.container.message = {
|
||||||
|
text: getErrorMessage(e),
|
||||||
|
description: '',
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isPassword: optionsSource.getIsPassword(key)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDropdownOptionsTypeField(context: FieldContext): void {
|
function processDropdownOptionsTypeField(context: FieldContext): azdata.DropDownComponent {
|
||||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||||
const options = context.fieldInfo.options as OptionsInfo;
|
const options = context.fieldInfo.options as OptionsInfo;
|
||||||
const dropdown = createDropdown(context.view, {
|
const dropdown = createDropdown(context.view, {
|
||||||
@@ -427,6 +484,7 @@ function processDropdownOptionsTypeField(context: FieldContext): void {
|
|||||||
dropdown.fireOnTextChange = true;
|
dropdown.fireOnTextChange = true;
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
||||||
|
return dropdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDateTimeTextField(context: FieldContext): void {
|
function processDateTimeTextField(context: FieldContext): void {
|
||||||
@@ -579,8 +637,8 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
|||||||
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
||||||
component: readOnlyField.text!,
|
component: readOnlyField.text!,
|
||||||
inputValueTransformer: () => {
|
inputValueTransformer: async () => {
|
||||||
readOnlyField.text!.value = substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
readOnlyField.text!.value = await substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
||||||
return readOnlyField.text?.value!;
|
return readOnlyField.text?.value!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -596,14 +654,15 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
|||||||
* @param inputValue
|
* @param inputValue
|
||||||
* @param inputComponents
|
* @param inputComponents
|
||||||
*/
|
*/
|
||||||
function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): string | undefined {
|
async function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): Promise<string | undefined> {
|
||||||
Object.keys(inputComponents)
|
await Promise.all(Object.keys(inputComponents)
|
||||||
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
||||||
.forEach(key => {
|
.map(async key => {
|
||||||
const value = getInputComponentValue(inputComponents, key) ?? '<undefined>';
|
const value = (await getInputComponentValue(inputComponents, key)) ?? '<undefined>';
|
||||||
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
||||||
inputValue = inputValue?.replace(re, value);
|
inputValue = inputValue?.replace(re, value);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return inputValue;
|
return inputValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,7 +1043,7 @@ async function handleSelectedAccountChanged(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await (await apiService.getAzurecoreApi()).getSubscriptions(selectedAccount, true);
|
const response = await apiService.azurecoreApi.getSubscriptions(selectedAccount, true);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1158,7 @@ async function handleSelectedSubscriptionChanged(context: AzureAccountFieldConte
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await (await apiService.getAzurecoreApi()).getResourceGroups(selectedAccount, selectedSubscription, true);
|
const response = await apiService.azurecoreApi.getResourceGroups(selectedAccount, selectedSubscription, true);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1150,8 +1209,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
|||||||
width: context.fieldInfo.labelWidth,
|
width: context.fieldInfo.labelWidth,
|
||||||
cssStyles: context.fieldInfo.labelCSSStyles
|
cssStyles: context.fieldInfo.labelCSSStyles
|
||||||
});
|
});
|
||||||
const azurecoreApi = await apiService.getAzurecoreApi();
|
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: apiService.azurecoreApi.getRegionDisplayName(l) }; });
|
||||||
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: azurecoreApi.getRegionDisplayName(l) }; });
|
|
||||||
const locationDropdown = createDropdown(context.view, {
|
const locationDropdown = createDropdown(context.view, {
|
||||||
defaultValue: locationValues?.find(l => l.name === context.fieldInfo.defaultValue),
|
defaultValue: locationValues?.find(l => l.name === context.fieldInfo.defaultValue),
|
||||||
width: context.fieldInfo.inputWidth,
|
width: context.fieldInfo.inputWidth,
|
||||||
@@ -1174,7 +1232,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
|||||||
label: label.value!,
|
label: label.value!,
|
||||||
variableName: context.fieldInfo.displayLocationVariableName
|
variableName: context.fieldInfo.displayLocationVariableName
|
||||||
});
|
});
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => azurecoreApi.getRegionDisplayName(value)) });
|
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => apiService.azurecoreApi.getRegionDisplayName(value)) });
|
||||||
}
|
}
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
||||||
return locationDropdown;
|
return locationDropdown;
|
||||||
@@ -1207,14 +1265,14 @@ export function getPasswordMismatchMessage(fieldName: string): string {
|
|||||||
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
export async function setModelValues(inputComponents: InputComponents, model: Model): Promise<void> {
|
||||||
Object.keys(inputComponents).forEach(key => {
|
await Promise.all(Object.keys(inputComponents).map(async key => {
|
||||||
const value = getInputComponentValue(inputComponents, key);
|
const value = await getInputComponentValue(inputComponents, key);
|
||||||
model.setPropertyValue(key, value);
|
model.setPropertyValue(key, value);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
|
async function getInputComponentValue(inputComponents: InputComponents, key: string): Promise<string | undefined> {
|
||||||
const input = inputComponents[key].component;
|
const input = inputComponents[key].component;
|
||||||
if (input === undefined) {
|
if (input === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -1236,7 +1294,10 @@ function getInputComponentValue(inputComponents: InputComponents, key: string):
|
|||||||
}
|
}
|
||||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||||
if (inputValueTransformer) {
|
if (inputValueTransformer) {
|
||||||
value = inputValueTransformer(value || '');
|
value = inputValueTransformer(value ?? '');
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
value = await value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async onOk(): Promise<void> {
|
protected async onOk(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.model);
|
await setModelValues(this.inputComponents, this.model);
|
||||||
const env: NodeJS.ProcessEnv = {};
|
const env: NodeJS.ProcessEnv = {};
|
||||||
this.model.setEnvironmentVariables(env, (varName) => {
|
this.model.setEnvironmentVariables(env, (varName) => {
|
||||||
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class NotebookWizardAutoSummaryPage extends NotebookWizardPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.message = { text: '' };
|
this.wizard.wizardObject.message = { text: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
// The following callback registration clears previous navigation validators.
|
// The following callback registration clears previous navigation validators.
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
@@ -66,7 +66,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
|||||||
|
|
||||||
public async onEnter(): Promise<void> {
|
public async onEnter(): Promise<void> {
|
||||||
if (this.pageInfo.isSummaryPage) {
|
if (this.pageInfo.isSummaryPage) {
|
||||||
setModelValues(this.wizard.inputComponents, this.wizard.model);
|
await setModelValues(this.wizard.inputComponents, this.wizard.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
return this._selectedResourceType.getProvider(options)!;
|
return this._selectedResourceType.getProvider(options)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
this.toolsService.toolsForCurrentProvider = this._tools;
|
this.toolsService.toolsForCurrentProvider = this._tools;
|
||||||
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model
|
|||||||
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
|
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
|
||||||
let previousPage = this.pages[e.lastPage];
|
let previousPage = this.pages[e.lastPage];
|
||||||
let newPage = this.pages[e.newPage];
|
let newPage = this.pages[e.newPage];
|
||||||
previousPage.onLeave();
|
await previousPage.onLeave();
|
||||||
await newPage.onEnter();
|
await newPage.onEnter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export abstract class WizardPageBase<T> {
|
|||||||
|
|
||||||
public async onEnter(): Promise<void> { }
|
public async onEnter(): Promise<void> { }
|
||||||
|
|
||||||
public onLeave(): void { }
|
public async onLeave(): Promise<void> { }
|
||||||
|
|
||||||
public abstract initialize(): void;
|
public abstract initialize(): void;
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[], env: Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assert(condition: boolean, message?: string): asserts condition {
|
/**
|
||||||
|
* Throws an Error with given {@link message} unless {@link condition} is true.
|
||||||
|
* This also tells the typescript compiler that the condition is 'truthy' in the remainder of the scope
|
||||||
|
* where this function was called.
|
||||||
|
*
|
||||||
|
* @param condition
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
export function throwUnless(condition: boolean, message?: string): asserts condition {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,28 @@
|
|||||||
"optionsType": "radio"
|
"optionsType": "radio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "options",
|
||||||
|
"label": "%wizard.data.controllers%",
|
||||||
|
"required": true,
|
||||||
|
"variableName": "AZDATA_NB_VAR_CONTROLLER",
|
||||||
|
"editable": false,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"values":[
|
||||||
|
"ignored1",
|
||||||
|
"ignored2"
|
||||||
|
],
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%wizard.dropdown.options.field%",
|
"label": "%wizard.dropdown.options.field%",
|
||||||
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
|
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
|
||||||
@@ -245,6 +267,36 @@
|
|||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
|
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.dropdown.options.field%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_DROPDOWN_OPTIONS)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.endpoint%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_ENDPOINT)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.username%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_USERNAME)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.password%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_PASSWORD)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"wizard.kube.cluster.context": "Cluster context",
|
"wizard.kube.cluster.context": "Cluster context",
|
||||||
"wizard.cluster.config.profile.title": "Choose the config profile",
|
"wizard.cluster.config.profile.title": "Choose the config profile",
|
||||||
"wizard.cluster.config.profile": "Config profile",
|
"wizard.cluster.config.profile": "Config profile",
|
||||||
|
"wizard.data.controllers": "Pick a data controller",
|
||||||
"wizard.dropdown.options.field": "dropdown field",
|
"wizard.dropdown.options.field": "dropdown field",
|
||||||
"wizard.project.details.title": "Project details",
|
"wizard.project.details.title": "Project details",
|
||||||
"wizard.project.details.description": "Project details for Contoso corporation",
|
"wizard.project.details.description": "Project details for Contoso corporation",
|
||||||
@@ -44,6 +45,10 @@
|
|||||||
"wizard.summary.kube.config.file.path": "Kube config file path",
|
"wizard.summary.kube.config.file.path": "Kube config file path",
|
||||||
"wizard.summary.cluster.context": "Cluster context",
|
"wizard.summary.cluster.context": "Cluster context",
|
||||||
"wizard.summary.profile": "Config profile",
|
"wizard.summary.profile": "Config profile",
|
||||||
|
"wizard.summary.controller": "Controller",
|
||||||
|
"wizard.summary.controller.endpoint": "Controller endpoint",
|
||||||
|
"wizard.summary.controller.username": "Controller username",
|
||||||
|
"wizard.summary.controller.password": "Controller password",
|
||||||
"wizard.data.controller.agreement": "I accept {0} and {1}.",
|
"wizard.data.controller.agreement": "I accept {0} and {1}.",
|
||||||
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
|
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
|
||||||
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
|
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
|
||||||
|
|||||||
Reference in New Issue
Block a user