deploy BDC wizard improvement for CU1 (#7756)

* unified admin user account (#7485)

* azdata changes

* spaces

* error message

* comments

* support AD authentication for bdc deployment (#7518)

* enable ad authentication

* remove export for internal interface

* add comments

* more changes after testing

* update notebooks

* escape slash

* more comments

* Update deploy-bdc-aks.ipynb

* Update deploy-bdc-existing-aks.ipynb

* Update deploy-bdc-existing-kubeadm.ipynb

* AD changes and review feedback (#7618)

* enable ad authentication

* remove export for internal interface

* add comments

* more changes after testing

* update notebooks

* escape slash

* more comments

* Update deploy-bdc-aks.ipynb

* Update deploy-bdc-existing-aks.ipynb

* Update deploy-bdc-existing-kubeadm.ipynb

* address comments from scenario review (#7546)

* support AD authentication for bdc deployment (#7518)

* enable ad authentication

* remove export for internal interface

* add comments

* more changes after testing

* update notebooks

* escape slash

* more comments

* Update deploy-bdc-aks.ipynb

* Update deploy-bdc-existing-aks.ipynb

* Update deploy-bdc-existing-kubeadm.ipynb

* scenario review feedbacks

* more fixes

* adjust the display order of resource types

* different way to implement left side buttons

* revert unwanted changes

* rename variable

* more fixes for the scenario review feedback (#7589)

* fix more issues

* add help links

* model view readonly text with links

* fix size string

* address comments

* update notebooks

* text update

* address the feedback of 2nd round of deploy BDC wizard review (#7646)

* 2nd review meeting comments

* fix the unit test failure

* recent changes in azdata

* notebook background execution with azdata (#7741)

* notebook background execution with azdata

* prompt to open notebook in case of failure

* fix path quote issue

* better temp file handling

* expose docker settings (#7751)

* add docker settings

* new icon for container image
This commit is contained in:
Alan Ren
2019-10-16 20:41:15 -07:00
committed by GitHub
parent 5d4da455bd
commit 2ab7a47353
40 changed files with 2019 additions and 730 deletions

View File

@@ -1,3 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="16" height="16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M780 848v149h-14V848h14zm-53 0v149h-14V848h14zm-197-57V641h15v150h-15zm144-150v150h-15V641h15zM504 791V641h14v150h-14zm-27 0V641h15v150h-15zm-26 0V641h14v150h-14zm303 57v149h-15V848h15zm-54 0v149h-14V848h14zm0-207v150h-14V641h14zm54 0v150h-15V641h15zm-27 0v150h-14V641h14zm-53 207v149h-15V848h15zm106-207v150h-14V641h14zm27 207v149h-14V848h14zM270 997V848h15v149h-15zm-26 0V848h14v149h-14zm53 0V848h14v149h-14zm260-206V641h14v150h-14zM377 997V848h14v149h-14zm207 0V848h14v149h-14zm-260 0V848h14v149h-14zm26 0V848h15v149h-15zm101 0V848h14v149h-14zm133-206V641h14v150h-14zM477 997V848h15v149h-15zm80 0V848h14v149h-14zm-27 0V848h15v149h-15zm-26 0V848h14v149h-14zm303-356v150h-14V641h14zm129-207v150h-14V434h14zm-27 0v150h-14V434h14zm-26 207v150h-15V641h15zm0-207v150h-15V434h15zm133 0v150h-15V434h15zm-53 0v150h-15V434h15zm26 0v150h-14V434h14zm236 414v149h-15V848h15zm-107 0v149h-14V848h14zm-26 0v149h-15V848h15zm106 0v149h-14V848h14zm-53 0v149h-15V848h15zm26 0v149h-14V848h14zm-262 0v149h-14V848h14zm27 0v149h-14V848h14zm27 0v149h-15V848h15zm-80 0v149h-15V848h15zm26-207v150h-14V641h14zm107 0v150h-15V641h15zm-27 207v149h-14V848h14zm27 0v149h-15V848h15zm-27-207v150h-14V641h14zm-26 0v150h-15V641h15zm-27 0v150h-14V641h14zm-324 678q-7 5-7 13 0 6 4 10t10 5q10 0 13-8 3 6 3 14 0 15-10 25t-26 11q-15 0-25-10t-11-26q0-15 10-25t26-11q5 0 13 2zm1435-454l-16 29q-22 40-52 67t-66 45-76 24-83 8h-13q-6 0-14-1-69 169-175 292t-243 205-297 120-338 39q-134 0-256-39t-216-114-148-186-55-254q0-23 1-45t6-45h191V803h209V596h415V389h244v414h207v207h105q57 0 112-14t105-42q-27-34-39-76t-12-86q0-60 17-105t58-90q28 23 54 47t48 52 36 60 19 71q23-8 47-11t50-3q52 0 91 13t84 39zM438 629v174h174V629H438zm211 724q0-21-14-35t-36-15q-21 0-35 14t-15 36q0 20 15 35t35 15q20 0 35-15t15-35zM438 836v174h174V836H438zm-207 0v174h175V836H231zm444 826h28q14 0 29-2-77-36-136-90t-94-133q-40 11-81 16t-83 8l-2 1q-25 1-49 1t-49 1q-19 0-37-1t-38-2q55 54 112 92t121 63 132 35 147 11zm146-652V836H647v174h174zm0-207V629H647v174h174zm209 207V836H856v174h174zm0-207V629H856v174h174zm0-207V422H856v174h174zm209 414V836h-174v174h174z" /> <title>opac_command_icons_bv</title>
</svg> <path d="M6.5,14h0c-.007.01-.062,0-.093-.01L.408,12.929A.5.5,0,0,1,0,12.437v-8.4a.5.5,0,0,1,.375-.484L6.366,2.019a.464.464,0,0,1,.243-.007h0l.013,0,8,2A.5.5,0,0,1,15,4.5v6a.5.5,0,0,1-.324.468l-7.989,3A.528.528,0,0,1,6.5,14h0ZM1,12.018,6,12.9V3.145L1,4.429ZM7,3.141v9.637l7-2.625V4.891Z"/>
<polygon points="3 3.947 3 12.357 4 12.534 4 3.69 3 3.947"/>
<polygon points="8 4.592 8 11.086 9 10.752 9 4.814 8 4.592"/>
<polygon points="10 5.037 10 10.419 11 10.086 11 5.259 10 5.037"/>
<polygon points="12 5.481 12 9.752 13 9.419 13 5.703 12 5.481"/>
<rect x="-0.004" width="16" height="16" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -1,31 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <title>opac_command_icons_bv</title>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <path d="M6.5,14h0c-.007-.013-.062,0-.093-.01L.408,12.928A.5.5,0,0,1,0,12.436V4.042a.5.5,0,0,1,.375-.485l6-1.54a.511.511,0,0,1,.246,0h0l.008,0,8,2A.5.5,0,0,1,15,4.5v6a.5.5,0,0,1-.324.467l-7.989,3A.506.506,0,0,1,6.5,14h0ZM1,12.017,6,12.9V3.144L1,4.429ZM7,3.14v9.638l7-2.625V4.89Z" fill="#fff"/>
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve"> <polygon points="3 3.947 3 12.357 4 12.534 4 3.69 3 3.947" fill="#fff"/>
<style type="text/css"> <polygon points="8 4.592 8 11.086 9 10.752 9 4.814 8 4.592" fill="#fff"/>
.st0{fill:#FFFFFF;} <polygon points="10 5.037 10 10.419 11 10.086 11 5.259 10 5.037" fill="#fff"/>
</style> <polygon points="12 5.481 12 9.752 13 9.419 13 5.703 12 5.481" fill="#fff"/>
<path class="st0" d="M6.1,6.6v1.2H6V6.6H6.1z M5.7,6.6v1.2H5.6V6.6H5.7z M4.1,6.2V5h0.1v1.2H4.1z M5.3,5v1.2H5.1V5H5.3z M3.9,6.2V5 <rect width="16" height="16" fill="none"/>
H4v1.2H3.9z M3.7,6.2V5h0.1v1.2H3.7z M3.5,6.2V5h0.1v1.2H3.5z M5.9,6.6v1.2H5.8V6.6H5.9z M5.5,6.6v1.2H5.4V6.6H5.5z M5.5,5v1.2H5.4
V5H5.5z M5.9,5v1.2H5.8V5H5.9z M5.7,5v1.2H5.6V5H5.7z M5.3,6.6v1.2H5.1V6.6H5.3z M6.1,5v1.2H6V5H6.1z M6.3,6.6v1.2H6.2V6.6H6.3z
M2.1,7.8V6.6h0.1v1.2H2.1z M1.9,7.8V6.6H2v1.2H1.9z M2.3,7.8V6.6h0.1v1.2H2.3z M4.4,6.2V5h0.1v1.2H4.4z M2.9,7.8V6.6h0.1v1.2H2.9z
M4.6,7.8V6.6h0.1v1.2H4.6z M2.5,7.8V6.6h0.1v1.2H2.5z M2.7,7.8V6.6h0.1v1.2H2.7z M3.5,7.8V6.6h0.1v1.2H3.5z M4.6,6.2V5h0.1v1.2H4.6
z M3.7,7.8V6.6h0.1v1.2H3.7z M4.4,7.8V6.6h0.1v1.2H4.4z M4.1,7.8V6.6h0.1v1.2H4.1z M3.9,7.8V6.6H4v1.2H3.9z M6.3,5v1.2H6.2V5H6.3z
M7.3,3.4v1.2H7.2V3.4H7.3z M7.1,3.4v1.2H7V3.4H7.1z M6.9,5v1.2H6.8V5H6.9z M6.9,3.4v1.2H6.8V3.4H6.9z M7.9,3.4v1.2H7.8V3.4H7.9z
M7.5,3.4v1.2H7.4V3.4H7.5z M7.7,3.4v1.2H7.6V3.4H7.7z M9.6,6.6v1.2H9.5V6.6H9.6z M8.7,6.6v1.2H8.6V6.6H8.7z M8.5,6.6v1.2H8.4V6.6
H8.5z M9.4,6.6v1.2H9.2V6.6H9.4z M8.9,6.6v1.2H8.8V6.6H8.9z M9.1,6.6v1.2H9V6.6H9.1z M7.1,6.6v1.2H7V6.6H7.1z M7.3,6.6v1.2H7.2V6.6
H7.3z M7.5,6.6v1.2H7.4V6.6H7.5z M6.9,6.6v1.2H6.8V6.6H6.9z M7.1,5v1.2H7V5H7.1z M7.9,5v1.2H7.8V5H7.9z M7.7,6.6v1.2H7.6V6.6H7.7z
M7.9,6.6v1.2H7.8V6.6H7.9z M7.7,5v1.2H7.6V5H7.7z M7.5,5v1.2H7.4V5H7.5z M7.3,5v1.2H7.2V5H7.3z M4.8,10.3c0,0-0.1,0.1-0.1,0.1
c0,0,0,0.1,0,0.1s0,0,0.1,0c0.1,0,0.1,0,0.1-0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.1-0.1,0.2s-0.1,0.1-0.2,0.1c-0.1,0-0.1,0-0.2-0.1
s-0.1-0.1-0.1-0.2c0-0.1,0-0.1,0.1-0.2s0.1-0.1,0.2-0.1C4.7,10.3,4.7,10.3,4.8,10.3z M16,6.8L15.9,7c-0.1,0.2-0.2,0.4-0.4,0.5
s-0.3,0.3-0.5,0.4S14.6,8,14.4,8s-0.4,0.1-0.6,0.1h-0.1c0,0-0.1,0-0.1,0c-0.4,0.9-0.8,1.6-1.4,2.3s-1.2,1.2-1.9,1.6
s-1.5,0.7-2.3,0.9s-1.7,0.3-2.6,0.3c-0.7,0-1.4-0.1-2-0.3S2.1,12.4,1.6,12s-0.9-0.9-1.2-1.5S0,9.3,0,8.6c0-0.1,0-0.2,0-0.4
S0,8,0,7.9h1.5V6.3h1.6V4.7h3.2V3h1.9v3.2h1.6v1.6h0.8c0.3,0,0.6,0,0.9-0.1s0.6-0.2,0.8-0.3c-0.1-0.2-0.2-0.4-0.3-0.6
s-0.1-0.4-0.1-0.7c0-0.3,0-0.6,0.1-0.8s0.2-0.5,0.5-0.7c0.1,0.1,0.3,0.2,0.4,0.4s0.3,0.3,0.4,0.4s0.2,0.3,0.3,0.5s0.1,0.4,0.1,0.6
c0.1,0,0.2-0.1,0.4-0.1s0.3,0,0.4,0c0.3,0,0.5,0,0.7,0.1S15.8,6.6,16,6.8z M3.4,4.9v1.4h1.4V4.9H3.4z M5.1,10.6c0-0.1,0-0.2-0.1-0.3
s-0.2-0.1-0.3-0.1c-0.1,0-0.2,0-0.3,0.1s-0.1,0.2-0.1,0.3c0,0.1,0,0.2,0.1,0.3S4.6,11,4.7,11s0.2,0,0.3-0.1S5.1,10.7,5.1,10.6z
M3.4,6.5v1.4h1.4V6.5H3.4z M1.8,6.5v1.4h1.4V6.5H1.8z M5.3,13h0.2c0.1,0,0.1,0,0.2,0c-0.4-0.2-0.8-0.4-1.1-0.7s-0.6-0.6-0.7-1
c-0.2,0.1-0.4,0.1-0.6,0.1s-0.4,0-0.6,0.1l0,0c-0.1,0-0.3,0-0.4,0s-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0s-0.2,0-0.3,0
c0.3,0.3,0.6,0.5,0.9,0.7s0.6,0.4,0.9,0.5s0.7,0.2,1,0.3S4.9,13,5.3,13z M6.4,7.9V6.5H5.1v1.4H6.4z M6.4,6.3V4.9H5.1v1.4H6.4z
M8,7.9V6.5H6.7v1.4H8z M8,6.3V4.9H6.7v1.4H8z M8,4.7V3.3H6.7v1.4H8z M9.7,7.9V6.5H8.3v1.4H9.7z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 782 B

View File

@@ -0,0 +1,273 @@
{
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
}
},
"nbformat_minor": 2,
"nbformat": 4,
"cells": [
{
"cell_type": "markdown",
"source": [
"![Microsoft](https://raw.githubusercontent.com/microsoft/azuredatastudio/master/src/sql/media/microsoft-small-logo.png)\n",
" \n",
"## Create Azure Kubernetes Service cluster and deploy SQL Server 2019 Big Data Cluster\n",
" \n",
"This notebook walks through the process of creating a new Azure Kubernetes Service cluster first, and then deploys a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on the newly created AKS cluster."
],
"metadata": {
"azdata_cell_guid": "4f6bc3bc-3592-420a-b534-384011189005"
}
},
{
"cell_type": "code",
"source": [
"import json,sys,os\n",
"def run_command(command):\n",
" print(\"Executing: \" + command)\n",
" !{command}\n",
" if _exit_code != 0:\n",
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
" print(f'Successfully executed: {command}')"
],
"metadata": {
"azdata_cell_guid": "326645cf-022a-47f2-8aff-37de71da8955",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 1
},
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "8716915b-1439-431b-ab0a-0221ef94cb7f"
}
},
{
"cell_type": "markdown",
"source": [
"### **Set password**"
],
"metadata": {
"azdata_cell_guid": "b083aa8d-990c-4170-ba1d-247ba5c6ae76"
}
},
{
"cell_type": "code",
"source": [
"mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]"
],
"metadata": {
"azdata_cell_guid": "de256ddd-b835-4eb6-8cfc-c1a6239b0726"
},
"outputs": [],
"execution_count": 0
},
{
"cell_type": "markdown",
"source": [
"### **Login to Azure**\n",
"\n",
"This will open a web browser window to enable credentials to be entered. If this cells is hanging forever, it might be because your Web browser windows is waiting for you to enter your Azure credentials!\n",
""
],
"metadata": {
"azdata_cell_guid": "baddf2d9-93ee-4c42-aaf1-b42116bb1912"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'az login')"
],
"metadata": {
"azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 5
},
{
"cell_type": "markdown",
"source": [
"\n",
"### **Set active Azure subscription**"
],
"metadata": {
"azdata_cell_guid": "230dc0f1-bf6e-474a-bfaa-aae6f8aad12e"
}
},
{
"cell_type": "code",
"source": [
"if azure_subscription_id != \"\":\n",
" run_command(f'az account set --subscription {azure_subscription_id}')\n",
"else:\n",
" print('Using the default Azure subscription', {azure_subscription_id})\n",
"run_command(f'az account show')"
],
"metadata": {
"azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
},
{
"cell_type": "markdown",
"source": [
"### **Create Azure resource group**"
],
"metadata": {
"azdata_cell_guid": "d51db914-f484-489f-990d-72edb3065068"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'az group create --name {azure_resource_group} --location {azure_region}')"
],
"metadata": {
"azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 7
},
{
"cell_type": "markdown",
"source": [
"### **Create AKS cluster**"
],
"metadata": {
"azdata_cell_guid": "818eb705-71e2-4013-8420-44886a5468b2"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'az aks create --name {aks_cluster_name} --resource-group {azure_resource_group} --generate-ssh-keys --node-vm-size {azure_vm_size} --node-count {azure_vm_count}')"
],
"metadata": {
"azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 8
},
{
"cell_type": "markdown",
"source": [
"### **Set the new AKS cluster as current context**"
],
"metadata": {
"azdata_cell_guid": "5ade8453-5e71-478f-b6b6-83c55626243d"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'az aks get-credentials --resource-group {azure_resource_group} --name {aks_cluster_name} --admin --overwrite-existing')"
],
"metadata": {
"azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 9
},
{
"cell_type": "markdown",
"source": [
"### **Create deployment configuration files**"
],
"metadata": {
"azdata_cell_guid": "57eb69fb-c68f-4ba8-818d-ffbaa0bc7aec"
}
},
{
"cell_type": "code",
"source": [
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
"bdcJsonObj = json.loads(bdc_json)\n",
"controlJsonObj = json.loads(control_json)\n",
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
"bdcJsonFile.close()\n",
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
"controlJsonFile.close()\n",
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 10
},
{
"cell_type": "markdown",
"source": [
"### **Create SQL Server 2019 Big Data Cluster**"
],
"metadata": {
"azdata_cell_guid": "6e82fad8-0fd0-4952-87ce-3fea1edd98cb"
}
},
{
"cell_type": "code",
"source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\tkubectl get pods -n {mssql_cluster_name} ')\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "c43ea026-ca5e-4e2a-8602-fcc786354168",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 11
}
]
}

View File

@@ -0,0 +1,170 @@
{
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
}
},
"nbformat_minor": 2,
"nbformat": 4,
"cells": [
{
"cell_type": "markdown",
"source": [
"![Microsoft](https://raw.githubusercontent.com/microsoft/azuredatastudio/master/src/sql/media/microsoft-small-logo.png)\n",
" \n",
"## Deploy SQL Server 2019 Big Data Cluster on an existing Azure Kubernetes Service (AKS) cluster\n",
" \n",
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing AKS cluster."
],
"metadata": {
"azdata_cell_guid": "82e60c1a-7acf-47ee-877f-9e85e92e11da"
}
},
{
"cell_type": "code",
"source": [
"import json,sys,os\n",
"def run_command(command):\n",
" print(\"Executing: \" + command)\n",
" !{command}\n",
" if _exit_code != 0:\n",
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
" print(f'Successfully executed: {command}')"
],
"metadata": {
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
"tags": []
},
"outputs": [],
"execution_count": 1
},
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
}
},
{
"cell_type": "markdown",
"source": [
"### **Set password**"
],
"metadata": {
"azdata_cell_guid": "7c37d248-b9ac-4ad6-be56-158cd70443b1"
}
},
{
"cell_type": "code",
"source": [
"mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]"
],
"metadata": {
"azdata_cell_guid": "83d455f3-db10-48bb-bb81-78a6b4e5f2fd"
},
"outputs": [],
"execution_count": 0
},
{
"cell_type": "markdown",
"source": [
"### **Set and show current context**"
],
"metadata": {
"azdata_cell_guid": "127c8042-181f-4862-a390-96e59c181d09"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
"run_command('kubectl config current-context')"
],
"metadata": {
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 0
},
{
"cell_type": "markdown",
"source": [
"### **Create deployment configuration files**"
],
"metadata": {
"azdata_cell_guid": "138536c3-1db6-428f-9e5c-8269a02fb52e"
}
},
{
"cell_type": "code",
"source": [
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
"bdcJsonObj = json.loads(bdc_json)\n",
"controlJsonObj = json.loads(control_json)\n",
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
"bdcJsonFile.close()\n",
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
"controlJsonFile.close()\n",
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
},
{
"cell_type": "markdown",
"source": [
"### **Create SQL Server 2019 Big Data Cluster**"
],
"metadata": {
"azdata_cell_guid": "efe78cd3-ed73-4c9b-b586-fdd6c07dd37f"
}
},
{
"cell_type": "code",
"source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 7
}
]
}

View File

@@ -0,0 +1,182 @@
{
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
}
},
"nbformat_minor": 2,
"nbformat": 4,
"cells": [
{
"cell_type": "markdown",
"source": [
"![Microsoft](https://raw.githubusercontent.com/microsoft/azuredatastudio/master/src/sql/media/microsoft-small-logo.png)\n",
" \n",
"## Deploy SQL Server 2019 Big Data Cluster on an existing cluster deployed using kubeadm\n",
" \n",
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing kubeadm cluster."
],
"metadata": {
"azdata_cell_guid": "23954d96-3932-4a8e-ab73-da605f99b1a4"
}
},
{
"cell_type": "code",
"source": [
"import json,sys,os\n",
"def run_command(command):\n",
" print(\"Executing: \" + command)\n",
" !{command}\n",
" if _exit_code != 0:\n",
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
" print(f'Successfully executed: {command}')"
],
"metadata": {
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 1
},
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "e70640d0-6059-4cab-939e-e985a978c0da"
}
},
{
"cell_type": "markdown",
"source": [
"### **Set password**"
],
"metadata": {
"azdata_cell_guid": "7b383b0d-5687-45b3-a16f-ba3b170c796e"
}
},
{
"cell_type": "code",
"source": [
"mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
"if mssql_auth_mode == \"ad\":\n",
" mssql_domain_service_account_password = os.environ[\"AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD\"]"
],
"metadata": {
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 3
},
{
"cell_type": "markdown",
"source": [
"### **Set and show current context**"
],
"metadata": {
"azdata_cell_guid": "6456bd0c-5b64-4d76-be59-e3a5b32697f5"
}
},
{
"cell_type": "code",
"source": [
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
"run_command('kubectl config current-context')"
],
"metadata": {
"azdata_cell_guid": "a38f8b3a-f93a-484c-b9e2-4eba3ed99cc2",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 0
},
{
"cell_type": "markdown",
"source": [
"### **Create deployment configuration files**"
],
"metadata": {
"azdata_cell_guid": "6d78da36-6af5-4309-baad-bc81bb2cdb7f"
}
},
{
"cell_type": "code",
"source": [
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
"bdcJsonObj = json.loads(bdc_json)\n",
"controlJsonObj = json.loads(control_json)\n",
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
"bdcJsonFile.close()\n",
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
"controlJsonFile.close()\n",
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
},
{
"cell_type": "markdown",
"source": [
"### **Create SQL Server 2019 Big Data Cluster**"
],
"metadata": {
"azdata_cell_guid": "7d56d262-8cd5-49e4-b745-332c6e7a3cb2"
}
},
{
"cell_type": "code",
"source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"if mssql_auth_mode == \"ad\":\n",
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_USERNAME\"] = mssql_domain_service_account_username\n",
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_PASSWORD\"] = mssql_domain_service_account_password\n",
"if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\tkubectl get pods -n {mssql_cluster_name} ')\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 7
}
]
}

View File

@@ -6,7 +6,7 @@
}, },
"language_info": { "language_info": {
"name": "python", "name": "python",
"version": "3.7.3", "version": "3.6.6",
"mimetype": "text/x-python", "mimetype": "text/x-python",
"codemirror_mode": { "codemirror_mode": {
"name": "ipython", "name": "ipython",
@@ -85,11 +85,24 @@
"run_command('az --version')" "run_command('az --version')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "326645cf-022a-47f2-8aff-37de71da8955" "azdata_cell_guid": "326645cf-022a-47f2-8aff-37de71da8955",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 1 "execution_count": 1
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "8716915b-1439-431b-ab0a-0221ef94cb7f"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -115,21 +128,14 @@
"print('You can also use the controller password to access Knox and SQL Server.')" "print('You can also use the controller password to access Knox and SQL Server.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "17e5d087-7128-4d02-8c16-fe1ddee675e5" "azdata_cell_guid": "17e5d087-7128-4d02-8c16-fe1ddee675e5",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 2 "execution_count": 2
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "4945bace-a50a-4e58-b55c-e9736106f805"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -148,7 +154,10 @@
"run_command(f'az login')" "run_command(f'az login')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083" "azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 5 "execution_count": 5
@@ -173,7 +182,10 @@
"run_command(f'az account show')" "run_command(f'az account show')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c" "azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 6 "execution_count": 6
@@ -193,7 +205,10 @@
"run_command(f'az group create --name {azure_resource_group} --location {azure_region}')" "run_command(f'az group create --name {azure_resource_group} --location {azure_region}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5" "azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 7 "execution_count": 7
@@ -213,7 +228,10 @@
"run_command(f'az aks create --name {aks_cluster_name} --resource-group {azure_resource_group} --generate-ssh-keys --node-vm-size {azure_vm_size} --node-count {azure_vm_count}')" "run_command(f'az aks create --name {aks_cluster_name} --resource-group {azure_resource_group} --generate-ssh-keys --node-vm-size {azure_vm_size} --node-count {azure_vm_count}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d" "azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 8 "execution_count": 8
@@ -233,7 +251,10 @@
"run_command(f'az aks get-credentials --resource-group {azure_resource_group} --name {aks_cluster_name} --admin --overwrite-existing')" "run_command(f'az aks get-credentials --resource-group {azure_resource_group} --name {aks_cluster_name} --admin --overwrite-existing')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437" "azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 9 "execution_count": 9
@@ -250,7 +271,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n", "mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n", "if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n", " os.mkdir(mssql_target_profile)\n",
@@ -265,7 +285,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')" "print(f'Created deployment configuration folder: {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558" "azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 10 "execution_count": 10
@@ -283,14 +306,18 @@
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n", "print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"CONTROLLER_USERNAME\"] = mssql_controller_username\n", "os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"CONTROLLER_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\n", "if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\tkubectl get pods -n {mssql_cluster_name} ')\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')" "run_command(f'azdata bdc create -c {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "c43ea026-ca5e-4e2a-8602-fcc786354168" "azdata_cell_guid": "c43ea026-ca5e-4e2a-8602-fcc786354168",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 11 "execution_count": 11
@@ -307,10 +334,13 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')" "run_command(f'azdata login -n {mssql_cluster_name}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "5120c387-1088-435b-856e-e59f147c45a2" "azdata_cell_guid": "5120c387-1088-435b-856e-e59f147c45a2",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 12 "execution_count": 12
@@ -337,7 +367,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))" "display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "9a5d0aef-a8da-4845-b470-d714435f0304" "azdata_cell_guid": "9a5d0aef-a8da-4845-b470-d714435f0304",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 13 "execution_count": 13
@@ -357,16 +390,19 @@
"source": [ "source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n", "sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
"if sqlEndpoints and len(sqlEndpoints) == 1:\n", "if sqlEndpoints and len(sqlEndpoints) == 1:\n",
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":\"sa\",\"password\":' + json.dumps(mssql_password) + '}'\n", " connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n", " display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
"else:\n", "else:\n",
" sys.exit('Could not find the SQL Server Master instance endpoint.')" " sys.exit('Could not find the SQL Server Master instance endpoint.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "1c9d1f2c-62ba-4070-920a-d30b67bdcc7c" "azdata_cell_guid": "1c9d1f2c-62ba-4070-920a-d30b67bdcc7c",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 14 "execution_count": 14
} }
] ]
} }

View File

@@ -6,7 +6,7 @@
}, },
"language_info": { "language_info": {
"name": "python", "name": "python",
"version": "3.7.3", "version": "3.6.6",
"mimetype": "text/x-python", "mimetype": "text/x-python",
"codemirror_mode": { "codemirror_mode": {
"name": "ipython", "name": "ipython",
@@ -83,11 +83,24 @@
"run_command('azdata --version')" "run_command('azdata --version')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d" "azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 1 "execution_count": 1
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -113,21 +126,14 @@
"print('You can also use the controller password to access Knox and SQL Server.')" "print('You can also use the controller password to access Knox and SQL Server.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac" "azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 3 "execution_count": 3
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "c009edfe-b964-4b28-beeb-02a2c65f9918"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -144,7 +150,10 @@
"run_command('kubectl config current-context')" "run_command('kubectl config current-context')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1" "azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 0 "execution_count": 0
@@ -161,7 +170,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n", "mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n", "if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n", " os.mkdir(mssql_target_profile)\n",
@@ -176,7 +184,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')" "print(f'Created deployment configuration folder: {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021" "azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 6 "execution_count": 6
@@ -194,14 +205,18 @@
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n", "print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"CONTROLLER_USERNAME\"] = mssql_controller_username\n", "os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"CONTROLLER_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\n", "if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\tkubectl get pods -n {mssql_cluster_name} ')\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')" "run_command(f'azdata bdc create -c {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76" "azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 7 "execution_count": 7
@@ -218,10 +233,13 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')" "run_command(f'azdata login -n {mssql_cluster_name}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "79adda27-371d-4dcb-b867-db025f8162a5" "azdata_cell_guid": "79adda27-371d-4dcb-b867-db025f8162a5",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 8 "execution_count": 8
@@ -248,7 +266,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))" "display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "a2202494-fd6c-4534-987d-15c403a5096f" "azdata_cell_guid": "a2202494-fd6c-4534-987d-15c403a5096f",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 9 "execution_count": 9
@@ -268,16 +289,19 @@
"source": [ "source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n", "sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
"if sqlEndpoints and len(sqlEndpoints) == 1:\n", "if sqlEndpoints and len(sqlEndpoints) == 1:\n",
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":\"sa\",\"password\":' + json.dumps(mssql_password) + '}'\n", " connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n", " display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
"else:\n", "else:\n",
" sys.exit('Could not find the SQL Server Master instance endpoint')" " sys.exit('Could not find the SQL Server Master instance endpoint.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "48342355-9d2b-4fa6-b1aa-3bc77d434dfa" "azdata_cell_guid": "48342355-9d2b-4fa6-b1aa-3bc77d434dfa",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 10 "execution_count": 10
} }
] ]
} }

View File

@@ -6,7 +6,7 @@
}, },
"language_info": { "language_info": {
"name": "python", "name": "python",
"version": "3.7.3", "version": "3.6.6",
"mimetype": "text/x-python", "mimetype": "text/x-python",
"codemirror_mode": { "codemirror_mode": {
"name": "ipython", "name": "ipython",
@@ -83,11 +83,24 @@
"run_command('azdata --version')" "run_command('azdata --version')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8" "azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 1 "execution_count": 1
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "e70640d0-6059-4cab-939e-e985a978c0da"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -103,6 +116,8 @@
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n", "invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
"if invoked_by_wizard:\n", "if invoked_by_wizard:\n",
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n", " mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
" if mssql_auth_mode == \"ad\":\n",
" mssql_domain_service_account_password = os.environ[\"AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD\"]\n",
"else:\n", "else:\n",
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n", " mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
" if mssql_password == \"\":\n", " if mssql_password == \"\":\n",
@@ -110,24 +125,21 @@
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n", " confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
" if mssql_password != confirm_password:\n", " if mssql_password != confirm_password:\n",
" sys.exit(f'Passwords do not match.')\n", " sys.exit(f'Passwords do not match.')\n",
" if mssql_auth_mode == \"ad\":\n",
" mssql_domain_service_account_password = getpass.getpass(prompt = 'Domain service account password')\n",
" if mssql_domain_service_account_password == \"\":\n",
" sys.exit(f'Domain service account password is required.')\n",
"print('You can also use the controller password to access Knox and SQL Server.')" "print('You can also use the controller password to access Knox and SQL Server.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e" "azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 3 "execution_count": 3
}, },
{
"cell_type": "markdown",
"source": [
"### **Set variables**\n",
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
],
"metadata": {
"azdata_cell_guid": "1d28aac5-955d-4b15-8b9c-8d6ec2b588fe"
}
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
@@ -161,7 +173,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n", "mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n", "if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n", " os.mkdir(mssql_target_profile)\n",
@@ -176,7 +187,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')" "print(f'Created deployment configuration folder: {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf" "azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 6 "execution_count": 6
@@ -194,14 +208,21 @@
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n", "print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
"os.environ[\"CONTROLLER_USERNAME\"] = mssql_controller_username\n", "os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"CONTROLLER_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n", "os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\n", "if mssql_auth_mode == \"ad\":\n",
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_USERNAME\"] = mssql_domain_service_account_username\n",
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_PASSWORD\"] = mssql_domain_service_account_password\n",
"if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\tkubectl get pods -n {mssql_cluster_name} ')\n",
"run_command(f'azdata bdc create -c {mssql_target_profile}')" "run_command(f'azdata bdc create -c {mssql_target_profile}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b" "azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 7 "execution_count": 7
@@ -218,10 +239,13 @@
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')" "run_command(f'azdata login -n {mssql_cluster_name}')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94" "azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 8 "execution_count": 8
@@ -248,7 +272,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))" "display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6" "azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 9 "execution_count": 9
@@ -268,16 +295,19 @@
"source": [ "source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n", "sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
"if sqlEndpoints and len(sqlEndpoints) == 1:\n", "if sqlEndpoints and len(sqlEndpoints) == 1:\n",
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":\"sa\",\"password\":' + json.dumps(mssql_password) + '}'\n", " connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n", " display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
"else:\n", "else:\n",
" sys.exit('Could not find the SQL Server Master instance endpoint')" " sys.exit('Could not find the SQL Server Master instance endpoint.')"
], ],
"metadata": { "metadata": {
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503" "azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503",
"tags": [
"hide_input"
]
}, },
"outputs": [], "outputs": [],
"execution_count": 10 "execution_count": 10
} }
] ]
} }

View File

@@ -26,16 +26,6 @@
], ],
"contributes": { "contributes": {
"commands": [ "commands": [
{
"command": "azdata.resource.sql-image.deploy",
"title": "%deploy-sql-image-command-name%",
"category": "%deploy-resource-command-category%"
},
{
"command": "azdata.resource.sql-bdc.deploy",
"title": "%deploy-sql-bdc-command-name%",
"category": "%deploy-resource-command-category%"
},
{ {
"command": "azdata.resource.deploy", "command": "azdata.resource.deploy",
"title": "%deploy-resource-command-name%", "title": "%deploy-resource-command-name%",
@@ -55,14 +45,6 @@
} }
], ],
"dataExplorer/action": [ "dataExplorer/action": [
{
"command": "azdata.resource.sql-image.deploy",
"group": "secondary"
},
{
"command": "azdata.resource.sql-bdc.deploy",
"group": "secondary"
},
{ {
"command": "azdata.resource.deploy", "command": "azdata.resource.deploy",
"group": "secondary" "group": "secondary"
@@ -72,6 +54,7 @@
"resourceDeploymentTypes": [ "resourceDeploymentTypes": [
{ {
"name": "sql-image", "name": "sql-image",
"displayIndex": 2,
"displayName": "%resource-type-sql-image-display-name%", "displayName": "%resource-type-sql-image-display-name%",
"description": "%resource-type-sql-image-description%", "description": "%resource-type-sql-image-description%",
"platforms": "*", "platforms": "*",
@@ -202,6 +185,7 @@
}, },
{ {
"name": "sql-bdc", "name": "sql-bdc",
"displayIndex": 3,
"displayName": "%resource-type-sql-bdc-display-name%", "displayName": "%resource-type-sql-bdc-display-name%",
"description": "%resource-type-sql-bdc-description%", "description": "%resource-type-sql-bdc-description%",
"platforms": "*", "platforms": "*",
@@ -243,7 +227,8 @@
{ {
"wizard": { "wizard": {
"type": "new-aks", "type": "new-aks",
"notebook": "%bdc-2019-aks-notebook%" "notebook": "%bdc-2019-aks-notebook%",
"azdata_notebook": "%azdata-bdc-2019-aks-notebook%"
}, },
"requiredTools": [ "requiredTools": [
{ {
@@ -261,7 +246,8 @@
{ {
"wizard": { "wizard": {
"type": "existing-aks", "type": "existing-aks",
"notebook": "%bdc-2019-existing-aks-notebook%" "notebook": "%bdc-2019-existing-aks-notebook%",
"azdata_notebook": "%azdata-bdc-2019-existing-aks-notebook"
}, },
"requiredTools": [ "requiredTools": [
{ {
@@ -276,7 +262,8 @@
{ {
"wizard": { "wizard": {
"type": "existing-kubeadm", "type": "existing-kubeadm",
"notebook": "%bdc-2019-existing-kubeadm-notebook%" "notebook": "%bdc-2019-existing-kubeadm-notebook%",
"azdata_notebook": "%azdata-bdc-2019-existing-kubeadm-notebook%"
}, },
"requiredTools": [ "requiredTools": [
{ {
@@ -309,6 +296,7 @@
}, },
{ {
"name": "sql-windows-setup", "name": "sql-windows-setup",
"displayIndex": 1,
"displayName": "%resource-type-sql-windows-setup-display-name%", "displayName": "%resource-type-sql-windows-setup-display-name%",
"description": "%resource-type-sql-windows-setup-description%", "description": "%resource-type-sql-windows-setup-description%",
"platforms": [ "platforms": [

View File

@@ -1,12 +1,10 @@
{ {
"extension-displayName": "SQL Server Deployment extension for Azure Data Studio", "extension-displayName": "SQL Server Deployment extension for Azure Data Studio",
"extension-description": "Provides a notebook-based experience to deploy Microsoft SQL Server", "extension-description": "Provides a notebook-based experience to deploy Microsoft SQL Server",
"deploy-sql-image-command-name": "Deploy SQL Server on Docker…", "deploy-resource-command-name": "Deploy SQL Server…",
"deploy-sql-bdc-command-name": "Deploy SQL Server Big Data Cluster…",
"deploy-resource-command-name": "More deployment options…",
"deploy-resource-command-category": "Deployment", "deploy-resource-command-category": "Deployment",
"resource-type-sql-image-display-name": "SQL Server container image", "resource-type-sql-image-display-name": "SQL Server container image",
"resource-type-sql-image-description": "Run SQL Server container image with Docker", "resource-type-sql-image-description": "Run SQL Server container image with docker",
"resource-type-sql-bdc-display-name": "SQL Server Big Data Cluster", "resource-type-sql-bdc-display-name": "SQL Server Big Data Cluster",
"resource-type-sql-bdc-description": "SQL Server Big Data Cluster allows you to deploy scalable clusters of SQL Server, Spark, and HDFS containers running on Kubernetes", "resource-type-sql-bdc-description": "SQL Server Big Data Cluster allows you to deploy scalable clusters of SQL Server, Spark, and HDFS containers running on Kubernetes",
"version-display-name": "Version", "version-display-name": "Version",
@@ -14,7 +12,7 @@
"sql-2019-display-name": "SQL Server 2019 RC", "sql-2019-display-name": "SQL Server 2019 RC",
"sql-2017-docker-notebook": "./notebooks/docker/2017/deploy-sql2017-image.ipynb", "sql-2017-docker-notebook": "./notebooks/docker/2017/deploy-sql2017-image.ipynb",
"sql-2019-docker-notebook": "./notebooks/docker/2019/deploy-sql2019-image.ipynb", "sql-2019-docker-notebook": "./notebooks/docker/2019/deploy-sql2019-image.ipynb",
"bdc-2019-display-name": "SQL Server 2019 RC Big Data Cluster", "bdc-2019-display-name": "SQL Server 2019 RC",
"bdc-deployment-target": "Deployment target", "bdc-deployment-target": "Deployment target",
"bdc-deployment-target-new-aks": "New Azure Kubernetes Service Cluster", "bdc-deployment-target-new-aks": "New Azure Kubernetes Service Cluster",
"bdc-deployment-target-existing-aks": "Existing Azure Kubernetes Service Cluster", "bdc-deployment-target-existing-aks": "Existing Azure Kubernetes Service Cluster",
@@ -22,8 +20,11 @@
"bdc-2019-aks-notebook": "./notebooks/bdc/2019/deploy-bdc-aks.ipynb", "bdc-2019-aks-notebook": "./notebooks/bdc/2019/deploy-bdc-aks.ipynb",
"bdc-2019-existing-aks-notebook": "./notebooks/bdc/2019/deploy-bdc-existing-aks.ipynb", "bdc-2019-existing-aks-notebook": "./notebooks/bdc/2019/deploy-bdc-existing-aks.ipynb",
"bdc-2019-existing-kubeadm-notebook": "./notebooks/bdc/2019/deploy-bdc-existing-kubeadm.ipynb", "bdc-2019-existing-kubeadm-notebook": "./notebooks/bdc/2019/deploy-bdc-existing-kubeadm.ipynb",
"docker-sql-2017-title": "Deploy SQL Server 2017 container images with Docker", "azdata-bdc-2019-aks-notebook": "./notebooks/bdc/2019/azdata/deploy-bdc-aks.ipynb",
"docker-sql-2019-title": "Deploy SQL Server 2019 container images with Docker", "azdata-bdc-2019-existing-aks-notebook": "./notebooks/bdc/2019/azdata/deploy-bdc-existing-aks.ipynb",
"azdata-bdc-2019-existing-kubeadm-notebook": "./notebooks/bdc/2019/azdata/deploy-bdc-existing-kubeadm.ipynb",
"docker-sql-2017-title": "Deploy SQL Server 2017 container images with docker",
"docker-sql-2019-title": "Deploy SQL Server 2019 container images with docker",
"docker-container-name-field": "Container name", "docker-container-name-field": "Container name",
"docker-sql-password-field": "SQL Server password", "docker-sql-password-field": "SQL Server password",
"docker-confirm-sql-password-field": "Confirm password", "docker-confirm-sql-password-field": "Confirm password",
@@ -52,5 +53,5 @@
"bdc-agreement": "I accept {0}, {1} and {2}.", "bdc-agreement": "I accept {0}, {1} and {2}.",
"bdc-agreement-privacy-statement":"Microsoft Privacy Statement", "bdc-agreement-privacy-statement":"Microsoft Privacy Statement",
"bdc-agreement-azdata-eula":"azdata License Terms", "bdc-agreement-azdata-eula":"azdata License Terms",
"bdc-agreement-bdc-eula":"SQL Server Big Data Cluster License Terms" "bdc-agreement-bdc-eula":"SQL Server License Terms"
} }

View File

@@ -16,6 +16,7 @@ export interface ResourceType {
options: ResourceTypeOption[]; options: ResourceTypeOption[];
providers: DeploymentProvider[]; providers: DeploymentProvider[];
agreement?: AgreementInfo; agreement?: AgreementInfo;
displayIndex?: number;
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined; getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
} }
@@ -92,6 +93,7 @@ export type DeploymentProvider = DialogDeploymentProvider | WizardDeploymentProv
export interface WizardInfo { export interface WizardInfo {
notebook: string | NotebookInfo; notebook: string | NotebookInfo;
azdata_notebook: string | NotebookInfo;
type: BdcDeploymentType; type: BdcDeploymentType;
} }
@@ -161,6 +163,9 @@ export interface FieldInfo {
useCustomValidator?: boolean; useCustomValidator?: boolean;
labelPosition?: LabelPosition; // overwrite the labelPosition of SectionInfo. labelPosition?: LabelPosition; // overwrite the labelPosition of SectionInfo.
fontStyle?: FontStyle; fontStyle?: FontStyle;
labelFontWeight?: FontWeight;
links?: azdata.LinkArea[];
editable?: boolean; // for editable dropdown
} }
export enum LabelPosition { export enum LabelPosition {
@@ -173,6 +178,11 @@ export enum FontStyle {
Italic = 'italic' Italic = 'italic'
} }
export enum FontWeight {
Normal = 'normal',
Bold = 'bold'
}
export enum FieldType { export enum FieldType {
Text = 'text', Text = 'text',
Number = 'number', Number = 'number',

View File

@@ -5,23 +5,42 @@
import * as path from 'path'; import * as path from 'path';
import { IPlatformService } from './platformService'; import { IPlatformService } from './platformService';
import { BigDataClusterDeploymentProfile } from './bigDataClusterDeploymentProfile'; import { BigDataClusterDeploymentProfile } from './bigDataClusterDeploymentProfile';
import { BdcDeploymentType } from '../interfaces';
interface BdcConfigListOutput { interface BdcConfigListOutput {
stdout: string[]; result: string[];
}
export interface BdcEndpoint {
endpoint: string;
name: 'sql-server-master';
} }
export interface IAzdataService { export interface IAzdataService {
getDeploymentProfiles(): Promise<BigDataClusterDeploymentProfile[]>; getDeploymentProfiles(deploymentType: BdcDeploymentType): Promise<BigDataClusterDeploymentProfile[]>;
getEndpoints(clusterName: string, userName: string, password: string): Promise<BdcEndpoint[]>;
} }
export class AzdataService implements IAzdataService { export class AzdataService implements IAzdataService {
constructor(private platformService: IPlatformService) { constructor(private platformService: IPlatformService) {
} }
public async getDeploymentProfiles(): Promise<BigDataClusterDeploymentProfile[]> { public async getDeploymentProfiles(deploymentType: BdcDeploymentType): Promise<BigDataClusterDeploymentProfile[]> {
let profilePrefix: string;
switch (deploymentType) {
case BdcDeploymentType.NewAKS:
case BdcDeploymentType.ExistingAKS:
profilePrefix = 'aks';
break;
case BdcDeploymentType.ExistingKubeAdm:
profilePrefix = 'kubeadm';
break;
default:
throw new Error(`Unknown deployment type: ${deploymentType}`);
}
await this.ensureWorkingDirectoryExists(); await this.ensureWorkingDirectoryExists();
const profileNames = await this.getDeploymentProfileNames(); const profileNames = await this.getDeploymentProfileNames();
return await Promise.all(profileNames.map(profile => this.getDeploymentProfileInfo(profile))); return await Promise.all(profileNames.filter(profile => profile.startsWith(profilePrefix)).map(profile => this.getDeploymentProfileInfo(profile)));
} }
private async getDeploymentProfileNames(): Promise<string[]> { private async getDeploymentProfileNames(): Promise<string[]> {
@@ -29,17 +48,16 @@ export class AzdataService implements IAzdataService {
// azdata requires this environment variables to be set // azdata requires this environment variables to be set
env['ACCEPT_EULA'] = 'yes'; env['ACCEPT_EULA'] = 'yes';
const cmd = 'azdata bdc config list -o json'; const cmd = 'azdata bdc config list -o json';
// Run the command twice to workaround the issue: const stdout = await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
// First time use of the azdata will have extra EULA related string in the output
// there is no easy and reliable way to filter out the profile names from it.
await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
const stdout = await this.platformService.runCommand(cmd);
const output = <BdcConfigListOutput>JSON.parse(stdout); const output = <BdcConfigListOutput>JSON.parse(stdout);
return output.stdout; return output.result;
} }
private async getDeploymentProfileInfo(profileName: string): Promise<BigDataClusterDeploymentProfile> { private async getDeploymentProfileInfo(profileName: string): Promise<BigDataClusterDeploymentProfile> {
await this.platformService.runCommand(`azdata bdc config init --source ${profileName} --target ${profileName} --force`, { workingDirectory: this.platformService.storagePath() }); const env: NodeJS.ProcessEnv = {};
// azdata requires this environment variables to be set
env['ACCEPT_EULA'] = 'yes';
await this.platformService.runCommand(`azdata bdc config init --source ${profileName} --target ${profileName} --force`, { workingDirectory: this.platformService.storagePath(), additionalEnvironmentVariables: env });
const configObjects = await Promise.all([ const configObjects = await Promise.all([
this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'bdc.json')), this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'bdc.json')),
this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'control.json')) this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'control.json'))
@@ -56,4 +74,16 @@ export class AzdataService implements IAzdataService {
private async getJsonObjectFromFile(path: string): Promise<any> { private async getJsonObjectFromFile(path: string): Promise<any> {
return JSON.parse(await this.platformService.readTextFile(path)); return JSON.parse(await this.platformService.readTextFile(path));
} }
public async getEndpoints(clusterName: string, userName: string, password: string): Promise<BdcEndpoint[]> {
const env: NodeJS.ProcessEnv = {};
env['AZDATA_USERNAME'] = userName;
env['AZDATA_PASSWORD'] = password;
env['ACCEPT_EULA'] = 'yes';
let cmd = 'azdata login -n ' + clusterName;
await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
cmd = 'azdata bdc endpoint list';
const stdout = await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
return <BdcEndpoint[]>JSON.parse(stdout);
}
} }

View File

@@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* 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 { AuthenticationMode } from '../ui/deployClusterWizard/deployClusterWizardModel';
export const SqlServerMasterResource = 'master'; export const SqlServerMasterResource = 'master';
export const DataResource = 'data-0'; export const DataResource = 'data-0';
export const HdfsResource = 'storage-0'; export const HdfsResource = 'storage-0';
@@ -11,15 +11,26 @@ export const NameNodeResource = 'nmnode-0';
export const SparkHeadResource = 'sparkhead'; export const SparkHeadResource = 'sparkhead';
export const ZooKeeperResource = 'zookeeper'; export const ZooKeeperResource = 'zookeeper';
export const SparkResource = 'spark-0'; export const SparkResource = 'spark-0';
export const HadrEnabledSetting = 'hadr.enabled';
interface ServiceEndpoint { interface ServiceEndpoint {
port: number; port: number;
serviceType: ServiceType; serviceType: ServiceType;
name: EndpointName; name: EndpointName;
dnsName?: string;
} }
type ServiceType = 'NodePort' | 'LoadBalancer'; type ServiceType = 'NodePort' | 'LoadBalancer';
type EndpointName = 'Controller' | 'Master' | 'Knox' | 'MasterSecondary'; type EndpointName = 'Controller' | 'Master' | 'Knox' | 'MasterSecondary' | 'AppServiceProxy' | 'ServiceProxy';
export interface ActiveDirectorySettings {
organizationalUnit: string;
domainControllerFQDNs: string;
dnsIPAddresses: string;
domainDNSName: string;
clusterUsers: string;
clusterAdmins: string;
appReaders?: string;
appOwners?: string;
}
export class BigDataClusterDeploymentProfile { export class BigDataClusterDeploymentProfile {
constructor(private _profileName: string, private _bdcConfig: any, private _controlConfig: any) { constructor(private _profileName: string, private _bdcConfig: any, private _controlConfig: any) {
@@ -39,6 +50,30 @@ export class BigDataClusterDeploymentProfile {
this._bdcConfig.metadata.name = value; this._bdcConfig.metadata.name = value;
} }
public get registry(): string {
return this._controlConfig.spec.docker.registry;
}
public set registry(value: string) {
this._controlConfig.spec.docker.registry = value;
}
public get repository(): string {
return this._controlConfig.spec.docker.repository;
}
public set repository(value: string) {
this._controlConfig.spec.docker.repository = value;
}
public get imageTag(): string {
return this._controlConfig.spec.docker.imageTag;
}
public set imageTag(value: string) {
this._controlConfig.spec.docker.imageTag = value;
}
public get bdcConfig(): any { public get bdcConfig(): any {
return this._bdcConfig; return this._bdcConfig;
} }
@@ -107,15 +142,6 @@ export class BigDataClusterDeploymentProfile {
return this._bdcConfig.spec.resources[SparkResource] ? this.getReplicas(SparkResource) : 0; return this._bdcConfig.spec.resources[SparkResource] ? this.getReplicas(SparkResource) : 0;
} }
public get hadrEnabled(): boolean {
const value = this._bdcConfig.spec.resources[SqlServerMasterResource].spec.settings.sql[HadrEnabledSetting];
return value === true || value === 'true';
}
public set hadrEnabled(value: boolean) {
this._bdcConfig.spec.resources[SqlServerMasterResource].spec.settings.sql[HadrEnabledSetting] = value;
}
public get includeSpark(): boolean { public get includeSpark(): boolean {
return <boolean>this._bdcConfig.spec.resources[HdfsResource].spec.settings.spark.includeSpark; return <boolean>this._bdcConfig.spec.resources[HdfsResource].spec.settings.spark.includeSpark;
} }
@@ -175,32 +201,48 @@ export class BigDataClusterDeploymentProfile {
return this.getEndpointPort(this._controlConfig.spec.endpoints, 'Controller', 30080); return this.getEndpointPort(this._controlConfig.spec.endpoints, 'Controller', 30080);
} }
public set controllerPort(port: number) { public setControllerEndpoint(port: number, dnsName?: string) {
this.setEndpointPort(this._controlConfig.spec.endpoints, 'Controller', port); this.setEndpoint(this._controlConfig.spec.endpoints, 'Controller', port, dnsName);
}
public get serviceProxyPort(): number {
return this.getEndpointPort(this._controlConfig.spec.endpoints, 'ServiceProxy', 30080);
}
public setServiceProxyEndpoint(port: number, dnsName?: string) {
this.setEndpoint(this._controlConfig.spec.endpoints, 'ServiceProxy', port, dnsName);
}
public get appServiceProxyPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.appproxy.spec.endpoints, 'AppServiceProxy', 30777);
}
public setAppServiceProxyEndpoint(port: number, dnsName?: string) {
this.setEndpoint(this._bdcConfig.spec.resources.appproxy.spec.endpoints, 'AppServiceProxy', port, dnsName);
} }
public get sqlServerPort(): number { public get sqlServerPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', 31433); return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', 31433);
} }
public set sqlServerPort(port: number) { public setSqlServerEndpoint(port: number, dnsName?: string) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', port); this.setEndpoint(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', port, dnsName);
} }
public get sqlServerReadableSecondaryPort(): number { public get sqlServerReadableSecondaryPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', 31436); return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', 31436);
} }
public set sqlServerReadableSecondaryPort(port: number) { public setSqlServerReadableSecondaryEndpoint(port: number, dnsName?: string) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', port); this.setEndpoint(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', port, dnsName);
} }
public get gatewayPort(): number { public get gatewayPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', 30443); return this.getEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', 30443);
} }
public set gatewayPort(port: number) { public setGatewayEndpoint(port: number, dnsName?: string) {
this.setEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', port); this.setEndpoint(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', port, dnsName);
} }
public addSparkResource(replicas: number): void { public addSparkResource(replicas: number): void {
@@ -220,8 +262,32 @@ export class BigDataClusterDeploymentProfile {
} }
public get activeDirectorySupported(): boolean { public get activeDirectorySupported(): boolean {
// TODO: Implement AD authentication // The profiles that highlight the AD authentication feature will have a security secion in the control.json for the AD settings.
return false; return 'security' in this._controlConfig;
}
public setAuthenticationMode(mode: string): void {
// If basic authentication is picked, the security section must be removed
// otherwise azdata will throw validation error
if (mode === AuthenticationMode.Basic && 'security' in this._controlConfig) {
delete this._controlConfig.security;
}
}
public setActiveDirectorySettings(adSettings: ActiveDirectorySettings): void {
this._controlConfig.security.ouDistinguishedName = adSettings.organizationalUnit;
this._controlConfig.security.dnsIpAddresses = this.splitByComma(adSettings.dnsIPAddresses);
this._controlConfig.security.domainControllerFullyQualifiedDns = this.splitByComma(adSettings.domainControllerFQDNs);
this._controlConfig.security.domainDnsName = adSettings.domainDNSName;
this._controlConfig.security.realm = adSettings.domainDNSName.toUpperCase();
this._controlConfig.security.clusterAdmins = this.splitByComma(adSettings.clusterAdmins);
this._controlConfig.security.clusterUsers = this.splitByComma(adSettings.clusterUsers);
if (adSettings.appReaders) {
this._controlConfig.security.appReaders = this.splitByComma(adSettings.appReaders);
}
if (adSettings.appOwners) {
this._controlConfig.security.appOwners = this.splitByComma(adSettings.appOwners);
}
} }
public getBdcJson(readable: boolean = true): string { public getBdcJson(readable: boolean = true): string {
@@ -249,16 +315,27 @@ export class BigDataClusterDeploymentProfile {
return endpoint ? endpoint.port : defaultValue; return endpoint ? endpoint.port : defaultValue;
} }
private setEndpointPort(endpoints: ServiceEndpoint[], name: EndpointName, port: number): void { private setEndpoint(endpoints: ServiceEndpoint[], name: EndpointName, port: number, dnsName?: string): void {
const endpoint = endpoints.find(endpoint => endpoint.name === name); const endpoint = endpoints.find(endpoint => endpoint.name === name);
if (endpoint) { if (endpoint) {
endpoint.port = port; endpoint.port = port;
endpoint.dnsName = dnsName;
} else { } else {
endpoints.push({ const newEndpoint: ServiceEndpoint = {
name: name, name: name,
serviceType: 'NodePort', serviceType: 'NodePort',
port: port port: port
}); };
// for newly added endpoint, we cannot have blank value for the dnsName, only set it if it is not empty
if (dnsName) {
newEndpoint.dnsName = dnsName;
}
endpoints.push(newEndpoint);
} }
} }
private splitByComma(value: string): string[] {
// split by comma, then remove trailing spaces for each item and finally remove the empty values.
return value.split(',').map(v => v && v.trim()).filter(v => v !== '' && v !== undefined);
}
} }

View File

@@ -10,10 +10,32 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { IPlatformService } from './platformService'; import { IPlatformService } from './platformService';
import { NotebookInfo } from '../interfaces'; import { NotebookInfo } from '../interfaces';
import { getErrorMessage, getDateTimeString } from '../utils';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export interface Notebook {
cells: NotebookCell[];
}
export interface NotebookCell {
cell_type: 'code';
source: string[];
metadata: {};
outputs: string[];
execution_count: number;
}
export interface NotebookExecutionResult {
succeeded: boolean;
outputNotebook?: string;
errorMessage?: string;
}
export interface INotebookService { export interface INotebookService {
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor>; launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor>;
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor>;
getNotebook(notebook: string | NotebookInfo): Promise<Notebook>;
executeNotebook(notebook: any, env: NodeJS.ProcessEnv): Promise<NotebookExecutionResult>;
} }
export class NotebookService implements INotebookService { export class NotebookService implements INotebookService {
@@ -21,32 +43,89 @@ export class NotebookService implements INotebookService {
constructor(private platformService: IPlatformService, private extensionPath: string) { } constructor(private platformService: IPlatformService, private extensionPath: string) { }
/** /**
* Copy the notebook to the user's home directory and launch the notebook from there. * Launch notebook with file path
* @param notebook the path of the notebook * @param notebook the path of the notebook
*/ */
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> { launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> {
const notebookPath = this.getNotebook(notebook); return this.getNotebookFullPath(notebook).then(notebookPath => {
const notebookFullPath = path.join(this.extensionPath, notebookPath); return this.showNotebookAsUntitled(notebookPath);
return this.platformService.fileExists(notebookPath).then((notebookPathExists) => {
if (notebookPathExists) {
return this.showNotebookAsUntitled(notebookPath);
} else {
return this.platformService.fileExists(notebookFullPath).then(notebookFullPathExists => {
if (notebookFullPathExists) {
return this.showNotebookAsUntitled(notebookFullPath);
} else {
throw localize('resourceDeployment.notebookNotFound', "The notebook {0} does not exist", notebookPath);
}
});
}
}); });
} }
/**
* Launch notebook with file path
* @param title the title of the notebook
* @param content the notebook content
*/
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor> {
const uri: vscode.Uri = vscode.Uri.parse(`untitled:${title}`);
return azdata.nb.showNotebookDocument(uri, {
connectionProfile: undefined,
preview: false,
initialContent: content,
initialDirtyState: false
});
}
async getNotebook(notebook: string | NotebookInfo): Promise<Notebook> {
const notebookPath = await this.getNotebookFullPath(notebook);
return <Notebook>JSON.parse(await this.platformService.readTextFile(notebookPath));
}
async executeNotebook(notebook: Notebook, env: NodeJS.ProcessEnv): Promise<NotebookExecutionResult> {
const content = JSON.stringify(notebook, undefined, 4);
const fileName = `nb-${getDateTimeString()}.ipynb`;
const workingDirectory = this.platformService.storagePath();
const notebookFullPath = path.join(workingDirectory, fileName);
const outputFullPath = path.join(workingDirectory, `output-${fileName}`);
try {
await this.platformService.saveTextFile(content, notebookFullPath);
await this.platformService.runCommand(`azdata notebook run --path "${notebookFullPath}" --output-path "${workingDirectory}" --timeout -1`,
{
additionalEnvironmentVariables: env,
workingDirectory: workingDirectory
});
return {
succeeded: true
};
}
catch (error) {
const outputExists = await this.platformService.fileExists(outputFullPath);
return {
succeeded: false,
outputNotebook: outputExists ? await this.platformService.readTextFile(outputFullPath) : undefined,
errorMessage: getErrorMessage(error)
};
} finally {
this.platformService.deleteFile(notebookFullPath);
this.platformService.deleteFile(outputFullPath);
}
}
async getNotebookFullPath(notebook: string | NotebookInfo): Promise<string> {
const notebookPath = this.getNotebookPath(notebook);
let notebookExists = await this.platformService.fileExists(notebookPath);
if (notebookExists) {
// this is for the scenarios when the provider is in a different extension, the full path will be passed in.
return notebookPath;
}
// this is for the scenarios in this extension, the notebook paths are relative path.
const absolutePath = path.join(this.extensionPath, notebookPath);
notebookExists = await this.platformService.fileExists(absolutePath);
if (notebookExists) {
return absolutePath;
} else {
throw new Error(localize('resourceDeployment.notebookNotFound', "The notebook {0} does not exist", notebookPath));
}
}
/** /**
* get the notebook path for current platform * get the notebook path for current platform
* @param notebook the notebook path * @param notebook the notebook path
*/ */
getNotebook(notebook: string | NotebookInfo): string { getNotebookPath(notebook: string | NotebookInfo): string {
let notebookPath; let notebookPath;
if (notebook && !isString(notebook)) { if (notebook && !isString(notebook)) {
const platform = this.platformService.platform(); const platform = this.platformService.platform();

View File

@@ -7,6 +7,7 @@ import * as fs from 'fs';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as cp from 'child_process'; import * as cp from 'child_process';
import { getErrorMessage } from '../utils';
/** /**
* Abstract of platform dependencies * Abstract of platform dependencies
@@ -22,6 +23,8 @@ export interface IPlatformService {
makeDirectory(path: string): Promise<void>; makeDirectory(path: string): Promise<void>;
readTextFile(filePath: string): Promise<string>; readTextFile(filePath: string): Promise<string>;
runCommand(command: string, options?: CommandOptions): Promise<string>; runCommand(command: string, options?: CommandOptions): Promise<string>;
saveTextFile(content: string, path: string): Promise<void>;
deleteFile(path: string, ignoreError?: boolean): Promise<void>;
} }
export interface CommandOptions { export interface CommandOptions {
@@ -91,4 +94,24 @@ export class PlatformService implements IPlatformService {
}); });
}); });
} }
saveTextFile(content: string, path: string): Promise<void> {
return fs.promises.writeFile(path, content, 'utf8');
}
async deleteFile(path: string, ignoreError: boolean = true): Promise<void> {
try {
const exists = await this.fileExists(path);
if (exists) {
fs.promises.unlink(path);
}
}
catch (error) {
if (ignoreError) {
console.error('Error occured deleting file: ', getErrorMessage(error));
} else {
throw error;
}
}
}
} }

View File

@@ -29,7 +29,7 @@ export class DockerTool extends ToolBase {
} }
get displayName(): string { get displayName(): string {
return localize('resourceDeployment.DockerDisplayName', 'Docker'); return localize('resourceDeployment.DockerDisplayName', 'docker');
} }
get homePage(): string { get homePage(): string {

View File

@@ -17,13 +17,13 @@ suite('Notebook Service Tests', function (): void {
const notebookService = new NotebookService(mockPlatformService.object, ''); const notebookService = new NotebookService(mockPlatformService.object, '');
const notebookInput = 'test-notebook.ipynb'; const notebookInput = 'test-notebook.ipynb';
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
let returnValue = notebookService.getNotebook(notebookInput); let returnValue = notebookService.getNotebookPath(notebookInput);
assert.equal(returnValue, notebookInput, 'returned notebook name does not match expected value'); assert.equal(returnValue, notebookInput, 'returned notebook name does not match expected value');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
mockPlatformService.reset(); mockPlatformService.reset();
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
returnValue = notebookService.getNotebook(''); returnValue = notebookService.getNotebookPath('');
assert.equal(returnValue, '', 'returned notebook name does not match expected value is not an empty string'); assert.equal(returnValue, '', 'returned notebook name does not match expected value is not an empty string');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
}); });
@@ -41,19 +41,19 @@ suite('Notebook Service Tests', function (): void {
linux: notebookLinux linux: notebookLinux
}; };
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
let returnValue = notebookService.getNotebook(notebookInput); let returnValue = notebookService.getNotebookPath(notebookInput);
assert.equal(returnValue, notebookWin32, 'returned notebook name does not match expected value for win32 platform'); assert.equal(returnValue, notebookWin32, 'returned notebook name does not match expected value for win32 platform');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
mockPlatformService.reset(); mockPlatformService.reset();
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'darwin'; }); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'darwin'; });
returnValue = notebookService.getNotebook(notebookInput); returnValue = notebookService.getNotebookPath(notebookInput);
assert.equal(returnValue, notebookDarwin, 'returned notebook name does not match expected value for darwin platform'); assert.equal(returnValue, notebookDarwin, 'returned notebook name does not match expected value for darwin platform');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
mockPlatformService.reset(); mockPlatformService.reset();
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'linux'; }); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'linux'; });
returnValue = notebookService.getNotebook(notebookInput); returnValue = notebookService.getNotebookPath(notebookInput);
assert.equal(returnValue, notebookLinux, 'returned notebook name does not match expected value for linux platform'); assert.equal(returnValue, notebookLinux, 'returned notebook name does not match expected value for linux platform');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
}); });

View File

@@ -8,17 +8,20 @@ export const ClusterName_VariableName = 'AZDATA_NB_VAR_BDC_CLUSTER_NAME';
export const AdminUserName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_USERNAME'; export const AdminUserName_VariableName = 'AZDATA_NB_VAR_BDC_CONTROLLER_USERNAME';
export const AdminPassword_VariableName = 'AZDATA_NB_VAR_BDC_ADMIN_PASSWORD'; export const AdminPassword_VariableName = 'AZDATA_NB_VAR_BDC_ADMIN_PASSWORD';
export const AuthenticationMode_VariableName = 'AZDATA_NB_VAR_BDC_AUTHENTICATION_MODE'; export const AuthenticationMode_VariableName = 'AZDATA_NB_VAR_BDC_AUTHENTICATION_MODE';
export const DistinguishedName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DN'; export const OrganizationalUnitDistinguishedName_VariableName = 'AZDATA_NB_VAR_BDC_AD_OUDN';
export const AdminPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_ADMIN_PRINCIPALS'; export const ClusterAdmins_VariableName = 'AZDATA_NB_VAR_BDC_AD_CLUSTER_ADMINS';
export const UserPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_USER_PRINCIPALS'; export const ClusterUsers_VariableName = 'AZDATA_NB_VAR_BDC_AD_CLUSTER_USERS';
export const UpstreamIPAddresses_VariableName = 'AZDATA_NB_VAR_BDC_AD_UPSTREAM_IPADDRESSES'; export const DomainDNSIPAddresses_VariableName = 'AZDATA_NB_VAR_BDC_AD_UPSTREAM_IPADDRESSES';
export const DnsName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DNS_NAME'; export const DomainControllerFQDNs_VariableName = 'AZDATA_NB_VAR_BDC_AD_DC_FQDNs';
export const DomainDNSName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DOMAIN_DNS_NAME';
export const Realm_VariableName = 'AZDATA_NB_VAR_BDC_AD_REALM'; export const Realm_VariableName = 'AZDATA_NB_VAR_BDC_AD_REALM';
export const AppOwnerPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_OWNER_PRINCIPALS'; export const DomainServiceAccountUserName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_USERNAME';
export const AppReaderPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_READER_PRINCIPALS'; export const DomainServiceAccountPassword_VariableName = 'AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD';
export const AppOwners_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_OWNERS';
export const AppReaders_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_READERS';
export const SubscriptionId_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_SUBSCRIPTION'; export const SubscriptionId_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_SUBSCRIPTION';
export const ResourceGroup_VariableName = 'AZDATA_NB_VAR_BDC_RESOURCEGROUP_NAME'; export const ResourceGroup_VariableName = 'AZDATA_NB_VAR_BDC_RESOURCEGROUP_NAME';
export const Region_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_REGION'; export const Location_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_REGION';
export const AksName_VariableName = 'AZDATA_NB_VAR_BDC_AKS_NAME'; export const AksName_VariableName = 'AZDATA_NB_VAR_BDC_AKS_NAME';
export const VMSize_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_VM_SIZE'; export const VMSize_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_VM_SIZE';
export const VMCount_VariableName = 'AZDATA_NB_VAR_BDC_VM_COUNT'; export const VMCount_VariableName = 'AZDATA_NB_VAR_BDC_VM_COUNT';
@@ -57,4 +60,12 @@ export const GatewayDNSName_VariableName = 'AZDATA_NB_VAR_BDC_GATEWAY_DNS';
export const GateWayPort_VariableName = 'AZDATA_NB_VAR_BDC_GATEWAY_PORT'; export const GateWayPort_VariableName = 'AZDATA_NB_VAR_BDC_GATEWAY_PORT';
export const ReadableSecondaryDNSName_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_DNS'; export const ReadableSecondaryDNSName_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_DNS';
export const ReadableSecondaryPort_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_PORT'; export const ReadableSecondaryPort_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_PORT';
export const EnableHADR_VariableName = 'AZDATA_NB_VAR_BDC_ENABLE_HADR'; export const ServiceProxyDNSName_VariableName = 'AZDATA_NB_VAR_BDC_SERVICEPROXY_DNS';
export const ServiceProxyPort_VariableName = 'AZDATA_NB_VAR_BDC_SERVICEPROXY_PORT';
export const AppServiceProxyDNSName_VariableName = 'AZDATA_NB_VAR_BDC_APPSERVICEPROXY_DNS';
export const AppServiceProxyPort_VariableName = 'AZDATA_NB_VAR_BDC_APPSERVICEPROXY_PORT';
export const DockerRepository_VariableName = 'AZDATA_NB_VAR_BDC_REPOSITORY';
export const DockerRegistry_VariableName = 'AZDATA_NB_VAR_BDC_REGISTRY';
export const DockerImageTag_VariableName = 'AZDATA_NB_VAR_BDC_DOCKER_IMAGE_TAG';
export const DockerUsername_VariableName = 'AZDATA_NB_VAR_BDC_DOCKER_USERNAME';
export const DockerPassword_VariableName = 'AZDATA_NB_VAR_BDC_DOCKER_PASSWORD';

View File

@@ -15,14 +15,20 @@ import { ClusterSettingsPage } from './pages/clusterSettingsPage';
import { ServiceSettingsPage } from './pages/serviceSettingsPage'; import { ServiceSettingsPage } from './pages/serviceSettingsPage';
import { TargetClusterContextPage } from './pages/targetClusterPage'; import { TargetClusterContextPage } from './pages/targetClusterPage';
import { IKubeService } from '../../services/kubeService'; import { IKubeService } from '../../services/kubeService';
import { IAzdataService } from '../../services/azdataService'; import { IAzdataService, BdcEndpoint } from '../../services/azdataService';
import { DeploymentProfilePage } from './pages/deploymentProfilePage'; import { DeploymentProfilePage } from './pages/deploymentProfilePage';
import { INotebookService } from '../../services/notebookService'; import { INotebookService } from '../../services/notebookService';
import { DeployClusterWizardModel } from './deployClusterWizardModel'; import { getErrorMessage, getDateTimeString } from '../../utils';
import { DeployClusterWizardModel, AuthenticationMode } from './deployClusterWizardModel';
import * as VariableNames from './constants'; import * as VariableNames from './constants';
import * as os from 'os';
import { join } from 'path';
import * as fs from 'fs';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> { export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> {
private _saveConfigButton: azdata.window.Button;
private _scriptToNotebookButton: azdata.window.Button;
public get kubeService(): IKubeService { public get kubeService(): IKubeService {
return this._kubeService; return this._kubeService;
@@ -36,8 +42,24 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
return this._notebookService; return this._notebookService;
} }
public get saveConfigButton(): azdata.window.Button {
return this._saveConfigButton;
}
public get scriptToNotebookButton(): azdata.window.Button {
return this._scriptToNotebookButton;
}
constructor(private wizardInfo: WizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService) { constructor(private wizardInfo: WizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService) {
super(DeployClusterWizard.getTitle(wizardInfo.type), new DeployClusterWizardModel(wizardInfo.type)); super(DeployClusterWizard.getTitle(wizardInfo.type), new DeployClusterWizardModel(wizardInfo.type));
this._saveConfigButton = azdata.window.createButton(localize('deployCluster.SaveConfigFiles', "Save config files"), 'left');
this._saveConfigButton.hidden = true;
this._scriptToNotebookButton = azdata.window.createButton(localize('deployCluster.ScriptToNotebook', 'Script to Notebook'), 'left');
this._scriptToNotebookButton.hidden = true;
this.addButton(this._saveConfigButton);
this.addButton(this._scriptToNotebookButton);
this.registerDisposable(this._saveConfigButton.onClick(() => this.saveConfigFiles()));
this.registerDisposable(this._scriptToNotebookButton.onClick(() => this.scriptToNotebook()));
} }
public get deploymentType(): BdcDeploymentType { public get deploymentType(): BdcDeploymentType {
@@ -47,23 +69,79 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
protected initialize(): void { protected initialize(): void {
this.setPages(this.getPages()); this.setPages(this.getPages());
this.wizardObject.generateScriptButton.hidden = true; this.wizardObject.generateScriptButton.hidden = true;
this.wizardObject.doneButton.label = localize('deployCluster.ScriptToNotebook', 'Script to Notebook'); this.wizardObject.doneButton.label = localize('deployCluster.Deploy', "Deploy");
} }
protected onCancel(): void { protected onCancel(): void {
} }
protected onOk(): void { protected onOk(): void {
process.env[VariableNames.AdminPassword_VariableName] = this.model.getStringValue(VariableNames.AdminPassword_VariableName); const taskName = localize('resourceDeployment.DeployBDCTask', "Deploy SQL Server Big Data Cluster \"{0}\"", this.model.getStringValue(VariableNames.ClusterName_VariableName));
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => { azdata.tasks.startBackgroundOperation({
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => { displayName: taskName,
editBuilder.insertCell({ description: taskName,
isCancelable: false,
operation: async op => {
op.updateStatus(azdata.TaskStatus.InProgress);
const env: NodeJS.ProcessEnv = {};
this.setEnvironmentVariables(env);
const notebook = await this.notebookService.getNotebook(this.wizardInfo.azdata_notebook);
notebook.cells.splice(3, 0, {
cell_type: 'code', cell_type: 'code',
source: this.model.getCodeCellContentForNotebook() source: this.model.getCodeCellContentForNotebook(),
}, 7); metadata: {},
}); execution_count: 0,
}, (error) => { outputs: []
vscode.window.showErrorMessage(error); });
const result = await this.notebookService.executeNotebook(notebook, env);
if (result.succeeded) {
op.updateStatus(azdata.TaskStatus.Succeeded);
const connectToMasterSql = localize('resourceDeployment.ConnectToMasterSQLServer', "Connect to Master SQL Server");
const selectedOption = await vscode.window.showInformationMessage(localize('resourceDeployment.DeploymentSucceeded', "Successfully deployed SQL Server Big Data Cluster: {0}",
this.model.getStringValue(VariableNames.ClusterName_VariableName)),
connectToMasterSql);
if (selectedOption === connectToMasterSql) {
let endpoints: BdcEndpoint[];
try {
endpoints = await this.azdataService.getEndpoints(this.model.getStringValue(VariableNames.ClusterName_VariableName)!,
this.model.getStringValue(VariableNames.AdminUserName_VariableName)!,
this.model.getStringValue(VariableNames.AdminPassword_VariableName)!);
} catch (error) {
vscode.window.showErrorMessage(localize('resourceDeployment.ErroRetrievingEndpoints', "Failed to retrieve the endpoint list. {0}{1}", os.EOL, getErrorMessage(error)));
return;
}
const sqlEndpoint = endpoints.find(endpoint => endpoint.name === 'sql-server-master');
if (sqlEndpoint) {
vscode.commands.executeCommand('azdata.connect', {
serverName: sqlEndpoint.endpoint,
providerName: 'MSSQL',
authenticationType: 'SqlLogin',
userName: this.model.getStringValue(VariableNames.AdminUserName_VariableName)!,
password: this.model.getStringValue(VariableNames.AdminPassword_VariableName)!
});
} else {
vscode.window.showErrorMessage(localize('resourceDeployment.NoSQLEndpointFound', "Master SQL Server endpoint is not found."));
}
}
} else {
op.updateStatus(azdata.TaskStatus.Failed, result.errorMessage);
if (result.outputNotebook) {
const viewErrorDetail = localize('resourceDeployment.ViewErrorDetail', "View error detail");
const selectedOption = await vscode.window.showErrorMessage(localize('resourceDeployment.DeployFailed', "Failed to deploy SQL Server Big Data Cluster \"{0}\".",
this.model.getStringValue(VariableNames.ClusterName_VariableName)),
viewErrorDetail);
if (selectedOption === viewErrorDetail) {
try {
this.notebookService.launchNotebookWithContent(`deploy-${getDateTimeString()}`, result.outputNotebook);
} catch (error) {
vscode.window.showErrorMessage(localize('resourceDeployment.FailedToOpenNotebook', "An error occured launching the output notebook. {1}{2}.", os.EOL, getErrorMessage(error)));
}
}
} else {
vscode.window.showErrorMessage(localize('resourceDeployment.DeployFailedNoOutputNotebook', "Failed to deploy SQL Server Big Data Cluster and no output notebook was generated."));
}
}
}
}); });
} }
@@ -100,6 +178,58 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
return pages; return pages;
} }
private async saveConfigFiles(): Promise<void> {
const options: vscode.OpenDialogOptions = {
defaultUri: vscode.Uri.file(os.homedir()),
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: localize('deployCluster.SelectConfigFileFolder', "Save config files")
};
const pathArray = await vscode.window.showOpenDialog(options);
if (pathArray && pathArray[0]) {
const targetFolder = pathArray[0].fsPath;
try {
const profile = this.model.createTargetProfile();
await fs.promises.writeFile(join(targetFolder, 'bdc.json'), profile.getBdcJson());
await fs.promises.writeFile(join(targetFolder, 'control.json'), profile.getControlJson());
this.wizardObject.message = {
text: localize('deployCluster.SaveConfigFileSucceeded', "Config files saved to {0}", targetFolder),
level: azdata.window.MessageLevel.Information
};
}
catch (error) {
this.wizardObject.message = {
text: error.message,
level: azdata.window.MessageLevel.Error
};
}
}
}
private scriptToNotebook(): void {
this.setEnvironmentVariables(process.env);
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => {
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
// 5 is the position after the 'Set variables' cell in the deployment notebooks
editBuilder.insertCell({
cell_type: 'code',
source: this.model.getCodeCellContentForNotebook()
}, 5);
});
}, (error) => {
vscode.window.showErrorMessage(error);
});
}
private setEnvironmentVariables(env: NodeJS.ProcessEnv): void {
env[VariableNames.AdminPassword_VariableName] = this.model.getStringValue(VariableNames.AdminPassword_VariableName);
env[VariableNames.DockerPassword_VariableName] = this.model.getStringValue(VariableNames.DockerPassword_VariableName);
if (this.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
env[VariableNames.DomainServiceAccountPassword_VariableName] = this.model.getStringValue(VariableNames.DomainServiceAccountPassword_VariableName);
}
}
static getTitle(type: BdcDeploymentType): string { static getTitle(type: BdcDeploymentType): string {
switch (type) { switch (type) {
case BdcDeploymentType.NewAKS: case BdcDeploymentType.NewAKS:

View File

@@ -15,14 +15,6 @@ export class DeployClusterWizardModel extends Model {
} }
public adAuthSupported: boolean = false; public adAuthSupported: boolean = false;
public get hadrEnabled(): boolean {
return this.getBooleanValue(VariableNames.EnableHADR_VariableName);
}
public set hadrEnabled(value: boolean) {
this.setPropertyValue(VariableNames.EnableHADR_VariableName, value);
}
public get authenticationMode(): string | undefined { public get authenticationMode(): string | undefined {
return this.getStringValue(VariableNames.AuthenticationMode_VariableName); return this.getStringValue(VariableNames.AuthenticationMode_VariableName);
} }
@@ -71,6 +63,13 @@ export class DeployClusterWizardModel extends Model {
const sourceBdcJson = Object.assign({}, this.selectedProfile!.bdcConfig); const sourceBdcJson = Object.assign({}, this.selectedProfile!.bdcConfig);
const sourceControlJson = Object.assign({}, this.selectedProfile!.controlConfig); const sourceControlJson = Object.assign({}, this.selectedProfile!.controlConfig);
const targetDeploymentProfile = new BigDataClusterDeploymentProfile('', sourceBdcJson, sourceControlJson); const targetDeploymentProfile = new BigDataClusterDeploymentProfile('', sourceBdcJson, sourceControlJson);
// docker settings
targetDeploymentProfile.controlConfig.spec.docker = {
registry: this.getStringValue(VariableNames.DockerRegistry_VariableName),
repository: this.getStringValue(VariableNames.DockerRepository_VariableName),
imageTag: this.getStringValue(VariableNames.DockerImageTag_VariableName),
imagePullPolicy: 'Always'
};
// cluster name // cluster name
targetDeploymentProfile.clusterName = this.getStringValue(VariableNames.ClusterName_VariableName)!; targetDeploymentProfile.clusterName = this.getStringValue(VariableNames.ClusterName_VariableName)!;
// storage settings // storage settings
@@ -111,23 +110,37 @@ export class DeployClusterWizardModel extends Model {
} }
targetDeploymentProfile.includeSpark = this.getBooleanValue(VariableNames.IncludeSpark_VariableName); targetDeploymentProfile.includeSpark = this.getBooleanValue(VariableNames.IncludeSpark_VariableName);
targetDeploymentProfile.hadrEnabled = this.getBooleanValue(VariableNames.EnableHADR_VariableName);
// port settings // endpoint settings
targetDeploymentProfile.gatewayPort = this.getIntegerValue(VariableNames.GateWayPort_VariableName); targetDeploymentProfile.setGatewayEndpoint(this.getIntegerValue(VariableNames.GateWayPort_VariableName), this.getStringValue(VariableNames.GatewayDNSName_VariableName));
targetDeploymentProfile.sqlServerPort = this.getIntegerValue(VariableNames.SQLServerPort_VariableName); targetDeploymentProfile.setSqlServerEndpoint(this.getIntegerValue(VariableNames.SQLServerPort_VariableName), this.getStringValue(VariableNames.SQLServerDNSName_VariableName));
targetDeploymentProfile.controllerPort = this.getIntegerValue(VariableNames.ControllerPort_VariableName); targetDeploymentProfile.setControllerEndpoint(this.getIntegerValue(VariableNames.ControllerPort_VariableName), this.getStringValue(VariableNames.ControllerDNSName_VariableName));
targetDeploymentProfile.sqlServerReadableSecondaryPort = this.getIntegerValue(VariableNames.ReadableSecondaryPort_VariableName); targetDeploymentProfile.setSqlServerReadableSecondaryEndpoint(this.getIntegerValue(VariableNames.ReadableSecondaryPort_VariableName), this.getStringValue(VariableNames.ReadableSecondaryDNSName_VariableName));
targetDeploymentProfile.setServiceProxyEndpoint(this.getIntegerValue(VariableNames.ServiceProxyPort_VariableName), this.getStringValue(VariableNames.ServiceProxyDNSName_VariableName));
targetDeploymentProfile.setAppServiceProxyEndpoint(this.getIntegerValue(VariableNames.AppServiceProxyPort_VariableName), this.getStringValue(VariableNames.AppServiceProxyDNSName_VariableName));
targetDeploymentProfile.setAuthenticationMode(this.authenticationMode!);
if (this.authenticationMode === AuthenticationMode.ActiveDirectory) {
targetDeploymentProfile.setActiveDirectorySettings({
organizationalUnit: this.getStringValue(VariableNames.OrganizationalUnitDistinguishedName_VariableName)!,
domainControllerFQDNs: this.getStringValue(VariableNames.DomainControllerFQDNs_VariableName)!,
domainDNSName: this.getStringValue(VariableNames.DomainDNSName_VariableName)!,
dnsIPAddresses: this.getStringValue(VariableNames.DomainDNSIPAddresses_VariableName)!,
clusterAdmins: this.getStringValue(VariableNames.ClusterAdmins_VariableName)!,
clusterUsers: this.getStringValue(VariableNames.ClusterUsers_VariableName)!,
appOwners: this.getStringValue(VariableNames.AppOwners_VariableName),
appReaders: this.getStringValue(VariableNames.AppReaders_VariableName)
});
}
return targetDeploymentProfile; return targetDeploymentProfile;
} }
public getCodeCellContentForNotebook(): string { public getCodeCellContentForNotebook(): string[] {
const profile = this.createTargetProfile(); const profile = this.createTargetProfile();
const statements: string[] = []; const statements: string[] = [];
if (this.deploymentTarget === BdcDeploymentType.NewAKS) { if (this.deploymentTarget === BdcDeploymentType.NewAKS) {
statements.push(`azure_subscription_id = '${this.getStringValue(VariableNames.SubscriptionId_VariableName, '')}'`); statements.push(`azure_subscription_id = '${this.getStringValue(VariableNames.SubscriptionId_VariableName, '')}'`);
statements.push(`azure_region = '${this.getStringValue(VariableNames.Region_VariableName)}'`); statements.push(`azure_region = '${this.getStringValue(VariableNames.Location_VariableName)}'`);
statements.push(`azure_resource_group = '${this.getStringValue(VariableNames.ResourceGroup_VariableName)}'`); statements.push(`azure_resource_group = '${this.getStringValue(VariableNames.ResourceGroup_VariableName)}'`);
statements.push(`azure_vm_size = '${this.getStringValue(VariableNames.VMSize_VariableName)}'`); statements.push(`azure_vm_size = '${this.getStringValue(VariableNames.VMSize_VariableName)}'`);
statements.push(`azure_vm_count = '${this.getStringValue(VariableNames.VMCount_VariableName)}'`); statements.push(`azure_vm_count = '${this.getStringValue(VariableNames.VMCount_VariableName)}'`);
@@ -137,12 +150,20 @@ export class DeployClusterWizardModel extends Model {
statements.push(`mssql_cluster_context = '${this.getStringValue(VariableNames.ClusterContext_VariableName)}'`); statements.push(`mssql_cluster_context = '${this.getStringValue(VariableNames.ClusterContext_VariableName)}'`);
statements.push('os.environ["KUBECONFIG"] = mssql_kube_config_path'); statements.push('os.environ["KUBECONFIG"] = mssql_kube_config_path');
} }
if (this.authenticationMode === AuthenticationMode.ActiveDirectory) {
statements.push(`mssql_domain_service_account_username = '${this.escapeForNotebookCodeCell(this.getStringValue(VariableNames.DomainServiceAccountUserName_VariableName)!)}'`);
}
statements.push(`mssql_cluster_name = '${this.getStringValue(VariableNames.ClusterName_VariableName)}'`); statements.push(`mssql_cluster_name = '${this.getStringValue(VariableNames.ClusterName_VariableName)}'`);
statements.push(`mssql_controller_username = '${this.getStringValue(VariableNames.AdminUserName_VariableName)}'`); statements.push(`mssql_username = '${this.getStringValue(VariableNames.AdminUserName_VariableName)}'`);
statements.push(`mssql_auth_mode = '${this.authenticationMode}'`);
statements.push(`bdc_json = '${profile.getBdcJson(false)}'`); statements.push(`bdc_json = '${profile.getBdcJson(false)}'`);
statements.push(`control_json = '${profile.getControlJson(false)}'`); statements.push(`control_json = '${profile.getControlJson(false)}'`);
if (this.getStringValue(VariableNames.DockerUsername_VariableName) && this.getStringValue(VariableNames.DockerPassword_VariableName)) {
statements.push(`os.environ["DOCKER_USERNAME"] = '${this.getStringValue(VariableNames.DockerUsername_VariableName)}'`);
statements.push(`os.environ["DOCKER_PASSWORD"] = os.environ["${VariableNames.DockerPassword_VariableName}"]`);
}
statements.push(`print('Variables have been set successfully.')`); statements.push(`print('Variables have been set successfully.')`);
return statements.join(EOL); return statements.map(line => line + EOL);
} }
private escapeForNotebookCodeCell(original: string): string { private escapeForNotebookCodeCell(original: string): string {

View File

@@ -9,8 +9,8 @@ import * as nls from 'vscode-nls';
import { DeployClusterWizard } from '../deployClusterWizard'; import { DeployClusterWizard } from '../deployClusterWizard';
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces'; import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
import { WizardPageBase } from '../../wizardPageBase'; import { WizardPageBase } from '../../wizardPageBase';
import { createSection, InputComponents, setModelValues, Validator } from '../../modelViewUtils'; import { createSection, InputComponents, setModelValues, Validator, getDropdownComponent, MissingRequiredInformationErrorMessage } from '../../modelViewUtils';
import { SubscriptionId_VariableName, ResourceGroup_VariableName, Region_VariableName, AksName_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants'; import { SubscriptionId_VariableName, ResourceGroup_VariableName, Location_VariableName, AksName_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> { export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
@@ -26,8 +26,9 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
const azureSection: SectionInfo = { const azureSection: SectionInfo = {
title: '', title: '',
labelPosition: LabelPosition.Left, labelPosition: LabelPosition.Left,
fields: [ spaceBetweenFields: '5px',
{ rows: [{
fields: [{
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.SubscriptionField', "Subscription id"), label: localize('deployCluster.SubscriptionField', "Subscription id"),
required: false, required: false,
@@ -35,42 +36,100 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
placeHolder: localize('deployCluster.SubscriptionPlaceholder', "Use my default Azure subscription"), placeHolder: localize('deployCluster.SubscriptionPlaceholder', "Use my default Azure subscription"),
description: localize('deployCluster.SubscriptionDescription', "The default subscription will be used if you leave this field blank.") description: localize('deployCluster.SubscriptionDescription', "The default subscription will be used if you leave this field blank.")
}, { }, {
type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.SubscriptionHelpText', "{0}"),
links: [
{
text: localize('deployCluster.SubscriptionHelpLink', "View available Azure subscriptions"),
url: 'https://portal.azure.com/#blade/Microsoft_Azure_Billing/SubscriptionsBlade'
}
]
}]
}, {
fields: [{
type: FieldType.DateTimeText, type: FieldType.DateTimeText,
label: localize('deployCluster.ResourceGroupName', "New resource group name"), label: localize('deployCluster.ResourceGroupName', "New resource group name"),
required: true, required: true,
variableName: ResourceGroup_VariableName, variableName: ResourceGroup_VariableName,
defaultValue: 'mssql-' defaultValue: 'mssql-'
}, { }]
type: FieldType.Text, }, {
label: localize('deployCluster.Region', "Region"), fields: [{
type: FieldType.Options,
label: localize('deployCluster.Location', "Location"),
required: true, required: true,
variableName: Region_VariableName, variableName: Location_VariableName,
defaultValue: 'eastus' defaultValue: 'eastus',
editable: true,
// The options are not localized because this is an editable dropdown,
// It would cause confusion to user about what value to type in, if they type in the localized value, we don't know how to process.
options: [
'centralus',
'eastus',
'eastus2',
'northcentralus',
'southcentralus',
'westus',
'westus2',
'canadacentral',
'canadaeast'
]
}, { }, {
type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.LocationHelpText', "{0}"),
links: [
{
text: localize('deployCluster.AzureLocationHelpLink', "View available Azure locations"),
url: 'https://azure.microsoft.com/global-infrastructure/services/?products=kubernetes-service'
}
]
}]
}, {
fields: [{
type: FieldType.DateTimeText, type: FieldType.DateTimeText,
label: localize('deployCluster.AksName', "AKS cluster name"), label: localize('deployCluster.AksName', "AKS cluster name"),
required: true, required: true,
variableName: AksName_VariableName, variableName: AksName_VariableName,
defaultValue: 'mssql-', defaultValue: 'mssql-',
}, { }]
type: FieldType.Number, }, {
label: localize('deployCluster.VMCount', "VM count"), fields: [
required: true, {
variableName: VMCount_VariableName, type: FieldType.Number,
defaultValue: '5', label: localize('deployCluster.VMCount', "VM count"),
min: 1, required: true,
max: 999 variableName: VMCount_VariableName,
}, { defaultValue: '5',
min: 1,
max: 999
}
]
}, {
fields: [{
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.VMSize', "VM size"), label: localize('deployCluster.VMSize', "VM size"),
required: true, required: true,
variableName: VMSize_VariableName, variableName: VMSize_VariableName,
defaultValue: 'Standard_E4s_v3' defaultValue: 'Standard_E8s_v3'
} }, {
] type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.VMSizeHelpText', "{0}"),
links: [
{
text: localize('deployCluster.VMSizeHelpLink', "View available VM sizes"),
url: 'https://docs.microsoft.com/azure/virtual-machines/linux/sizes'
}
]
}]
}]
}; };
this.pageObject.registerContent((view: azdata.ModelView) => { this.pageObject.registerContent((view: azdata.ModelView) => {
const azureGroup = createSection({ const azureGroup = createSection({
sectionInfo: azureSection, sectionInfo: azureSection,
view: view, view: view,
@@ -101,7 +160,28 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
}); });
} }
public onEnter(): void {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
this.wizard.wizardObject.message = { text: '' };
if (pcInfo.newPage > pcInfo.lastPage) {
const location = getDropdownComponent(Location_VariableName, this.inputComponents).value;
if (!location) {
this.wizard.wizardObject.message = {
text: MissingRequiredInformationErrorMessage,
level: azdata.window.MessageLevel.Error
};
}
return !!location;
} else {
return true;
}
});
}
public onLeave(): void { public onLeave(): void {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
setModelValues(this.inputComponents, this.wizard.model); setModelValues(this.inputComponents, this.wizard.model);
} }
} }

View File

@@ -18,6 +18,8 @@ const localize = nls.loadMessageBundle();
const ConfirmPasswordName = 'ConfirmPassword'; const ConfirmPasswordName = 'ConfirmPassword';
export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> { export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
private inputComponents: InputComponents = {}; private inputComponents: InputComponents = {};
private activeDirectorySection!: azdata.FormComponent;
private formBuilder!: azdata.FormBuilder;
constructor(wizard: DeployClusterWizard) { constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.ClusterSettingsPageTitle', "Cluster settings"), super(localize('deployCluster.ClusterSettingsPageTitle', "Cluster settings"),
@@ -39,11 +41,12 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
useCustomValidator: true useCustomValidator: true
}, { }, {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.ControllerUsername', "Controller username"), label: localize('deployCluster.AdminUsername', "Admin username"),
required: true, required: true,
variableName: VariableNames.AdminUserName_VariableName, variableName: VariableNames.AdminUserName_VariableName,
defaultValue: 'admin', defaultValue: 'admin',
useCustomValidator: true useCustomValidator: true,
description: localize('deployCluster.AdminUsernameDescription', "This username will be used for controller and SQL Server. Username for the gateway will be root.")
}, { }, {
type: FieldType.Password, type: FieldType.Password,
label: localize('deployCluster.AdminPassword', "Password"), label: localize('deployCluster.AdminPassword', "Password"),
@@ -51,7 +54,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.AdminPassword_VariableName, variableName: VariableNames.AdminPassword_VariableName,
defaultValue: '', defaultValue: '',
useCustomValidator: true, useCustomValidator: true,
description: localize('deployCluster.AdminPasswordDescription', "You can also use this password to access SQL Server and gateway.") description: localize('deployCluster.AdminPasswordDescription', "This password can be used to access the controller, SQL Server and gateway.")
}, { }, {
type: FieldType.Password, type: FieldType.Password,
label: localize('deployCluster.ConfirmPassword', "Confirm password"), label: localize('deployCluster.ConfirmPassword', "Confirm password"),
@@ -80,58 +83,118 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
] ]
}; };
const dockerSection: SectionInfo = {
labelPosition: LabelPosition.Left,
collapsed: true,
collapsible: true,
title: localize('deployCluster.DockerSettings', "Docker settings"),
fields: [
{
type: FieldType.Text,
label: localize('deployCluster.DockerRegistry', "Registry"),
required: true,
variableName: VariableNames.DockerRegistry_VariableName
}, {
type: FieldType.Text,
label: localize('deployCluster.DockerRepository', "Repository"),
required: true,
variableName: VariableNames.DockerRepository_VariableName
}, {
type: FieldType.Text,
label: localize('deployCluster.DockerImageTag', "Image tag"),
required: true,
variableName: VariableNames.DockerImageTag_VariableName
}, {
type: FieldType.Text,
label: localize('deployCluster.DockerUsername', "Username"),
required: false,
variableName: VariableNames.DockerUsername_VariableName
}, {
type: FieldType.Text,
label: localize('deployCluster.DockerPassword', "Password"),
required: false,
variableName: VariableNames.DockerPassword_VariableName
}
]
};
const activeDirectorySection: SectionInfo = { const activeDirectorySection: SectionInfo = {
labelPosition: LabelPosition.Left, labelPosition: LabelPosition.Left,
title: localize('deployCluster.ActiveDirectorySettings', "Active Directory settings"), title: localize('deployCluster.ActiveDirectorySettings', "Active Directory settings"),
fields: [ fields: [
{ {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.DistinguishedName', "Distinguished name"), label: localize('deployCluster.OuDistinguishedName', "Organizational unit"),
required: true, required: true,
variableName: VariableNames.DistinguishedName_VariableName, variableName: VariableNames.OrganizationalUnitDistinguishedName_VariableName,
useCustomValidator: true,
description: localize('deployCluster.OuDistinguishedNameDescription', "Distinguished name for the organizational unit. For example: OU=bdc,DC=contoso,DC=com.")
}, {
type: FieldType.Text,
label: localize('deployCluster.DomainControllerFQDNs', "Domain controller FQDNs"),
required: true,
variableName: VariableNames.DomainControllerFQDNs_VariableName,
useCustomValidator: true,
placeHolder: localize('deployCluster.DomainControllerFQDNsPlaceHolder', "Use comma to separate the values."),
description: localize('deployCluster.DomainControllerFQDNDescription', "Fully qualified domain names for the domain controller. For example: DC1.CONTOSO.COM. Use comma to separate multiple FQDNs.")
}, {
type: FieldType.Text,
label: localize('deployCluster.DomainDNSIPAddresses', "Domain DNS IP addresses"),
required: true,
variableName: VariableNames.DomainDNSIPAddresses_VariableName,
useCustomValidator: true,
placeHolder: localize('deployCluster.DomainDNSIPAddressesPlaceHolder', "Use comma to separate the values."),
description: localize('deployCluster.DomainDNSIPAddressesDescription', "Domain DNS servers' IP Addresses. Use comma to separate multiple IP addresses.")
}, {
type: FieldType.Text,
label: localize('deployCluster.DomainDNSName', "Domain DNS name"),
required: true,
variableName: VariableNames.DomainDNSName_VariableName,
useCustomValidator: true useCustomValidator: true
}, { }, {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.AdminPrincipals', "Admin principals"), label: localize('deployCluster.ClusterAdmins', "Cluster admin group"),
required: true, required: true,
variableName: VariableNames.AdminPrincipals_VariableName, variableName: VariableNames.ClusterAdmins_VariableName,
useCustomValidator: true,
description: localize('deployCluster.ClusterAdminsDescription', "The Active Directory group for cluster admin.")
}, {
type: FieldType.Text,
label: localize('deployCluster.ClusterUsers', "Cluster users"),
required: true,
variableName: VariableNames.ClusterUsers_VariableName,
useCustomValidator: true,
placeHolder: localize('deployCluster.ClusterUsersPlaceHolder', "Use comma to separate the values."),
description: localize('deployCluster.ClusterUsersDescription', "The Active Directory users/groups with cluster users role. Use comma to separate multiple users/groups.")
}, {
type: FieldType.Text,
label: localize('deployCluster.DomainServiceAccountUserName', "Service account username"),
required: true,
variableName: VariableNames.DomainServiceAccountUserName_VariableName,
useCustomValidator: true,
description: localize('deployCluster.DomainServiceAccountUserNameDescription', "Domain service account for Big Data Cluster")
}, {
type: FieldType.Password,
label: localize('deployCluster.DomainServiceAccountPassword', "Service account password"),
required: true,
variableName: VariableNames.DomainServiceAccountPassword_VariableName,
useCustomValidator: true useCustomValidator: true
}, { }, {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.UserPrincipals', "User principals"), label: localize('deployCluster.AppOwers', "App owners"),
required: true, required: false,
variableName: VariableNames.UserPrincipals_VariableName, variableName: VariableNames.AppOwners_VariableName,
useCustomValidator: true useCustomValidator: true,
placeHolder: localize('deployCluster.AppOwnersPlaceHolder', "Use comma to separate the values."),
description: localize('deployCluster.AppOwnersDescription', "The Active Directory users or groups with app owners role. Use comma to separate multiple users/groups.")
}, { }, {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.UpstreamIPAddresses', "Upstream IP Addresses"), label: localize('deployCluster.AppReaders', "App readers"),
required: true, required: false,
variableName: VariableNames.UpstreamIPAddresses_VariableName, variableName: VariableNames.AppReaders_VariableName,
useCustomValidator: true useCustomValidator: true,
}, { placeHolder: localize('deployCluster.AppReadersPlaceHolder', "Use comma to separate the values."),
type: FieldType.Text, description: localize('deployCluster.AppReadersDescription', "The Active Directory users or groups of app readers. Use comma as separator them if there are multiple users/groups.")
label: localize('deployCluster.DNSName', "DNS name"),
required: true,
variableName: VariableNames.DnsName_VariableName,
useCustomValidator: true
}, {
type: FieldType.Text,
label: localize('deployCluster.Realm', "Realm"),
required: true,
variableName: VariableNames.Realm_VariableName,
useCustomValidator: true
}, {
type: FieldType.Text,
label: localize('deployCluster.AppOnwerPrincipals', "App owner principals"),
required: true,
variableName: VariableNames.AppOwnerPrincipals_VariableName,
useCustomValidator: true
}, {
type: FieldType.Text,
label: localize('deployCluster.AppReaderPrincipals', "App reader principals"),
required: true,
variableName: VariableNames.AppReaderPrincipals_VariableName,
useCustomValidator: true
} }
] ]
}; };
@@ -164,11 +227,26 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
self.validators.push(validator); self.validators.push(validator);
} }
}); });
const dockerSettingsGroup = createSection({
view: view,
container: self.wizard.wizardObject,
sectionInfo: dockerSection,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: azdata.DropDownComponent | azdata.InputBoxComponent | azdata.CheckBoxComponent): void => {
self.inputComponents[name] = component;
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
}
});
const basicSettingsFormItem = { title: '', component: basicSettingsGroup }; const basicSettingsFormItem = { title: '', component: basicSettingsGroup };
const activeDirectoryFormItem = { title: '', component: activeDirectorySettingsGroup }; const dockerSettingsFormItem = { title: '', component: dockerSettingsGroup };
this.activeDirectorySection = { title: '', component: activeDirectorySettingsGroup };
const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName]; const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName];
const formBuilder = view.modelBuilder.formContainer().withFormItems( this.formBuilder = view.modelBuilder.formContainer().withFormItems(
[basicSettingsFormItem], [basicSettingsFormItem, dockerSettingsFormItem],
{ {
horizontal: false, horizontal: false,
componentWidth: '100%' componentWidth: '100%'
@@ -176,76 +254,99 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
); );
this.wizard.registerDisposable(authModeDropdown.onValueChanged(() => { this.wizard.registerDisposable(authModeDropdown.onValueChanged(() => {
const isBasicAuthMode = (<azdata.CategoryValue>authModeDropdown.value).name === 'basic'; const isBasicAuthMode = (<azdata.CategoryValue>authModeDropdown.value).name === 'basic';
if (isBasicAuthMode) { if (isBasicAuthMode) {
formBuilder.removeFormItem(activeDirectoryFormItem); this.formBuilder.removeFormItem(this.activeDirectorySection);
} else { } else {
formBuilder.insertFormItem(activeDirectoryFormItem); this.formBuilder.insertFormItem(this.activeDirectorySection);
} }
})); }));
const form = this.formBuilder.withLayout({ width: '100%' }).component();
const form = formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(form); return view.initializeModel(form);
}); });
} }
public onLeave() { public onLeave() {
setModelValues(this.inputComponents, this.wizard.model); setModelValues(this.inputComponents, this.wizard.model);
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
const variableDNSPrefixMapping: { [s: string]: string } = {};
variableDNSPrefixMapping[VariableNames.AppServiceProxyDNSName_VariableName] = 'bdc-appproxy';
variableDNSPrefixMapping[VariableNames.ControllerDNSName_VariableName] = 'bdc-control';
variableDNSPrefixMapping[VariableNames.GatewayDNSName_VariableName] = 'bdc-gateway';
variableDNSPrefixMapping[VariableNames.ReadableSecondaryDNSName_VariableName] = 'bdc-sqlread';
variableDNSPrefixMapping[VariableNames.SQLServerDNSName_VariableName] = 'bdc-sql';
variableDNSPrefixMapping[VariableNames.ServiceProxyDNSName_VariableName] = 'bdc-proxy';
Object.keys(variableDNSPrefixMapping).forEach((variableName: string) => {
if (!this.wizard.model.getStringValue(variableName)) {
this.wizard.model.setPropertyValue(variableName, `${variableDNSPrefixMapping[variableName]}.${this.wizard.model.getStringValue(VariableNames.DomainDNSName_VariableName)}`);
}
});
}
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true; return true;
}); });
} }
public onEnter() { public onEnter() {
getInputBoxComponent(VariableNames.DockerRegistry_VariableName, this.inputComponents).value = this.wizard.model.getStringValue(VariableNames.DockerRegistry_VariableName);
getInputBoxComponent(VariableNames.DockerRepository_VariableName, this.inputComponents).value = this.wizard.model.getStringValue(VariableNames.DockerRepository_VariableName);
getInputBoxComponent(VariableNames.DockerImageTag_VariableName, this.inputComponents).value = this.wizard.model.getStringValue(VariableNames.DockerImageTag_VariableName);
const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName]; const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName];
if (authModeDropdown) { if (authModeDropdown) {
authModeDropdown.enabled = this.wizard.model.adAuthSupported; authModeDropdown.enabled = this.wizard.model.adAuthSupported;
} const adAuthSelected = (<azdata.CategoryValue>authModeDropdown.value).name === 'ad';
if (!this.wizard.model.adAuthSupported && adAuthSelected) {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { this.formBuilder.removeFormItem(this.activeDirectorySection);
this.wizard.wizardObject.message = { text: '' }; authModeDropdown.value = {
if (pcInfo.newPage > pcInfo.lastPage) { name: AuthenticationMode.Basic,
const messages: string[] = []; displayName: localize('deployCluster.AuthenticationMode.Basic', "Basic")
const authMode = typeof authModeDropdown.value === 'string' ? authModeDropdown.value : authModeDropdown.value!.name; };
const requiredFieldsFilled: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ClusterName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminUserName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))
&& (!(authMode === AuthenticationMode.ActiveDirectory) || (
!isInputBoxEmpty(getInputBoxComponent(VariableNames.DistinguishedName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPrincipals_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.UserPrincipals_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.UpstreamIPAddresses_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DnsName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.Realm_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppOwnerPrincipals_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppReaderPrincipals_VariableName, this.inputComponents))));
if (!requiredFieldsFilled) {
messages.push(MissingRequiredInformationErrorMessage);
}
if (!isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))) {
const password = getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents).value!;
const confirmPassword = getInputBoxComponent(ConfirmPasswordName, this.inputComponents).value!;
if (password !== confirmPassword) {
messages.push(getPasswordMismatchMessage(localize('deployCluster.AdminPasswordField', "Password")));
}
if (!isValidSQLPassword(password)) {
messages.push(getInvalidSQLPasswordMessage(localize('deployCluster.AdminPasswordField', "Password")));
}
}
if (messages.length > 0) {
this.wizard.wizardObject.message = {
text: messages.length === 1 ? messages[0] : localize('deployCluster.ValidationError', "There are some errors on this page, click 'Show Details' to view the errors."),
description: messages.length === 1 ? undefined : messages.join(EOL),
level: azdata.window.MessageLevel.Error
};
}
return messages.length === 0;
} }
return true;
}); this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
this.wizard.wizardObject.message = { text: '' };
if (pcInfo.newPage > pcInfo.lastPage) {
const messages: string[] = [];
const authMode = typeof authModeDropdown.value === 'string' ? authModeDropdown.value : authModeDropdown.value!.name;
const requiredFieldsFilled: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ClusterName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminUserName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))
&& (!(authMode === AuthenticationMode.ActiveDirectory) || (
!isInputBoxEmpty(getInputBoxComponent(VariableNames.OrganizationalUnitDistinguishedName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DomainControllerFQDNs_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ClusterAdmins_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ClusterUsers_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DomainDNSIPAddresses_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DomainDNSName_VariableName, this.inputComponents))));
if (!requiredFieldsFilled) {
messages.push(MissingRequiredInformationErrorMessage);
}
if (!isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminUserName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(ConfirmPasswordName, this.inputComponents))) {
const password = getInputBoxComponent(VariableNames.AdminPassword_VariableName, this.inputComponents).value!;
const confirmPassword = getInputBoxComponent(ConfirmPasswordName, this.inputComponents).value!;
if (password !== confirmPassword) {
messages.push(getPasswordMismatchMessage(localize('deployCluster.AdminPasswordField', "Password")));
}
if (!isValidSQLPassword(password, getInputBoxComponent(VariableNames.AdminUserName_VariableName, this.inputComponents).value!)) {
messages.push(getInvalidSQLPasswordMessage(localize('deployCluster.AdminPasswordField', "Password")));
}
}
if (messages.length > 0) {
this.wizard.wizardObject.message = {
text: messages.length === 1 ? messages[0] : localize('deployCluster.ValidationError', "There are some errors on this page, click 'Show Details' to view the errors."),
description: messages.length === 1 ? undefined : messages.join(EOL),
level: azdata.window.MessageLevel.Error
};
}
return messages.length === 0;
}
return true;
});
}
} }
} }

View File

@@ -81,31 +81,36 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
}, { }, {
label: '' // line separator label: '' // line separator
}, { }, {
label: localize('deployCluster.defaultDataStorage', "Data storage size (GB)"), label: localize('deployCluster.storageSize', "Storage size"),
value: localize('deployCluster.gbPerInstance', "GB per Instance"),
fontWeight: 'bold'
}, {
label: localize('deployCluster.defaultDataStorage', "Data storage"),
value: profile.controllerDataStorageSize.toString() value: profile.controllerDataStorageSize.toString()
}, { }, {
label: localize('deployCluster.defaultLogStorage', "Log storage size (GB)"), label: localize('deployCluster.defaultLogStorage', "Log storage"),
value: profile.controllerLogsStorageSize.toString() value: profile.controllerLogsStorageSize.toString()
}, { }, {
label: '' // line separator label: '' // line separator
} }, {
]; label: localize('deployCluster.features', "Features"),
value: '',
fontWeight: 'bold'
}, {
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
value: ''
}];
if (profile.activeDirectorySupported) { if (profile.activeDirectorySupported) {
descriptions.push({ descriptions.push({
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"), label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"),
value: '' value: ''
});
} else {
descriptions.push({
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
value: '✅'
}); });
} }
if (profile.hadrEnabled) { if (profile.sqlServerReplicas > 1) {
descriptions.push({ descriptions.push({
label: localize('deployCluster.hadr', "High Availability"), label: localize('deployCluster.hadr', "High Availability"),
value: '' value: ''
}); });
} }
@@ -114,7 +119,7 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
label: profile.profileName, label: profile.profileName,
descriptions: descriptions, descriptions: descriptions,
width: '240px', width: '240px',
height: '300px', height: '320px',
}).component(); }).component();
this._cards.push(card); this._cards.push(card);
this.wizard.registerDisposable(card.onCardSelectedChanged(() => { this.wizard.registerDisposable(card.onCardSelectedChanged(() => {
@@ -150,20 +155,24 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
this.wizard.model.setPropertyValue(VariableNames.ZooKeeperScale_VariableName, selectedProfile.zooKeeperReplicas); this.wizard.model.setPropertyValue(VariableNames.ZooKeeperScale_VariableName, selectedProfile.zooKeeperReplicas);
this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageSize_VariableName, selectedProfile.controllerDataStorageSize); this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageSize_VariableName, selectedProfile.controllerDataStorageSize);
this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageSize_VariableName, selectedProfile.controllerLogsStorageSize); this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageSize_VariableName, selectedProfile.controllerLogsStorageSize);
this.wizard.model.setPropertyValue(VariableNames.EnableHADR_VariableName, selectedProfile.hadrEnabled);
this.wizard.model.setPropertyValue(VariableNames.SQLServerPort_VariableName, selectedProfile.sqlServerPort); this.wizard.model.setPropertyValue(VariableNames.SQLServerPort_VariableName, selectedProfile.sqlServerPort);
this.wizard.model.setPropertyValue(VariableNames.GateWayPort_VariableName, selectedProfile.gatewayPort); this.wizard.model.setPropertyValue(VariableNames.GateWayPort_VariableName, selectedProfile.gatewayPort);
this.wizard.model.setPropertyValue(VariableNames.ControllerPort_VariableName, selectedProfile.controllerPort); this.wizard.model.setPropertyValue(VariableNames.ControllerPort_VariableName, selectedProfile.controllerPort);
this.wizard.model.setPropertyValue(VariableNames.ServiceProxyPort_VariableName, selectedProfile.serviceProxyPort);
this.wizard.model.setPropertyValue(VariableNames.AppServiceProxyPort_VariableName, selectedProfile.appServiceProxyPort);
this.wizard.model.setPropertyValue(VariableNames.IncludeSpark_VariableName, selectedProfile.includeSpark); this.wizard.model.setPropertyValue(VariableNames.IncludeSpark_VariableName, selectedProfile.includeSpark);
this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageClassName_VariableName, selectedProfile.controllerDataStorageClass); this.wizard.model.setPropertyValue(VariableNames.ControllerDataStorageClassName_VariableName, selectedProfile.controllerDataStorageClass);
this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageClassName_VariableName, selectedProfile.controllerLogsStorageClass); this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageClassName_VariableName, selectedProfile.controllerLogsStorageClass);
this.wizard.model.setPropertyValue(VariableNames.ReadableSecondaryPort_VariableName, selectedProfile.sqlServerReadableSecondaryPort); this.wizard.model.setPropertyValue(VariableNames.ReadableSecondaryPort_VariableName, selectedProfile.sqlServerReadableSecondaryPort);
this.wizard.model.setPropertyValue(VariableNames.DockerRegistry_VariableName, selectedProfile.registry);
this.wizard.model.setPropertyValue(VariableNames.DockerRepository_VariableName, selectedProfile.repository);
this.wizard.model.setPropertyValue(VariableNames.DockerImageTag_VariableName, selectedProfile.imageTag);
this.wizard.model.adAuthSupported = selectedProfile.activeDirectorySupported; this.wizard.model.adAuthSupported = selectedProfile.activeDirectorySupported;
this.wizard.model.selectedProfile = selectedProfile; this.wizard.model.selectedProfile = selectedProfile;
} }
private loadCards(): Promise<void> { private loadCards(): Promise<void> {
return this.wizard.azdataService.getDeploymentProfiles().then((profiles: BigDataClusterDeploymentProfile[]) => { return this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType).then((profiles: BigDataClusterDeploymentProfile[]) => {
const defaultProfile: string = this.getDefaultProfile(); const defaultProfile: string = this.getDefaultProfile();
profiles.forEach(profile => { profiles.forEach(profile => {

View File

@@ -13,9 +13,9 @@ import * as VariableNames from '../constants';
import { AuthenticationMode } from '../deployClusterWizardModel'; import { AuthenticationMode } from '../deployClusterWizardModel';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const PortInputWidth = '100px'; const NumberInputWidth = '100px';
const inputWidth = '180px'; const inputWidth = '180px';
const labelWidth = '150px'; const labelWidth = '200px';
const spaceBetweenFields = '5px'; const spaceBetweenFields = '5px';
export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> { export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
@@ -32,6 +32,12 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
private gatewayDNSInput!: azdata.InputBoxComponent; private gatewayDNSInput!: azdata.InputBoxComponent;
private gatewayPortInput!: azdata.InputBoxComponent; private gatewayPortInput!: azdata.InputBoxComponent;
private gatewayEndpointRow!: azdata.FlexContainer; private gatewayEndpointRow!: azdata.FlexContainer;
private serviceProxyDNSInput!: azdata.InputBoxComponent;
private serviceProxyPortInput!: azdata.InputBoxComponent;
private serviceProxyEndpointRow!: azdata.FlexContainer;
private appServiceProxyDNSInput!: azdata.InputBoxComponent;
private appServiceProxyPortInput!: azdata.InputBoxComponent;
private appServiceProxyEndpointRow!: azdata.FlexContainer;
private readableSecondaryDNSInput!: azdata.InputBoxComponent; private readableSecondaryDNSInput!: azdata.InputBoxComponent;
private readableSecondaryPortInput!: azdata.InputBoxComponent; private readableSecondaryPortInput!: azdata.InputBoxComponent;
private readableSecondaryEndpointRow!: azdata.FlexContainer; private readableSecondaryEndpointRow!: azdata.FlexContainer;
@@ -39,7 +45,10 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
private controllerNameLabel!: azdata.TextComponent; private controllerNameLabel!: azdata.TextComponent;
private SqlServerNameLabel!: azdata.TextComponent; private SqlServerNameLabel!: azdata.TextComponent;
private gatewayNameLabel!: azdata.TextComponent; private gatewayNameLabel!: azdata.TextComponent;
private serviceProxyNameLabel!: azdata.TextComponent;
private appServiceProxyNameLabel!: azdata.TextComponent;
private readableSecondaryNameLabel!: azdata.TextComponent; private readableSecondaryNameLabel!: azdata.TextComponent;
private endpointSection!: azdata.GroupContainer;
constructor(wizard: DeployClusterWizard) { constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.ServiceSettingsPageTitle', "Service settings"), '', wizard); super(localize('deployCluster.ServiceSettingsPageTitle', "Service settings"), '', wizard);
@@ -48,37 +57,51 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
const scaleSectionInfo: SectionInfo = { const scaleSectionInfo: SectionInfo = {
title: localize('deployCluster.scaleSectionTitle', "Scale settings"), title: localize('deployCluster.scaleSectionTitle', "Scale settings"),
labelWidth: labelWidth, labelWidth: labelWidth,
inputWidth: inputWidth, inputWidth: NumberInputWidth,
spaceBetweenFields: spaceBetweenFields, spaceBetweenFields: '40px',
rows: [{ rows: [{
fields: [ fields: [{
{ type: FieldType.Options,
type: FieldType.Number, label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
label: localize('deployCluster.ComputeText', "Compute"), options: ['1', '3', '4', '5', '6', '7', '8', '9'],
min: 1, defaultValue: '1',
max: 100, required: true,
defaultValue: '1', variableName: VariableNames.SQLServerScale_VariableName,
useCustomValidator: true, }, {
required: true, type: FieldType.Number,
variableName: VariableNames.ComputePoolScale_VariableName, label: localize('deployCluster.ComputePoolInstances', "Compute pool instances"),
} min: 1,
] max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.ComputePoolScale_VariableName,
}]
}, { }, {
fields: [{ fields: [{
type: FieldType.Number, type: FieldType.Number,
label: localize('deployCluster.DataText', "Data"), label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
min: 1, min: 1,
max: 100, max: 100,
defaultValue: '1', defaultValue: '1',
useCustomValidator: true, useCustomValidator: true,
required: true, required: true,
variableName: VariableNames.DataPoolScale_VariableName, variableName: VariableNames.DataPoolScale_VariableName,
}, {
type: FieldType.Number,
label: localize('deployCluster.SparkPoolInstances', "Spark pool instances"),
min: 0,
max: 100,
defaultValue: '0',
useCustomValidator: true,
required: true,
variableName: VariableNames.SparkPoolScale_VariableName
}] }]
}, { }, {
fields: [ fields: [
{ {
type: FieldType.Number, type: FieldType.Number,
label: localize('deployCluster.HDFSText', "HDFS"), label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
min: 1, min: 1,
max: 100, max: 100,
defaultValue: '1', defaultValue: '1',
@@ -87,90 +110,12 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.HDFSPoolScale_VariableName variableName: VariableNames.HDFSPoolScale_VariableName
}, { }, {
type: FieldType.Checkbox, type: FieldType.Checkbox,
label: localize('deployCluster.includeSparkInHDFSPool', "Include Spark"), label: localize('deployCluster.IncludeSparkInStoragePool', "Include Spark in storage pool"),
defaultValue: 'true', defaultValue: 'true',
variableName: VariableNames.IncludeSpark_VariableName, variableName: VariableNames.IncludeSpark_VariableName,
required: false required: false
} }
] ]
}, {
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.SparkText', "Spark"),
min: 0,
max: 100,
defaultValue: '0',
useCustomValidator: true,
required: true,
variableName: VariableNames.SparkPoolScale_VariableName
}
]
}
]
};
const hadrSectionInfo: SectionInfo = {
title: localize('deployCluster.HadrSection', "High availability settings"),
labelWidth: labelWidth,
inputWidth: inputWidth,
spaceBetweenFields: spaceBetweenFields,
rows: [{
fields: [
{
type: FieldType.Options,
label: localize('deployCluster.MasterSqlText', "SQL Server Master"),
options: ['1', '3', '4', '5', '6', '7', '8', '9'],
defaultValue: '1',
required: true,
variableName: VariableNames.SQLServerScale_VariableName,
}, {
type: FieldType.Checkbox,
label: localize('deployCluster.EnableHADR', "Enable Availability Groups"),
defaultValue: 'false',
variableName: VariableNames.EnableHADR_VariableName,
required: false
}
]
}, {
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.HDFSNameNodeText', "HDFS name node"),
min: 1,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.HDFSNameNodeScale_VariableName
}
]
}, {
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.SparkHeadText', "SparkHead"),
min: 0,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.SparkHeadScale_VariableName
}
]
}, {
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.ZooKeeperText', "ZooKeeper"),
min: 0,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.ZooKeeperScale_VariableName
}
]
} }
] ]
}; };
@@ -255,7 +200,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
fields: [ fields: [
{ {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.HDFSText', "HDFS"), label: localize('deployCluster.StoragePool', "Storage pool (HDFS)"),
required: false, required: false,
variableName: VariableNames.HDFSDataStorageClassName_VariableName, variableName: VariableNames.HDFSDataStorageClassName_VariableName,
placeHolder: hintTextForStorageFields, placeHolder: hintTextForStorageFields,
@@ -286,7 +231,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
fields: [ fields: [
{ {
type: FieldType.Text, type: FieldType.Text,
label: localize('deployCluster.DataText', "Data"), label: localize('deployCluster.DataPool', "Data pool"),
required: false, required: false,
variableName: VariableNames.DataPoolDataStorageClassName_VariableName, variableName: VariableNames.DataPoolDataStorageClassName_VariableName,
labelWidth: labelWidth, labelWidth: labelWidth,
@@ -364,25 +309,22 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
}); });
}; };
const scaleSection = createSectionFunc(scaleSectionInfo); const scaleSection = createSectionFunc(scaleSectionInfo);
const hadrSection = createSectionFunc(hadrSectionInfo); this.endpointSection = this.createEndpointSection(view);
const endpointSection = this.createEndpointSection(view);
const storageSection = createSectionFunc(storageSectionInfo); const storageSection = createSectionFunc(storageSectionInfo);
const advancedStorageSection = createSectionFunc(advancedStorageSectionInfo); const advancedStorageSection = createSectionFunc(advancedStorageSectionInfo);
const storageContainer = createGroupContainer(view, [storageSection, advancedStorageSection], { const storageContainer = createGroupContainer(view, [storageSection, advancedStorageSection], {
header: localize('deployCluster.StorageSectionTitle', "Storage settings"), header: localize('deployCluster.StorageSectionTitle', "Storage settings"),
collapsible: true collapsible: true
}); });
this.setSQLServerMasterFieldEventHandler();
this.handleSparkSettingEvents();
const form = view.modelBuilder.formContainer().withFormItems([ const form = view.modelBuilder.formContainer().withFormItems([
{ {
title: '', title: '',
component: scaleSection component: scaleSection
}, { }, {
title: '', title: '',
component: hadrSection component: this.endpointSection
}, {
title: '',
component: endpointSection
}, { }, {
title: '', title: '',
component: storageContainer component: storageContainer
@@ -392,86 +334,120 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
}); });
} }
private handleSparkSettingEvents(): void {
const sparkInstanceInput = getInputBoxComponent(VariableNames.SparkPoolScale_VariableName, this.inputComponents);
const includeSparkCheckbox = getCheckboxComponent(VariableNames.IncludeSpark_VariableName, this.inputComponents);
this.wizard.registerDisposable(includeSparkCheckbox.onChanged(() => {
if (!includeSparkCheckbox.checked && !(sparkInstanceInput.value && Number.parseInt(sparkInstanceInput.value) > 0)) {
sparkInstanceInput.value = '1';
}
}));
}
private createEndpointSection(view: azdata.ModelView): azdata.GroupContainer { private createEndpointSection(view: azdata.ModelView): azdata.GroupContainer {
this.endpointNameColumnHeader = createLabel(view, { text: '', width: labelWidth }); this.endpointNameColumnHeader = createLabel(view, { text: '', width: labelWidth });
this.dnsColumnHeader = createLabel(view, { text: localize('deployCluster.DNSNameHeader', "DNS name"), width: inputWidth }); this.dnsColumnHeader = createLabel(view, { text: localize('deployCluster.DNSNameHeader', "DNS name"), width: inputWidth });
this.portColumnHeader = createLabel(view, { text: localize('deployCluster.PortHeader', "Port"), width: PortInputWidth }); this.portColumnHeader = createLabel(view, { text: localize('deployCluster.PortHeader', "Port"), width: NumberInputWidth });
this.endpointHeaderRow = createFlexContainer(view, [this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]); this.endpointHeaderRow = createFlexContainer(view, [this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]);
this.controllerNameLabel = createLabel(view, { text: localize('deployCluster.ControllerText', "Controller"), width: labelWidth, required: true }); this.controllerNameLabel = createLabel(view, { text: localize('deployCluster.ControllerText', "Controller"), width: labelWidth, required: true });
this.controllerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ControllerDNSName', "Controller DNS name"), required: false, width: inputWidth }); this.controllerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ControllerDNSName', "Controller DNS name"), required: false, width: inputWidth });
this.controllerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ControllerPortName', "Controller port"), required: true, width: PortInputWidth, min: 1 }); this.controllerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ControllerPortName', "Controller port"), required: true, width: NumberInputWidth, min: 1 });
this.controllerEndpointRow = createFlexContainer(view, [this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput]); this.controllerEndpointRow = createFlexContainer(view, [this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput]);
this.inputComponents[VariableNames.ControllerDNSName_VariableName] = this.controllerDNSInput; this.inputComponents[VariableNames.ControllerDNSName_VariableName] = this.controllerDNSInput;
this.inputComponents[VariableNames.ControllerPort_VariableName] = this.controllerPortInput; this.inputComponents[VariableNames.ControllerPort_VariableName] = this.controllerPortInput;
this.SqlServerNameLabel = createLabel(view, { text: localize('deployCluster.MasterSqlText', "SQL Server Master"), width: labelWidth, required: true }); this.SqlServerNameLabel = createLabel(view, { text: localize('deployCluster.MasterSqlText', "SQL Server Master"), width: labelWidth, required: true });
this.sqlServerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerDNSName', "SQL Server Master DNS name"), required: false, width: inputWidth }); this.sqlServerDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerDNSName', "SQL Server Master DNS name"), required: false, width: inputWidth });
this.sqlServerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerPortName', "SQL Server Master port"), required: true, width: PortInputWidth, min: 1 }); this.sqlServerPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.MasterSQLServerPortName', "SQL Server Master port"), required: true, width: NumberInputWidth, min: 1 });
this.sqlServerEndpointRow = createFlexContainer(view, [this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput]); this.sqlServerEndpointRow = createFlexContainer(view, [this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput]);
this.inputComponents[VariableNames.SQLServerDNSName_VariableName] = this.sqlServerDNSInput; this.inputComponents[VariableNames.SQLServerDNSName_VariableName] = this.sqlServerDNSInput;
this.inputComponents[VariableNames.SQLServerPort_VariableName] = this.sqlServerPortInput; this.inputComponents[VariableNames.SQLServerPort_VariableName] = this.sqlServerPortInput;
this.gatewayNameLabel = createLabel(view, { text: localize('deployCluster.GatewayText', "Gateway"), width: labelWidth, required: true }); this.gatewayNameLabel = createLabel(view, { text: localize('deployCluster.GatewayText', "Gateway"), width: labelWidth, required: true });
this.gatewayDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.GatewayDNSName', "Gateway DNS name"), required: false, width: inputWidth }); this.gatewayDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.GatewayDNSName', "Gateway DNS name"), required: false, width: inputWidth });
this.gatewayPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.GatewayPortName', "Gateway port"), required: true, width: PortInputWidth, min: 1 }); this.gatewayPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.GatewayPortName', "Gateway port"), required: true, width: NumberInputWidth, min: 1 });
this.gatewayEndpointRow = createFlexContainer(view, [this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput]); this.gatewayEndpointRow = createFlexContainer(view, [this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput]);
this.inputComponents[VariableNames.GatewayDNSName_VariableName] = this.gatewayDNSInput; this.inputComponents[VariableNames.GatewayDNSName_VariableName] = this.gatewayDNSInput;
this.inputComponents[VariableNames.GateWayPort_VariableName] = this.gatewayPortInput; this.inputComponents[VariableNames.GateWayPort_VariableName] = this.gatewayPortInput;
this.serviceProxyNameLabel = createLabel(view, { text: localize('deployCluster.ServiceProxyText', "Management proxy"), width: labelWidth, required: true });
this.serviceProxyDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ServiceProxyDNSName', "Management proxy DNS name"), required: false, width: inputWidth });
this.serviceProxyPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ServiceProxyPortName', "Management proxy port"), required: true, width: NumberInputWidth, min: 1 });
this.serviceProxyEndpointRow = createFlexContainer(view, [this.serviceProxyNameLabel, this.serviceProxyDNSInput, this.serviceProxyPortInput]);
this.inputComponents[VariableNames.ServiceProxyDNSName_VariableName] = this.serviceProxyDNSInput;
this.inputComponents[VariableNames.ServiceProxyPort_VariableName] = this.serviceProxyPortInput;
this.appServiceProxyNameLabel = createLabel(view, { text: localize('deployCluster.AppServiceProxyText', "Application proxy"), width: labelWidth, required: true });
this.appServiceProxyDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.AppServiceProxyDNSName', "Application proxy DNS name"), required: false, width: inputWidth });
this.appServiceProxyPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.AppServiceProxyPortName', "Application proxy port"), required: true, width: NumberInputWidth, min: 1 });
this.appServiceProxyEndpointRow = createFlexContainer(view, [this.appServiceProxyNameLabel, this.appServiceProxyDNSInput, this.appServiceProxyPortInput]);
this.inputComponents[VariableNames.AppServiceProxyDNSName_VariableName] = this.appServiceProxyDNSInput;
this.inputComponents[VariableNames.AppServiceProxyPort_VariableName] = this.appServiceProxyPortInput;
this.readableSecondaryNameLabel = createLabel(view, { text: localize('deployCluster.ReadableSecondaryText', "Readable secondary"), width: labelWidth, required: true }); this.readableSecondaryNameLabel = createLabel(view, { text: localize('deployCluster.ReadableSecondaryText', "Readable secondary"), width: labelWidth, required: true });
this.readableSecondaryDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryDNSName', "Readable secondary DNS name"), required: false, width: inputWidth }); this.readableSecondaryDNSInput = createTextInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryDNSName', "Readable secondary DNS name"), required: false, width: inputWidth });
this.readableSecondaryPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryPortName', "Readable secondary port"), required: false, width: PortInputWidth, min: 1 }); this.readableSecondaryPortInput = createNumberInput(view, { ariaLabel: localize('deployCluster.ReadableSecondaryPortName', "Readable secondary port"), required: false, width: NumberInputWidth, min: 1 });
this.readableSecondaryEndpointRow = createFlexContainer(view, [this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput]); this.readableSecondaryEndpointRow = createFlexContainer(view, [this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput]);
this.inputComponents[VariableNames.ReadableSecondaryDNSName_VariableName] = this.readableSecondaryDNSInput; this.inputComponents[VariableNames.ReadableSecondaryDNSName_VariableName] = this.readableSecondaryDNSInput;
this.inputComponents[VariableNames.ReadableSecondaryPort_VariableName] = this.readableSecondaryPortInput; this.inputComponents[VariableNames.ReadableSecondaryPort_VariableName] = this.readableSecondaryPortInput;
return createGroupContainer(view, [this.endpointHeaderRow, this.controllerEndpointRow, this.sqlServerEndpointRow, this.gatewayEndpointRow, this.readableSecondaryEndpointRow], { return createGroupContainer(view, [this.endpointHeaderRow, this.controllerEndpointRow, this.sqlServerEndpointRow, this.gatewayEndpointRow, this.serviceProxyEndpointRow, this.appServiceProxyEndpointRow, this.readableSecondaryEndpointRow], {
header: localize('deployCluster.EndpointSettings', "Endpoint settings"), header: localize('deployCluster.EndpointSettings', "Endpoint settings"),
collapsible: true collapsible: true
}); });
} }
public onEnter(): void { public onEnter(): void {
this.setDropdownValue(VariableNames.SQLServerScale_VariableName);
this.setCheckboxValue(VariableNames.EnableHADR_VariableName);
this.setInputBoxValue(VariableNames.ComputePoolScale_VariableName); this.setInputBoxValue(VariableNames.ComputePoolScale_VariableName);
this.setInputBoxValue(VariableNames.DataPoolScale_VariableName); this.setInputBoxValue(VariableNames.DataPoolScale_VariableName);
this.setInputBoxValue(VariableNames.HDFSPoolScale_VariableName); this.setInputBoxValue(VariableNames.HDFSPoolScale_VariableName);
this.setInputBoxValue(VariableNames.HDFSNameNodeScale_VariableName);
this.setInputBoxValue(VariableNames.SparkPoolScale_VariableName); this.setInputBoxValue(VariableNames.SparkPoolScale_VariableName);
this.setInputBoxValue(VariableNames.SparkHeadScale_VariableName);
this.setInputBoxValue(VariableNames.ZooKeeperScale_VariableName);
this.setCheckboxValue(VariableNames.IncludeSpark_VariableName); this.setCheckboxValue(VariableNames.IncludeSpark_VariableName);
this.setEnableHadrCheckboxState(this.wizard.model.getIntegerValue(VariableNames.SQLServerScale_VariableName));
this.setInputBoxValue(VariableNames.ControllerPort_VariableName); this.setInputBoxValue(VariableNames.ControllerPort_VariableName);
this.setInputBoxValue(VariableNames.SQLServerPort_VariableName); this.setInputBoxValue(VariableNames.SQLServerPort_VariableName);
this.setInputBoxValue(VariableNames.GateWayPort_VariableName); this.setInputBoxValue(VariableNames.GateWayPort_VariableName);
this.setInputBoxValue(VariableNames.ServiceProxyPort_VariableName);
this.setInputBoxValue(VariableNames.AppServiceProxyPort_VariableName);
this.setInputBoxValue(VariableNames.ReadableSecondaryPort_VariableName); this.setInputBoxValue(VariableNames.ReadableSecondaryPort_VariableName);
this.setInputBoxValue(VariableNames.GatewayDNSName_VariableName);
this.setInputBoxValue(VariableNames.AppServiceProxyDNSName_VariableName);
this.setInputBoxValue(VariableNames.SQLServerDNSName_VariableName);
this.setInputBoxValue(VariableNames.ReadableSecondaryDNSName_VariableName);
this.setInputBoxValue(VariableNames.ServiceProxyDNSName_VariableName);
this.setInputBoxValue(VariableNames.ControllerDNSName_VariableName);
this.setInputBoxValue(VariableNames.ControllerDataStorageClassName_VariableName); this.setInputBoxValue(VariableNames.ControllerDataStorageClassName_VariableName);
this.setInputBoxValue(VariableNames.ControllerDataStorageSize_VariableName); this.setInputBoxValue(VariableNames.ControllerDataStorageSize_VariableName);
this.setInputBoxValue(VariableNames.ControllerLogsStorageClassName_VariableName); this.setInputBoxValue(VariableNames.ControllerLogsStorageClassName_VariableName);
this.setInputBoxValue(VariableNames.ControllerLogsStorageSize_VariableName); this.setInputBoxValue(VariableNames.ControllerLogsStorageSize_VariableName);
this.endpointHeaderRow.clearItems(); this.endpointHeaderRow.clearItems();
this.endpointSection.collapsed = this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory;
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) { if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
this.endpointHeaderRow.addItems([this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]); this.endpointHeaderRow.addItems([this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]);
} }
this.loadEndpointRow(this.controllerEndpointRow, this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput); this.loadEndpointRow(this.controllerEndpointRow, this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput);
this.loadEndpointRow(this.gatewayEndpointRow, this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput); this.loadEndpointRow(this.gatewayEndpointRow, this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput);
this.loadEndpointRow(this.sqlServerEndpointRow, this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput); this.loadEndpointRow(this.sqlServerEndpointRow, this.SqlServerNameLabel, this.sqlServerDNSInput, this.sqlServerPortInput);
this.updateReadableSecondaryEndpointComponents(this.wizard.model.hadrEnabled); this.loadEndpointRow(this.appServiceProxyEndpointRow, this.appServiceProxyNameLabel, this.appServiceProxyDNSInput, this.appServiceProxyPortInput);
this.loadEndpointRow(this.serviceProxyEndpointRow, this.serviceProxyNameLabel, this.serviceProxyDNSInput, this.serviceProxyPortInput);
const sqlServerScaleDropdown = getDropdownComponent(VariableNames.SQLServerScale_VariableName, this.inputComponents);
const sqlServerScale = this.wizard.model.getIntegerValue(VariableNames.SQLServerScale_VariableName);
if (sqlServerScale > 1) {
sqlServerScaleDropdown.values = ['3', '4', '5', '6', '7', '8', '9'];
this.loadEndpointRow(this.readableSecondaryEndpointRow, this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput);
} else {
this.readableSecondaryEndpointRow.clearItems();
sqlServerScaleDropdown.values = ['1'];
}
sqlServerScaleDropdown.value = sqlServerScale.toString();
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => { this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
this.wizard.wizardObject.message = { text: '' }; this.wizard.wizardObject.message = { text: '' };
if (pcInfo.newPage > pcInfo.lastPage) { if (pcInfo.newPage > pcInfo.lastPage) {
const isValid: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ComputePoolScale_VariableName, this.inputComponents)) const allInputFilled: boolean = !isInputBoxEmpty(getInputBoxComponent(VariableNames.ComputePoolScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.DataPoolScale_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.DataPoolScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSNameNodeScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSPoolScale_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSPoolScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SparkPoolScale_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.SparkPoolScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SparkHeadScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ZooKeeperScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageClassName_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageClassName_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageSize_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerDataStorageSize_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerLogsStorageClassName_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerLogsStorageClassName_VariableName, this.inputComponents))
@@ -479,21 +455,34 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerPort_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.ControllerPort_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.SQLServerPort_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.SQLServerPort_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.GateWayPort_VariableName, this.inputComponents)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.GateWayPort_VariableName, this.inputComponents))
&& (!getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents).checked && !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppServiceProxyPort_VariableName, this.inputComponents))
|| !isInputBoxEmpty(this.readableSecondaryPortInput)) && !isInputBoxEmpty(getInputBoxComponent(VariableNames.ServiceProxyPort_VariableName, this.inputComponents))
&& (getDropdownComponent(VariableNames.SQLServerScale_VariableName, this.inputComponents).value === '1'
|| (!isInputBoxEmpty(this.readableSecondaryPortInput)
&& (this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory || !isInputBoxEmpty(this.readableSecondaryDNSInput))))
&& (this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory && (this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory
|| (!isInputBoxEmpty(this.gatewayDNSInput) || (!isInputBoxEmpty(this.gatewayDNSInput)
&& !isInputBoxEmpty(this.controllerDNSInput) && !isInputBoxEmpty(this.controllerDNSInput)
&& !isInputBoxEmpty(this.sqlServerDNSInput) && !isInputBoxEmpty(this.sqlServerDNSInput)
&& !isInputBoxEmpty(this.readableSecondaryDNSInput) && !isInputBoxEmpty(this.appServiceProxyDNSInput)
&& !isInputBoxEmpty(this.serviceProxyDNSInput)
)); ));
if (!isValid) { const sparkEnabled = Number.parseInt(getInputBoxComponent(VariableNames.SparkPoolScale_VariableName, this.inputComponents).value!) !== 0
|| getCheckboxComponent(VariableNames.IncludeSpark_VariableName, this.inputComponents).checked!;
let errorMessage: string | undefined;
if (!allInputFilled) {
errorMessage = MissingRequiredInformationErrorMessage;
} else if (!sparkEnabled) {
errorMessage = localize('deployCluster.SparkMustBeIncluded', "Invalid Spark configuration, you must check the 'Include Spark' checkbox or set the 'Spark pool instances' to at least 1.");
}
if (errorMessage) {
this.wizard.wizardObject.message = { this.wizard.wizardObject.message = {
text: MissingRequiredInformationErrorMessage, text: errorMessage,
level: azdata.window.MessageLevel.Error level: azdata.window.MessageLevel.Error
}; };
} }
return isValid; return allInputFilled && sparkEnabled;
} }
return true; return true;
}); });
@@ -514,43 +503,6 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
getCheckboxComponent(variableName, this.inputComponents).checked = this.wizard.model.getBooleanValue(variableName); getCheckboxComponent(variableName, this.inputComponents).checked = this.wizard.model.getBooleanValue(variableName);
} }
private setDropdownValue(variableName: string): void {
getDropdownComponent(variableName, this.inputComponents).value = this.wizard.model.getStringValue(variableName);
}
private setSQLServerMasterFieldEventHandler() {
const sqlScaleDropdown = getDropdownComponent(VariableNames.SQLServerScale_VariableName, this.inputComponents);
const enableHadrCheckbox = getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents);
this.wizard.registerDisposable(sqlScaleDropdown.onValueChanged(() => {
const selectedValue = typeof sqlScaleDropdown.value === 'string' ? sqlScaleDropdown.value : sqlScaleDropdown.value!.name;
this.setEnableHadrCheckboxState(Number.parseInt(selectedValue));
}));
this.wizard.registerDisposable(enableHadrCheckbox.onChanged(() => {
this.updateReadableSecondaryEndpointComponents(!!enableHadrCheckbox.checked);
}));
}
private setEnableHadrCheckboxState(sqlInstances: number) {
// 1. it is ok to enable HADR when there is only 1 replica
// 2. if there are multiple replicas, the hadr.enabled switch must be set to true.
const enableHadrCheckbox = getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents);
const hadrEnabled = sqlInstances === 1 ? !!enableHadrCheckbox.checked : true;
if (sqlInstances === 1) {
enableHadrCheckbox.enabled = true;
} else {
enableHadrCheckbox.enabled = false;
}
enableHadrCheckbox.checked = hadrEnabled;
this.updateReadableSecondaryEndpointComponents(hadrEnabled);
}
private updateReadableSecondaryEndpointComponents(hadrEnabled: boolean) {
this.readableSecondaryEndpointRow.clearItems();
if (hadrEnabled) {
this.loadEndpointRow(this.readableSecondaryEndpointRow, this.readableSecondaryNameLabel, this.readableSecondaryDNSInput, this.readableSecondaryPortInput);
}
}
private loadEndpointRow(row: azdata.FlexContainer, label: azdata.TextComponent, dnsInput: azdata.InputBoxComponent, portInput: azdata.InputBoxComponent): void { private loadEndpointRow(row: azdata.FlexContainer, label: azdata.TextComponent, dnsInput: azdata.InputBoxComponent, portInput: azdata.InputBoxComponent): void {
row.clearItems(); row.clearItems();
const itemLayout: azdata.FlexItemLayout = { CSSStyles: { 'margin-right': '20px' } }; const itemLayout: azdata.FlexItemLayout = { CSSStyles: { 'margin-right': '20px' } };

View File

@@ -4,24 +4,18 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { DeployClusterWizard } from '../deployClusterWizard'; import { DeployClusterWizard } from '../deployClusterWizard';
import { SectionInfo, FieldType, LabelPosition, FontStyle, BdcDeploymentType } from '../../../interfaces'; import { SectionInfo, FieldType, LabelPosition, BdcDeploymentType, FontWeight } from '../../../interfaces';
import { createSection, createGroupContainer, createFlexContainer, createLabel } from '../../modelViewUtils'; import { createSection, createGroupContainer, createFlexContainer, createLabel } from '../../modelViewUtils';
import { WizardPageBase } from '../../wizardPageBase'; import { WizardPageBase } from '../../wizardPageBase';
import * as VariableNames from '../constants'; import * as VariableNames from '../constants';
import * as os from 'os';
import { join } from 'path';
import * as fs from 'fs';
import { AuthenticationMode } from '../deployClusterWizardModel'; import { AuthenticationMode } from '../deployClusterWizardModel';
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class SummaryPage extends WizardPageBase<DeployClusterWizard> { export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
private formItems: azdata.FormComponent[] = []; private formItems: azdata.FormComponent[] = [];
private form!: azdata.FormBuilder; private form!: azdata.FormBuilder;
private view!: azdata.ModelView; private view!: azdata.ModelView;
private targetDeploymentProfile!: BigDataClusterDeploymentProfile;
constructor(wizard: DeployClusterWizard) { constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.summaryPageTitle', "Summary"), '', wizard); super(localize('deployCluster.summaryPageTitle', "Summary"), '', wizard);
@@ -30,30 +24,20 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
public initialize(): void { public initialize(): void {
this.pageObject.registerContent((view: azdata.ModelView) => { this.pageObject.registerContent((view: azdata.ModelView) => {
this.view = view; this.view = view;
const deploymentJsonSection = createGroupContainer(view, [ this.form = view.modelBuilder.formContainer();
view.modelBuilder.flexContainer().withItems([
this.createSaveJsonButton(localize('deployCluster.SaveBdcJson', "Save bdc.json"), 'bdc.json', () => { return this.targetDeploymentProfile.getBdcJson(); }),
this.createSaveJsonButton(localize('deployCluster.SaveControlJson', "Save control.json"), 'control.json', () => { return this.targetDeploymentProfile.getControlJson(); })
], {
CSSStyles: { 'margin-right': '10px' }
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component()
], {
header: localize('deployCluster.DeploymentJSON', "Deployment JSON files"),
collapsible: true
});
this.form = view.modelBuilder.formContainer().withFormItems([
{
title: '',
component: deploymentJsonSection
}
]);
return view.initializeModel(this.form!.withLayout({ width: '100%' }).component()); return view.initializeModel(this.form!.withLayout({ width: '100%' }).component());
}); });
} }
public onEnter() { public onEnter() {
this.targetDeploymentProfile = this.wizard.model.createTargetProfile(); if (this.wizard.model.deploymentTarget === BdcDeploymentType.NewAKS) {
this.wizard.wizardObject.message = {
level: azdata.window.MessageLevel.Information,
text: localize('resourceDeployment.NewAKSBrowserWindowPrompt', "A browser window for logging to Azure will be opened during the SQL Server Big Data Cluster deployment.")
};
}
this.wizard.saveConfigButton.hidden = false;
this.wizard.scriptToNotebookButton.hidden = false;
this.formItems.forEach(item => { this.formItems.forEach(item => {
this.form!.removeFormItem(item); this.form!.removeFormItem(item);
}); });
@@ -71,13 +55,13 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.Kubeconfig', "Kube config"), label: localize('deployCluster.Kubeconfig', "Kube config"),
defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, },
{ {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterContext', "Cluster context"), label: localize('deployCluster.ClusterContext', "Cluster context"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterContext_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterContext_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}] }]
} }
] ]
@@ -96,13 +80,13 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.DeploymentProfile', "Deployment profile"), label: localize('deployCluster.DeploymentProfile', "Deployment profile"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, },
{ {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterName', "Cluster name"), label: localize('deployCluster.ClusterName', "Cluster name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}] }]
}, { }, {
fields: [ fields: [
@@ -110,20 +94,92 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.ControllerUsername', "Controller username"), label: localize('deployCluster.ControllerUsername', "Controller username"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, { }, {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.AuthenticationMode', "Authentication mode"), label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ? defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ?
localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") : localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") :
localize('deployCluster.AuthenticationMode.Basic', "Basic"), localize('deployCluster.AuthenticationMode.Basic', "Basic"),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
} }
] ]
} }
] ]
}; };
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
clusterSectionInfo.rows!.push({
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.OuDistinguishedName', "Organizational unit"),
defaultValue: this.wizard.model.getStringValue(VariableNames.OrganizationalUnitDistinguishedName_VariableName),
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainControllerFQDNs', "Domain controller FQDNs"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainControllerFQDNs_VariableName),
labelFontWeight: FontWeight.Bold
}]
});
clusterSectionInfo.rows!.push({
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainDNSIPAddresses', "Domain DNS IP addresses"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSIPAddresses_VariableName),
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainDNSName', "Domain DNS name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSName_VariableName),
labelFontWeight: FontWeight.Bold
}]
});
clusterSectionInfo.rows!.push({
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterAdmins', "Cluster admin group"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterAdmins_VariableName),
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterUsers', "Cluster users"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterUsers_VariableName),
labelFontWeight: FontWeight.Bold
}]
});
clusterSectionInfo.rows!.push({
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.AppOwers', "App owners"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AppOwners_VariableName),
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.AppReaders', "App readers"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AppReaders_VariableName),
labelFontWeight: FontWeight.Bold
}]
});
clusterSectionInfo.rows!.push({
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainServiceAccountUserName', "Service account username"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainServiceAccountUserName_VariableName),
labelFontWeight: FontWeight.Bold
}]
});
}
const azureSectionInfo: SectionInfo = { const azureSectionInfo: SectionInfo = {
labelPosition: LabelPosition.Left, labelPosition: LabelPosition.Left,
labelWidth: '150px', labelWidth: '150px',
@@ -135,26 +191,26 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.SubscriptionId', "Subscription id"), label: localize('deployCluster.SubscriptionId', "Subscription id"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"), defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, { }, {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.ResourceGroup', "Resource group"), label: localize('deployCluster.ResourceGroup', "Resource group"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
} }
] ]
}, { }, {
fields: [ fields: [
{ {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.Region', "Region"), label: localize('deployCluster.Location', "Location"),
defaultValue: this.wizard.model.getStringValue(VariableNames.Region_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.Location_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, { }, {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.AksClusterName', "AKS cluster name"), label: localize('deployCluster.AksClusterName', "AKS cluster name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AksName_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.AksName_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
} }
] ]
}, { }, {
@@ -163,12 +219,12 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.VMSize', "VM size"), label: localize('deployCluster.VMSize', "VM size"),
defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, { }, {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.VMCount', "VM count"), label: localize('deployCluster.VMCount', "VM count"),
defaultValue: this.wizard.model.getStringValue(VariableNames.VMCount_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.VMCount_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
} }
] ]
} }
@@ -184,68 +240,34 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
{ {
fields: [{ fields: [{
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.ComputeText', "Compute"), label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
}, { }, {
type: FieldType.ReadonlyText, type: FieldType.ReadonlyText,
label: localize('deployCluster.DataText', "Data"), label: localize('deployCluster.ComputePoolInstances', "Compute pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName),
labelFontWeight: FontWeight.Bold
}]
}, {
fields: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DataPoolScale_VariableName), defaultValue: this.wizard.model.getStringValue(VariableNames.DataPoolScale_VariableName),
fontStyle: FontStyle.Italic labelFontWeight: FontWeight.Bold
} }, {
] type: FieldType.ReadonlyText,
label: localize('deployCluster.SparkPoolInstances', "Spark pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkPoolScale_VariableName),
labelFontWeight: FontWeight.Bold
}]
}, { }, {
fields: [ fields: [{
{ type: FieldType.ReadonlyText,
type: FieldType.ReadonlyText, label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
label: localize('deployCluster.HDFSText', "HDFS"), defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`,
defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`, labelFontWeight: FontWeight.Bold
fontStyle: FontStyle.Italic }]
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.SparkText', "Spark"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkPoolScale_VariableName),
fontStyle: FontStyle.Italic
}
]
}
]
};
const hadrSectionInfo: SectionInfo = {
labelPosition: LabelPosition.Left,
labelWidth: '150px',
inputWidth: '200px',
title: localize('deployCluster.HadrSection', "High availability settings"),
rows: [
{
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.SqlServerText', "SQL Server Master"),
defaultValue: `${this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName)} ${this.wizard.model.hadrEnabled ? localize('deployCluster.WithHADR', "(Availability Groups Enabled)") : ''}`,
fontStyle: FontStyle.Italic
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.HDFSNameNodeText', "HDFS name node"),
defaultValue: this.wizard.model.getStringValue(VariableNames.HDFSNameNodeScale_VariableName),
fontStyle: FontStyle.Italic
}
]
}, {
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ZooKeeperText', "ZooKeeper"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ZooKeeperScale_VariableName),
fontStyle: FontStyle.Italic
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.SparkHeadText', "SparkHead"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkHeadScale_VariableName),
fontStyle: FontStyle.Italic
}
]
} }
] ]
}; };
@@ -271,7 +293,6 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
const clusterSection = createSectionFunc(clusterSectionInfo); const clusterSection = createSectionFunc(clusterSectionInfo);
const scaleSection = createSectionFunc(scaleSectionInfo); const scaleSection = createSectionFunc(scaleSectionInfo);
const hadrSection = createSectionFunc(hadrSectionInfo);
const endpointSection = { const endpointSection = {
title: '', title: '',
component: this.createEndpointSection() component: this.createEndpointSection()
@@ -285,10 +306,16 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
this.formItems.push(azureSection); this.formItems.push(azureSection);
} }
this.formItems.push(clusterSection, scaleSection, hadrSection, endpointSection, storageSection); this.formItems.push(clusterSection, scaleSection, endpointSection, storageSection);
this.form.addFormItems(this.formItems); this.form.addFormItems(this.formItems);
} }
public onLeave() {
this.wizard.saveConfigButton.hidden = true;
this.wizard.scriptToNotebookButton.hidden = true;
this.wizard.wizardObject.message = { text: '' };
}
private getStorageSettingValue(propertyName: string, defaultValuePropertyName: string): string | undefined { private getStorageSettingValue(propertyName: string, defaultValuePropertyName: string): string | undefined {
const value = this.wizard.model.getStringValue(propertyName); const value = this.wizard.model.getStringValue(propertyName);
return (value === undefined || value === '') ? this.wizard.model.getStringValue(defaultValuePropertyName) : value; return (value === undefined || value === '') ? this.wizard.model.getStringValue(defaultValuePropertyName) : value;
@@ -324,7 +351,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageClassName_VariableName), this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageClassName_VariableName),
this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageSize_VariableName)], this.wizard.model.getStringValue(VariableNames.ControllerLogsStorageSize_VariableName)],
[ [
localize('deployCluster.HDFSText', "HDFS"), localize('deployCluster.StoragePool', "Storage pool (HDFS)"),
this.getStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName), this.getStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName),
this.getStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName), this.getStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName),
this.getStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName), this.getStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName),
@@ -357,10 +384,12 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
const endpointRows = [ const endpointRows = [
this.createEndpointRow(localize('deployCluster.ControllerText', "Controller"), VariableNames.ControllerDNSName_VariableName, VariableNames.ControllerPort_VariableName), this.createEndpointRow(localize('deployCluster.ControllerText', "Controller"), VariableNames.ControllerDNSName_VariableName, VariableNames.ControllerPort_VariableName),
this.createEndpointRow(localize('deployCluster.SqlServerText', "SQL Server Master"), VariableNames.SQLServerDNSName_VariableName, VariableNames.SQLServerPort_VariableName), this.createEndpointRow(localize('deployCluster.SqlServerText', "SQL Server Master"), VariableNames.SQLServerDNSName_VariableName, VariableNames.SQLServerPort_VariableName),
this.createEndpointRow(localize('deployCluster.GatewayText', "Gateway"), VariableNames.GatewayDNSName_VariableName, VariableNames.GateWayPort_VariableName) this.createEndpointRow(localize('deployCluster.GatewayText', "Gateway"), VariableNames.GatewayDNSName_VariableName, VariableNames.GateWayPort_VariableName),
this.createEndpointRow(localize('deployCluster.AppServiceProxyText', "Application proxy"), VariableNames.AppServiceProxyDNSName_VariableName, VariableNames.AppServiceProxyPort_VariableName),
this.createEndpointRow(localize('deployCluster.ServiceProxyText', "Management proxy"), VariableNames.ServiceProxyDNSName_VariableName, VariableNames.ServiceProxyPort_VariableName)
]; ];
if (this.wizard.model.hadrEnabled) { if (this.wizard.model.getIntegerValue(VariableNames.SQLServerScale_VariableName) > 1) {
endpointRows.push( endpointRows.push(
this.createEndpointRow(localize('deployCluster.ReadableSecondaryText', "Readable secondary"), VariableNames.ReadableSecondaryDNSName_VariableName, VariableNames.ReadableSecondaryPort_VariableName) this.createEndpointRow(localize('deployCluster.ReadableSecondaryText', "Readable secondary"), VariableNames.ReadableSecondaryDNSName_VariableName, VariableNames.ReadableSecondaryPort_VariableName)
); );
@@ -373,43 +402,15 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
private createEndpointRow(name: string, dnsVariableName: string, portVariableName: string): azdata.FlexContainer { private createEndpointRow(name: string, dnsVariableName: string, portVariableName: string): azdata.FlexContainer {
const items = []; const items = [];
items.push(createLabel(this.view, { text: name, width: '150px' })); items.push(createLabel(this.view, { text: name, width: '150px', fontWeight: FontWeight.Bold }));
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) { if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
items.push(createLabel(this.view, { text: this.wizard.model.getStringValue(dnsVariableName)!, width: '200px', fontStyle: FontStyle.Italic })); items.push(createLabel(this.view, {
text: this.wizard.model.getStringValue(dnsVariableName)!, width: '200px'
}));
} }
items.push(createLabel(this.view, { text: this.wizard.model.getStringValue(portVariableName)!, width: '100px', fontStyle: FontStyle.Italic })); items.push(createLabel(this.view, {
text: this.wizard.model.getStringValue(portVariableName)!, width: '100px'
}));
return createFlexContainer(this.view, items); return createFlexContainer(this.view, items);
} }
private createSaveJsonButton(label: string, fileName: string, getContent: () => string): azdata.ButtonComponent {
const button = this.view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
title: label,
label: fileName,
ariaLabel: label,
width: '150px'
}).component();
this.wizard.registerDisposable(button.onDidClick(() => {
vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(join(os.homedir(), fileName)),
filters: {
'JSON': ['json']
}
}).then((path) => {
if (path) {
fs.promises.writeFile(path.fsPath, getContent()).then(() => {
this.wizard.wizardObject.message = {
text: localize('deployCluster.SaveJsonFileMessage', "File saved: {0}", path.fsPath),
level: azdata.window.MessageLevel.Information
};
}).catch((error) => {
this.wizard.wizardObject.message = {
text: error.message,
level: azdata.window.MessageLevel.Error
};
});
}
});
}));
return button;
}
} }

View File

@@ -6,8 +6,9 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { DialogInfoBase, FieldType, FieldInfo, SectionInfo, LabelPosition } from '../interfaces'; import { DialogInfoBase, FieldType, FieldInfo, SectionInfo, LabelPosition, FontWeight, FontStyle } from '../interfaces';
import { Model } from './model'; import { Model } from './model';
import { getDateTimeString } from '../utils';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -40,7 +41,6 @@ export interface WizardPageContext extends CreateContext {
container: azdata.window.Wizard; container: azdata.window.Wizard;
} }
export interface SectionContext extends CreateContext { export interface SectionContext extends CreateContext {
sectionInfo: SectionInfo; sectionInfo: SectionInfo;
view: azdata.ModelView; view: azdata.ModelView;
@@ -70,12 +70,13 @@ export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValu
}).component(); }).component();
} }
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, fontStyle?: string }): azdata.TextComponent { export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, fontStyle?: FontStyle, fontWeight?: FontWeight, links?: azdata.LinkArea[] }): azdata.TextComponent {
const text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ const text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: info.text, value: info.text,
description: info.description, description: info.description,
requiredIndicator: info.required, requiredIndicator: info.required,
CSSStyles: { 'font-style': info.fontStyle || 'normal' } CSSStyles: { 'font-style': info.fontStyle || 'normal', 'font-weight': info.fontWeight || 'normal' },
links: info.links
}).component(); }).component();
text.width = info.width; text.width = info.width;
return text; return text;
@@ -101,11 +102,13 @@ export function createCheckbox(view: azdata.ModelView, info: { initialValue: boo
}).component(); }).component();
} }
export function createDropdown(view: azdata.ModelView, info: { defaultValue?: string | azdata.CategoryValue, values?: string[] | azdata.CategoryValue[], width?: string }): azdata.DropDownComponent { export function createDropdown(view: azdata.ModelView, info: { defaultValue?: string | azdata.CategoryValue, values?: string[] | azdata.CategoryValue[], width?: string, editable?: boolean, required?: boolean }): azdata.DropDownComponent {
return view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ return view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
values: info.values, values: info.values,
value: info.defaultValue, value: info.defaultValue,
width: info.width width: info.width,
editable: info.editable,
fireOnTextChange: true
}).component(); }).component();
} }
@@ -232,7 +235,6 @@ function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata
} }
} }
function processField(context: FieldContext): void { function processField(context: FieldContext): void {
switch (context.fieldInfo.type) { switch (context.fieldInfo.type) {
case FieldType.Options: case FieldType.Options:
@@ -263,19 +265,21 @@ function processField(context: FieldContext): void {
} }
function processOptionsTypeField(context: FieldContext): void { function processOptionsTypeField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth }); const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const dropdown = createDropdown(context.view, { const dropdown = createDropdown(context.view, {
values: context.fieldInfo.options, values: context.fieldInfo.options,
defaultValue: context.fieldInfo.defaultValue, defaultValue: context.fieldInfo.defaultValue,
width: context.fieldInfo.inputWidth width: context.fieldInfo.inputWidth,
editable: context.fieldInfo.editable,
required: context.fieldInfo.required
}); });
context.onNewInputComponentCreated(context.fieldInfo.variableName!, dropdown); context.onNewInputComponentCreated(context.fieldInfo.variableName!, dropdown);
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition); addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition);
} }
function processDateTimeTextField(context: FieldContext): void { function processDateTimeTextField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth }); const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const defaultValue = context.fieldInfo.defaultValue + new Date().toISOString().slice(0, 19).replace(/[^0-9]/g, ''); // Take the date time information and only leaving the numbers const defaultValue = context.fieldInfo.defaultValue + getDateTimeString();
const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: defaultValue, value: defaultValue,
ariaLabel: context.fieldInfo.label, ariaLabel: context.fieldInfo.label,
@@ -289,7 +293,7 @@ function processDateTimeTextField(context: FieldContext): void {
} }
function processNumberField(context: FieldContext): void { function processNumberField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth }); const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const input = createNumberInput(context.view, { const input = createNumberInput(context.view, {
defaultValue: context.fieldInfo.defaultValue, defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label, ariaLabel: context.fieldInfo.label,
@@ -304,7 +308,7 @@ function processNumberField(context: FieldContext): void {
} }
function processTextField(context: FieldContext): void { function processTextField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth }); const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const input = createTextInput(context.view, { const input = createTextInput(context.view, {
defaultValue: context.fieldInfo.defaultValue, defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label, ariaLabel: context.fieldInfo.label,
@@ -317,7 +321,7 @@ function processTextField(context: FieldContext): void {
} }
function processPasswordField(context: FieldContext): void { function processPasswordField(context: FieldContext): void {
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth }); const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const passwordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const passwordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
ariaLabel: context.fieldInfo.label, ariaLabel: context.fieldInfo.label,
inputType: 'password', inputType: 'password',
@@ -343,7 +347,7 @@ function processPasswordField(context: FieldContext): void {
if (context.fieldInfo.confirmationRequired) { if (context.fieldInfo.confirmationRequired) {
const passwordNotMatchMessage = getPasswordMismatchMessage(context.fieldInfo.label); const passwordNotMatchMessage = getPasswordMismatchMessage(context.fieldInfo.label);
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth }); const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const confirmPasswordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const confirmPasswordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
ariaLabel: context.fieldInfo.confirmationLabel, ariaLabel: context.fieldInfo.confirmationLabel,
inputType: 'password', inputType: 'password',
@@ -373,8 +377,8 @@ function processPasswordField(context: FieldContext): void {
} }
function processReadonlyTextField(context: FieldContext): void { function processReadonlyTextField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth }); const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const text = createLabel(context.view, { text: context.fieldInfo.defaultValue!, description: '', required: false, width: context.fieldInfo.inputWidth, fontStyle: context.fieldInfo.fontStyle }); const text = createLabel(context.view, { text: context.fieldInfo.defaultValue!, description: '', required: false, width: context.fieldInfo.inputWidth, fontStyle: context.fieldInfo.fontStyle, links: context.fieldInfo.links });
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo.labelPosition); addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo.labelPosition);
} }

View File

@@ -52,7 +52,9 @@ export class ResourceTypePickerDialog extends DialogBase {
tab.registerContent((view: azdata.ModelView) => { tab.registerContent((view: azdata.ModelView) => {
const tableWidth = 1126; const tableWidth = 1126;
this._view = view; this._view = view;
this.resourceTypeService.getResourceTypes().forEach(resourceType => this.addCard(resourceType)); this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => {
return (a.displayIndex || Number.MAX_VALUE) - (b.displayIndex || Number.MAX_VALUE);
}).forEach(resourceType => this.addCard(resourceType));
const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component(); const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component();
this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component(); this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component();
this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function getErrorMessage(error: string | Error): string {
return typeof error === 'string' ? error : error.message;
}
export function getDateTimeString(): string {
return new Date().toISOString().slice(0, 19).replace(/[^0-9]/g, ''); // Take the date time information and only leaving the numbers
}

15
src/sql/azdata.d.ts vendored
View File

@@ -2895,7 +2895,7 @@ declare module 'azdata' {
export interface FormContainer extends Container<FormLayout, FormItemLayout> { export interface FormContainer extends Container<FormLayout, FormItemLayout> {
} }
export interface GroupContainer extends Container<GroupLayout, GroupItemLayout> { export interface GroupContainer extends Container<GroupLayout, GroupItemLayout>, GroupContainerProperties {
} }
@@ -3114,6 +3114,10 @@ declare module 'azdata' {
} }
export interface GroupContainerProperties {
collapsed: boolean;
}
export interface LinkArea { export interface LinkArea {
text: string; text: string;
url: string; url: string;
@@ -3527,7 +3531,7 @@ declare module 'azdata' {
* Create a button which can be included in a dialog * Create a button which can be included in a dialog
* @param label The label of the button * @param label The label of the button
*/ */
export function createButton(label: string): Button; export function createButton(label: string, position?: DialogButtonPosition): Button;
/** /**
* Opens the given dialog if it is not already open * Opens the given dialog if it is not already open
@@ -3691,8 +3695,15 @@ declare module 'azdata' {
* Raised when the button is clicked * Raised when the button is clicked
*/ */
readonly onClick: vscode.Event<void>; readonly onClick: vscode.Event<void>;
/**
* Position of the button on the dialog footer
*/
position?: DialogButtonPosition;
} }
export type DialogButtonPosition = 'left' | 'right';
export interface WizardPageChangeInfo { export interface WizardPageChangeInfo {
/** /**
* The page number that the wizard changed from * The page number that the wizard changed from

View File

@@ -91,7 +91,7 @@ export class DialogModal extends Modal {
} }
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button { private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect); let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled; buttonElement.enabled = button.enabled;
if (registerClickEvent) { if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick); button.registerClickEvent(buttonElement.onDidClick);

View File

@@ -102,7 +102,7 @@ export class WizardModal extends Modal {
} }
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button { private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect); let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled; buttonElement.enabled = button.enabled;
if (registerClickEvent) { if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick); button.registerClickEvent(buttonElement.onDidClick);

View File

@@ -84,6 +84,7 @@ export class DialogButton implements azdata.window.Button {
private _enabled: boolean; private _enabled: boolean;
private _hidden: boolean; private _hidden: boolean;
private _focused: boolean; private _focused: boolean;
private _position?: azdata.window.DialogButtonPosition;
private _onClick: Emitter<void> = new Emitter<void>(); private _onClick: Emitter<void> = new Emitter<void>();
public readonly onClick: Event<void> = this._onClick.event; public readonly onClick: Event<void> = this._onClick.event;
private _onUpdate: Emitter<void> = new Emitter<void>(); private _onUpdate: Emitter<void> = new Emitter<void>();
@@ -131,6 +132,15 @@ export class DialogButton implements azdata.window.Button {
this._onUpdate.fire(); this._onUpdate.fire();
} }
public get position(): azdata.window.DialogButtonPosition | undefined {
return this._position;
}
public set position(value: azdata.window.DialogButtonPosition | undefined) {
this._position = value;
this._onUpdate.fire();
}
/** /**
* Register an event that notifies the button that it has been clicked * Register an event that notifies the button that it has been clicked
*/ */

View File

@@ -126,6 +126,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
let button = this._buttons.get(handle); let button = this._buttons.get(handle);
if (!button) { if (!button) {
button = new DialogButton(details.label, details.enabled); button = new DialogButton(details.label, details.enabled);
button.position = details.position;
button.hidden = details.hidden; button.hidden = details.hidden;
button.onClick(() => this.onButtonClick(handle)); button.onClick(() => this.onButtonClick(handle));
this._buttons.set(handle, button); this._buttons.set(handle, button);
@@ -134,6 +135,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
button.enabled = details.enabled; button.enabled = details.enabled;
button.hidden = details.hidden; button.hidden = details.hidden;
button.focused = details.focused; button.focused = details.focused;
button.position = details.position;
} }
return Promise.resolve(); return Promise.resolve();

View File

@@ -74,7 +74,7 @@ class ModelBuilderImpl implements azdata.ModelBuilder {
groupContainer(): azdata.GroupBuilder { groupContainer(): azdata.GroupBuilder {
let id = this.getNextComponentId(); let id = this.getNextComponentId();
let container: GenericContainerBuilder<azdata.GroupContainer, any, any> = new GenericContainerBuilder<azdata.GroupContainer, azdata.GroupLayout, azdata.GroupItemLayout>(this._proxy, this._handle, ModelComponentTypes.Group, id); let container = new GroupContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Group, id);
this._componentBuilders.set(id, container); this._componentBuilders.set(id, container);
return container; return container;
} }
@@ -422,6 +422,12 @@ class FormContainerBuilder extends GenericContainerBuilder<azdata.FormContainer,
} }
} }
class GroupContainerBuilder extends ContainerBuilderImpl<azdata.GroupContainer, azdata.GroupLayout, azdata.GroupItemLayout> {
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
super(new GroupContainerComponentWrapper(proxy, handle, type, id));
}
}
class ToolbarContainerBuilder extends GenericContainerBuilder<azdata.ToolbarContainer, azdata.ToolbarLayout, any> implements azdata.ToolbarBuilder { class ToolbarContainerBuilder extends GenericContainerBuilder<azdata.ToolbarContainer, azdata.ToolbarLayout, any> implements azdata.ToolbarBuilder {
withToolbarItems(components: azdata.ToolbarComponent[]): azdata.ContainerBuilder<azdata.ToolbarContainer, any, any> { withToolbarItems(components: azdata.ToolbarComponent[]): azdata.ContainerBuilder<azdata.ToolbarContainer, any, any> {
this._component.itemConfigs = components.map(item => { this._component.itemConfigs = components.map(item => {
@@ -1291,7 +1297,7 @@ class DropDownWrapper extends ComponentWrapper implements azdata.DropDownCompone
public get value(): string | azdata.CategoryValue { public get value(): string | azdata.CategoryValue {
let val = this.properties['value']; let val = this.properties['value'];
if (!val && this.values && this.values.length > 0) { if (!this.editable && !val && this.values && this.values.length > 0) {
val = this.values[0]; val = this.values[0];
} }
return val; return val;
@@ -1545,6 +1551,19 @@ class HyperlinkComponentWrapper extends ComponentWrapper implements azdata.Hyper
} }
} }
class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer {
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
super(proxy, handle, type, id);
this.properties = {};
}
public get collapsed(): boolean {
return this.properties['collapsed'];
}
public set collapsed(v: boolean) {
this.setProperty('collapsed', v);
}
}
class ModelViewImpl implements azdata.ModelView { class ModelViewImpl implements azdata.ModelView {
public onClosedEmitter = new Emitter<any>(); public onClosedEmitter = new Emitter<any>();

View File

@@ -208,6 +208,7 @@ class ButtonImpl implements azdata.window.Button {
private _enabled: boolean; private _enabled: boolean;
private _hidden: boolean; private _hidden: boolean;
private _focused: boolean; private _focused: boolean;
private _position: azdata.window.DialogButtonPosition;
private _onClick = new Emitter<void>(); private _onClick = new Emitter<void>();
public onClick = this._onClick.event; public onClick = this._onClick.event;
@@ -215,6 +216,7 @@ class ButtonImpl implements azdata.window.Button {
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) { constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) {
this._enabled = true; this._enabled = true;
this._hidden = false; this._hidden = false;
this._position = 'right';
} }
public get label(): string { public get label(): string {
@@ -244,6 +246,15 @@ class ButtonImpl implements azdata.window.Button {
this._extHostModelViewDialog.updateButton(this); this._extHostModelViewDialog.updateButton(this);
} }
public get position(): azdata.window.DialogButtonPosition {
return this._position;
}
public set position(value: azdata.window.DialogButtonPosition) {
this._position = value;
this._extHostModelViewDialog.updateButton(this);
}
public get focused(): boolean { public get focused(): boolean {
return this._focused; return this._focused;
} }
@@ -471,10 +482,10 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
return handle; return handle;
} }
private getHandle(item: azdata.window.Button | azdata.window.Dialog | azdata.window.DialogTab public getHandle(item: azdata.window.Button | azdata.window.Dialog | azdata.window.DialogTab
| azdata.window.ModelViewPanel | azdata.window.Wizard | azdata.window.WizardPage | azdata.workspace.ModelViewEditor) { | azdata.window.ModelViewPanel | azdata.window.Wizard | azdata.window.WizardPage | azdata.workspace.ModelViewEditor, createIfNotFound: boolean = true) {
let handle = this._objectHandles.get(item); let handle = this._objectHandles.get(item);
if (handle === undefined) { if (createIfNotFound && handle === undefined) {
handle = ExtHostModelViewDialog.getNewHandle(); handle = ExtHostModelViewDialog.getNewHandle();
this._objectHandles.set(item, handle); this._objectHandles.set(item, handle);
this._objectsByHandle.set(handle, item); this._objectsByHandle.set(handle, item);
@@ -587,7 +598,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
label: button.label, label: button.label,
enabled: button.enabled, enabled: button.enabled,
hidden: button.hidden, hidden: button.hidden,
focused: button.focused focused: button.focused,
position: button.position
}); });
} }
@@ -614,11 +626,12 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
return tab; return tab;
} }
public createButton(label: string): azdata.window.Button { public createButton(label: string, position: azdata.window.DialogButtonPosition = 'right'): azdata.window.Button {
let button = new ButtonImpl(this); let button = new ButtonImpl(this);
this.getHandle(button); this.getHandle(button);
this.registerOnClickCallback(button, button.getOnClickCallback()); this.registerOnClickCallback(button, button.getOnClickCallback());
button.label = label; button.label = label;
button.position = position;
return button; return button;
} }

View File

@@ -413,8 +413,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
createTab(title: string): azdata.window.DialogTab { createTab(title: string): azdata.window.DialogTab {
return extHostModelViewDialog.createTab(title, extension); return extHostModelViewDialog.createTab(title, extension);
}, },
createButton(label: string): azdata.window.Button { createButton(label: string, position: azdata.window.DialogButtonPosition = 'right'): azdata.window.Button {
return extHostModelViewDialog.createButton(label); return extHostModelViewDialog.createButton(label, position);
}, },
openDialog(dialog: azdata.window.Dialog) { openDialog(dialog: azdata.window.Dialog) {
return extHostModelViewDialog.openDialog(dialog); return extHostModelViewDialog.openDialog(dialog);

View File

@@ -253,6 +253,7 @@ export interface IModelViewButtonDetails {
enabled: boolean; enabled: boolean;
hidden: boolean; hidden: boolean;
focused?: boolean; focused?: boolean;
position?: 'left' | 'right';
} }
export interface IModelViewWizardPageDetails { export interface IModelViewWizardPageDetails {

View File

@@ -10,7 +10,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces'; import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces';
import { GroupLayout } from 'azdata'; import { GroupLayout, GroupContainerProperties } from 'azdata';
import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase';
@@ -37,7 +37,6 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
@Input() modelStore: IModelStore; @Input() modelStore: IModelStore;
private _containerLayout: GroupLayout; private _containerLayout: GroupLayout;
private _collapsed: boolean;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef; @ViewChild('container', { read: ElementRef }) private _container: ElementRef;
@@ -45,7 +44,7 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) { @Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el); super(changeRef, el);
this._collapsed = false; this.collapsed = false;
} }
ngOnInit(): void { ngOnInit(): void {
@@ -63,10 +62,18 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
public setLayout(layout: GroupLayout): void { public setLayout(layout: GroupLayout): void {
this._containerLayout = layout; this._containerLayout = layout;
this._collapsed = !!layout.collapsed; this.collapsed = !!layout.collapsed;
this.layout(); this.layout();
} }
public set collapsed(newValue: boolean) {
this.setPropertyFromUI<GroupContainerProperties, boolean>((properties, value) => { properties.collapsed = value; }, newValue);
}
public get collapsed(): boolean {
return this.getPropertyOrDefault<GroupContainerProperties, boolean>((props) => props.collapsed, false);
}
private hasHeader(): boolean { private hasHeader(): boolean {
return this._containerLayout && this._containerLayout && this._containerLayout.header !== undefined; return this._containerLayout && this._containerLayout && this._containerLayout.header !== undefined;
} }
@@ -88,12 +95,12 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
} }
private getContainerDisplayStyle(): string { private getContainerDisplayStyle(): string {
return !this.isCollapsible() || !this._collapsed ? 'block' : 'none'; return !this.isCollapsible() || !this.collapsed ? 'block' : 'none';
} }
private getHeaderClass(): string { private getHeaderClass(): string {
if (this.isCollapsible()) { if (this.isCollapsible()) {
let modifier = this._collapsed ? 'collapsed' : 'expanded'; let modifier = this.collapsed ? 'collapsed' : 'expanded';
return `modelview-group-header-collapsible ${modifier}`; return `modelview-group-header-collapsible ${modifier}`;
} else { } else {
return 'modelview-group-header'; return 'modelview-group-header';
@@ -102,7 +109,7 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
private changeState(): void { private changeState(): void {
if (this.isCollapsible()) { if (this.isCollapsible()) {
this._collapsed = !this._collapsed; this.collapsed = !this.collapsed;
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }
} }

View File

@@ -41,9 +41,7 @@ export default () => `
<div class="section deploy" style="display:${showDeploySection() ? 'block' : 'none'}"> <div class="section deploy" style="display:${showDeploySection() ? 'block' : 'none'}">
<h2 class="caption">${escape(localize('welcomePage.deploy', "Deploy"))}</h2> <h2 class="caption">${escape(localize('welcomePage.deploy', "Deploy"))}</h2>
<ul> <ul>
<li><a href="command:azdata.resource.sql-image.deploy">${escape(localize('welcomePage.deploy-image', "Deploy SQL Server on Docker…"))}</a></li> <li><a href="command:azdata.resource.deploy">${escape(localize('welcomePage.DeploySQLServer', "Deploy SQL Server…"))}</a></li>
<li><a href="command:azdata.resource.sql-bdc.deploy">${escape(localize('welcomePage.deploy-bdc', "Deploy SQL Server Big Data Cluster…"))}</a></li>
<li><a href="command:azdata.resource.deploy">${escape(localize('welcomePage.MoreOptions', "More…"))}</a></li>
</ul> </ul>
</div> </div>
<div class="section recent"> <div class="section recent">

View File

@@ -105,10 +105,6 @@ suite('ExtHostModelViewDialog Tests', () => {
}); });
test('Button clicks are forwarded to the correct button', () => { test('Button clicks are forwarded to the correct button', () => {
// Set up the proxy to record button handles
let handles = [];
mockProxy.setup(x => x.$setButtonDetails(It.isAny(), It.isAny())).callback((handle, details) => handles.push(handle));
// Set up the buttons to record click events // Set up the buttons to record click events
let label1 = 'button_1'; let label1 = 'button_1';
let label2 = 'button_2'; let label2 = 'button_2';
@@ -117,14 +113,16 @@ suite('ExtHostModelViewDialog Tests', () => {
let clickEvents = []; let clickEvents = [];
button1.onClick(() => clickEvents.push(1)); button1.onClick(() => clickEvents.push(1));
button2.onClick(() => clickEvents.push(2)); button2.onClick(() => clickEvents.push(2));
const button1Handle = extHostModelViewDialog.getHandle(button1, false);
const button2Handle = extHostModelViewDialog.getHandle(button2, false);
extHostModelViewDialog.updateButton(button1); extHostModelViewDialog.updateButton(button1);
extHostModelViewDialog.updateButton(button2); extHostModelViewDialog.updateButton(button2);
// If the main thread sends some notifications that the buttons have been clicked // If the main thread sends some notifications that the buttons have been clicked
extHostModelViewDialog.$onButtonClick(handles[0]); extHostModelViewDialog.$onButtonClick(button1Handle);
extHostModelViewDialog.$onButtonClick(handles[1]); extHostModelViewDialog.$onButtonClick(button2Handle);
extHostModelViewDialog.$onButtonClick(handles[1]); extHostModelViewDialog.$onButtonClick(button2Handle);
extHostModelViewDialog.$onButtonClick(handles[0]); extHostModelViewDialog.$onButtonClick(button1Handle);
// Then the clicks should have been handled by the expected handlers // Then the clicks should have been handled by the expected handlers
assert.deepEqual(clickEvents, [1, 2, 2, 1]); assert.deepEqual(clickEvents, [1, 2, 2, 1]);