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">
<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" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>opac_command_icons_bv</title>
<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"?>
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<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"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<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
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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>opac_command_icons_bv</title>
<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"/>
<polygon points="3 3.947 3 12.357 4 12.534 4 3.69 3 3.947" fill="#fff"/>
<polygon points="8 4.592 8 11.086 9 10.752 9 4.814 8 4.592" fill="#fff"/>
<polygon points="10 5.037 10 10.419 11 10.086 11 5.259 10 5.037" fill="#fff"/>
<polygon points="12 5.481 12 9.752 13 9.419 13 5.703 12 5.481" fill="#fff"/>
<rect width="16" height="16" fill="none"/>
</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": {
"name": "python",
"version": "3.7.3",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
@@ -85,11 +85,24 @@
"run_command('az --version')"
],
"metadata": {
"azdata_cell_guid": "326645cf-022a-47f2-8aff-37de71da8955"
"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": [
@@ -115,21 +128,14 @@
"print('You can also use the controller password to access Knox and SQL Server.')"
],
"metadata": {
"azdata_cell_guid": "17e5d087-7128-4d02-8c16-fe1ddee675e5"
"azdata_cell_guid": "17e5d087-7128-4d02-8c16-fe1ddee675e5",
"tags": [
"hide_input"
]
},
"outputs": [],
"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",
"source": [
@@ -148,7 +154,10 @@
"run_command(f'az login')"
],
"metadata": {
"azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083"
"azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 5
@@ -173,7 +182,10 @@
"run_command(f'az account show')"
],
"metadata": {
"azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c"
"azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
@@ -193,7 +205,10 @@
"run_command(f'az group create --name {azure_resource_group} --location {azure_region}')"
],
"metadata": {
"azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5"
"azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5",
"tags": [
"hide_input"
]
},
"outputs": [],
"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}')"
],
"metadata": {
"azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d"
"azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d",
"tags": [
"hide_input"
]
},
"outputs": [],
"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')"
],
"metadata": {
"azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437"
"azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 9
@@ -250,7 +271,6 @@
{
"cell_type": "code",
"source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
@@ -265,7 +285,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558"
"azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 10
@@ -283,14 +306,18 @@
"cell_type": "code",
"source": [
"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[\"CONTROLLER_PASSWORD\"] = mssql_password\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\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"
"azdata_cell_guid": "c43ea026-ca5e-4e2a-8602-fcc786354168",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 11
@@ -307,10 +334,13 @@
{
"cell_type": "code",
"source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')"
"run_command(f'azdata login -n {mssql_cluster_name}')"
],
"metadata": {
"azdata_cell_guid": "5120c387-1088-435b-856e-e59f147c45a2"
"azdata_cell_guid": "5120c387-1088-435b-856e-e59f147c45a2",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 12
@@ -337,7 +367,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
],
"metadata": {
"azdata_cell_guid": "9a5d0aef-a8da-4845-b470-d714435f0304"
"azdata_cell_guid": "9a5d0aef-a8da-4845-b470-d714435f0304",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 13
@@ -357,16 +390,19 @@
"source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\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",
"else:\n",
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
],
"metadata": {
"azdata_cell_guid": "1c9d1f2c-62ba-4070-920a-d30b67bdcc7c"
"azdata_cell_guid": "1c9d1f2c-62ba-4070-920a-d30b67bdcc7c",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 14
}
]
}
}

View File

@@ -6,7 +6,7 @@
},
"language_info": {
"name": "python",
"version": "3.7.3",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
@@ -83,11 +83,24 @@
"run_command('azdata --version')"
],
"metadata": {
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d"
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
"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": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
}
},
{
"cell_type": "markdown",
"source": [
@@ -113,21 +126,14 @@
"print('You can also use the controller password to access Knox and SQL Server.')"
],
"metadata": {
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac"
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
"tags": [
"hide_input"
]
},
"outputs": [],
"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",
"source": [
@@ -144,7 +150,10 @@
"run_command('kubectl config current-context')"
],
"metadata": {
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1"
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 0
@@ -161,7 +170,6 @@
{
"cell_type": "code",
"source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
@@ -176,7 +184,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021"
"azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
@@ -194,14 +205,18 @@
"cell_type": "code",
"source": [
"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[\"CONTROLLER_PASSWORD\"] = mssql_password\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\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": "373947a1-90b9-49ee-86f4-17a4c7d4ca76"
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 7
@@ -218,10 +233,13 @@
{
"cell_type": "code",
"source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')"
"run_command(f'azdata login -n {mssql_cluster_name}')"
],
"metadata": {
"azdata_cell_guid": "79adda27-371d-4dcb-b867-db025f8162a5"
"azdata_cell_guid": "79adda27-371d-4dcb-b867-db025f8162a5",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 8
@@ -248,7 +266,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
],
"metadata": {
"azdata_cell_guid": "a2202494-fd6c-4534-987d-15c403a5096f"
"azdata_cell_guid": "a2202494-fd6c-4534-987d-15c403a5096f",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 9
@@ -268,16 +289,19 @@
"source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\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",
"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": {
"azdata_cell_guid": "48342355-9d2b-4fa6-b1aa-3bc77d434dfa"
"azdata_cell_guid": "48342355-9d2b-4fa6-b1aa-3bc77d434dfa",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 10
}
]
}
}

View File

@@ -6,7 +6,7 @@
},
"language_info": {
"name": "python",
"version": "3.7.3",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
@@ -83,11 +83,24 @@
"run_command('azdata --version')"
],
"metadata": {
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8"
"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": [
@@ -103,6 +116,8 @@
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
"if invoked_by_wizard:\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",
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
" if mssql_password == \"\":\n",
@@ -110,24 +125,21 @@
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
" if mssql_password != confirm_password:\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.')"
],
"metadata": {
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e"
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
"tags": [
"hide_input"
]
},
"outputs": [],
"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",
"source": [
@@ -161,7 +173,6 @@
{
"cell_type": "code",
"source": [
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
"if not os.path.exists(mssql_target_profile):\n",
" os.mkdir(mssql_target_profile)\n",
@@ -176,7 +187,10 @@
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
],
"metadata": {
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf"
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 6
@@ -194,14 +208,21 @@
"cell_type": "code",
"source": [
"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[\"CONTROLLER_PASSWORD\"] = mssql_password\n",
"os.environ[\"MSSQL_SA_PASSWORD\"] = mssql_password\n",
"os.environ[\"KNOX_PASSWORD\"] = mssql_password\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"
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 7
@@ -218,10 +239,13 @@
{
"cell_type": "code",
"source": [
"run_command(f'azdata login --cluster-name {mssql_cluster_name}')"
"run_command(f'azdata login -n {mssql_cluster_name}')"
],
"metadata": {
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94"
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 8
@@ -248,7 +272,10 @@
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
],
"metadata": {
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6"
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 9
@@ -268,16 +295,19 @@
"source": [
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\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",
"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": {
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503"
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503",
"tags": [
"hide_input"
]
},
"outputs": [],
"execution_count": 10
}
]
}
}

View File

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

View File

@@ -1,12 +1,10 @@
{
"extension-displayName": "SQL Server Deployment extension for Azure Data Studio",
"extension-description": "Provides a notebook-based experience to deploy Microsoft SQL Server",
"deploy-sql-image-command-name": "Deploy SQL Server on Docker…",
"deploy-sql-bdc-command-name": "Deploy SQL Server Big Data Cluster…",
"deploy-resource-command-name": "More deployment options…",
"deploy-resource-command-name": "Deploy SQL Server…",
"deploy-resource-command-category": "Deployment",
"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-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",
@@ -14,7 +12,7 @@
"sql-2019-display-name": "SQL Server 2019 RC",
"sql-2017-docker-notebook": "./notebooks/docker/2017/deploy-sql2017-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-new-aks": "New 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-existing-aks-notebook": "./notebooks/bdc/2019/deploy-bdc-existing-aks.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",
"docker-sql-2019-title": "Deploy SQL Server 2019 container images with Docker",
"azdata-bdc-2019-aks-notebook": "./notebooks/bdc/2019/azdata/deploy-bdc-aks.ipynb",
"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-sql-password-field": "SQL Server password",
"docker-confirm-sql-password-field": "Confirm password",
@@ -52,5 +53,5 @@
"bdc-agreement": "I accept {0}, {1} and {2}.",
"bdc-agreement-privacy-statement":"Microsoft Privacy Statement",
"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[];
providers: DeploymentProvider[];
agreement?: AgreementInfo;
displayIndex?: number;
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
}
@@ -92,6 +93,7 @@ export type DeploymentProvider = DialogDeploymentProvider | WizardDeploymentProv
export interface WizardInfo {
notebook: string | NotebookInfo;
azdata_notebook: string | NotebookInfo;
type: BdcDeploymentType;
}
@@ -161,6 +163,9 @@ export interface FieldInfo {
useCustomValidator?: boolean;
labelPosition?: LabelPosition; // overwrite the labelPosition of SectionInfo.
fontStyle?: FontStyle;
labelFontWeight?: FontWeight;
links?: azdata.LinkArea[];
editable?: boolean; // for editable dropdown
}
export enum LabelPosition {
@@ -173,6 +178,11 @@ export enum FontStyle {
Italic = 'italic'
}
export enum FontWeight {
Normal = 'normal',
Bold = 'bold'
}
export enum FieldType {
Text = 'text',
Number = 'number',

View File

@@ -5,23 +5,42 @@
import * as path from 'path';
import { IPlatformService } from './platformService';
import { BigDataClusterDeploymentProfile } from './bigDataClusterDeploymentProfile';
import { BdcDeploymentType } from '../interfaces';
interface BdcConfigListOutput {
stdout: string[];
result: string[];
}
export interface BdcEndpoint {
endpoint: string;
name: 'sql-server-master';
}
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 {
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();
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[]> {
@@ -29,17 +48,16 @@ export class AzdataService implements IAzdataService {
// azdata requires this environment variables to be set
env['ACCEPT_EULA'] = 'yes';
const cmd = 'azdata bdc config list -o json';
// Run the command twice to workaround the issue:
// 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 stdout = await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
const output = <BdcConfigListOutput>JSON.parse(stdout);
return output.stdout;
return output.result;
}
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([
this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'bdc.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> {
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.
* 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 DataResource = 'data-0';
export const HdfsResource = 'storage-0';
@@ -11,15 +11,26 @@ export const NameNodeResource = 'nmnode-0';
export const SparkHeadResource = 'sparkhead';
export const ZooKeeperResource = 'zookeeper';
export const SparkResource = 'spark-0';
export const HadrEnabledSetting = 'hadr.enabled';
interface ServiceEndpoint {
port: number;
serviceType: ServiceType;
name: EndpointName;
dnsName?: string;
}
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 {
constructor(private _profileName: string, private _bdcConfig: any, private _controlConfig: any) {
@@ -39,6 +50,30 @@ export class BigDataClusterDeploymentProfile {
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 {
return this._bdcConfig;
}
@@ -107,15 +142,6 @@ export class BigDataClusterDeploymentProfile {
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 {
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);
}
public set controllerPort(port: number) {
this.setEndpointPort(this._controlConfig.spec.endpoints, 'Controller', port);
public setControllerEndpoint(port: number, dnsName?: string) {
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 {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', 31433);
}
public set sqlServerPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', port);
public setSqlServerEndpoint(port: number, dnsName?: string) {
this.setEndpoint(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', port, dnsName);
}
public get sqlServerReadableSecondaryPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', 31436);
}
public set sqlServerReadableSecondaryPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', port);
public setSqlServerReadableSecondaryEndpoint(port: number, dnsName?: string) {
this.setEndpoint(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', port, dnsName);
}
public get gatewayPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', 30443);
}
public set gatewayPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', port);
public setGatewayEndpoint(port: number, dnsName?: string) {
this.setEndpoint(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', port, dnsName);
}
public addSparkResource(replicas: number): void {
@@ -220,8 +262,32 @@ export class BigDataClusterDeploymentProfile {
}
public get activeDirectorySupported(): boolean {
// TODO: Implement AD authentication
return false;
// The profiles that highlight the AD authentication feature will have a security secion in the control.json for the AD settings.
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 {
@@ -249,16 +315,27 @@ export class BigDataClusterDeploymentProfile {
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);
if (endpoint) {
endpoint.port = port;
endpoint.dnsName = dnsName;
} else {
endpoints.push({
const newEndpoint: ServiceEndpoint = {
name: name,
serviceType: 'NodePort',
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 { IPlatformService } from './platformService';
import { NotebookInfo } from '../interfaces';
import { getErrorMessage, getDateTimeString } from '../utils';
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 {
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 {
@@ -21,32 +43,89 @@ export class NotebookService implements INotebookService {
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
*/
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> {
const notebookPath = this.getNotebook(notebook);
const notebookFullPath = path.join(this.extensionPath, 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);
}
});
}
return this.getNotebookFullPath(notebook).then(notebookPath => {
return this.showNotebookAsUntitled(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
* @param notebook the notebook path
*/
getNotebook(notebook: string | NotebookInfo): string {
getNotebookPath(notebook: string | NotebookInfo): string {
let notebookPath;
if (notebook && !isString(notebook)) {
const platform = this.platformService.platform();

View File

@@ -7,6 +7,7 @@ import * as fs from 'fs';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as cp from 'child_process';
import { getErrorMessage } from '../utils';
/**
* Abstract of platform dependencies
@@ -22,6 +23,8 @@ export interface IPlatformService {
makeDirectory(path: string): Promise<void>;
readTextFile(filePath: string): 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 {
@@ -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 {
return localize('resourceDeployment.DockerDisplayName', 'Docker');
return localize('resourceDeployment.DockerDisplayName', 'docker');
}
get homePage(): string {

View File

@@ -17,13 +17,13 @@ suite('Notebook Service Tests', function (): void {
const notebookService = new NotebookService(mockPlatformService.object, '');
const notebookInput = 'test-notebook.ipynb';
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');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
mockPlatformService.reset();
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');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
});
@@ -41,19 +41,19 @@ suite('Notebook Service Tests', function (): void {
linux: notebookLinux
};
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');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
mockPlatformService.reset();
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');
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
mockPlatformService.reset();
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');
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 AdminPassword_VariableName = 'AZDATA_NB_VAR_BDC_ADMIN_PASSWORD';
export const AuthenticationMode_VariableName = 'AZDATA_NB_VAR_BDC_AUTHENTICATION_MODE';
export const DistinguishedName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DN';
export const AdminPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_ADMIN_PRINCIPALS';
export const UserPrincipals_VariableName = 'AZDATA_NB_VAR_BDC_AD_USER_PRINCIPALS';
export const UpstreamIPAddresses_VariableName = 'AZDATA_NB_VAR_BDC_AD_UPSTREAM_IPADDRESSES';
export const DnsName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DNS_NAME';
export const OrganizationalUnitDistinguishedName_VariableName = 'AZDATA_NB_VAR_BDC_AD_OUDN';
export const ClusterAdmins_VariableName = 'AZDATA_NB_VAR_BDC_AD_CLUSTER_ADMINS';
export const ClusterUsers_VariableName = 'AZDATA_NB_VAR_BDC_AD_CLUSTER_USERS';
export const DomainDNSIPAddresses_VariableName = 'AZDATA_NB_VAR_BDC_AD_UPSTREAM_IPADDRESSES';
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 AppOwnerPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_OWNER_PRINCIPALS';
export const AppReaderPrincipals_VariableName = 'AZDATA_NB_VAR_AD_BDC_APP_READER_PRINCIPALS';
export const DomainServiceAccountUserName_VariableName = 'AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_USERNAME';
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 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 VMSize_VariableName = 'AZDATA_NB_VAR_BDC_AZURE_VM_SIZE';
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 ReadableSecondaryDNSName_VariableName = 'AZDATA_NB_VAR_BDC_READABLE_SECONDARY_DNS';
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 { TargetClusterContextPage } from './pages/targetClusterPage';
import { IKubeService } from '../../services/kubeService';
import { IAzdataService } from '../../services/azdataService';
import { IAzdataService, BdcEndpoint } from '../../services/azdataService';
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
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 os from 'os';
import { join } from 'path';
import * as fs from 'fs';
const localize = nls.loadMessageBundle();
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> {
private _saveConfigButton: azdata.window.Button;
private _scriptToNotebookButton: azdata.window.Button;
public get kubeService(): IKubeService {
return this._kubeService;
@@ -36,8 +42,24 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
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) {
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 {
@@ -47,23 +69,79 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
protected initialize(): void {
this.setPages(this.getPages());
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 onOk(): void {
process.env[VariableNames.AdminPassword_VariableName] = this.model.getStringValue(VariableNames.AdminPassword_VariableName);
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => {
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
editBuilder.insertCell({
const taskName = localize('resourceDeployment.DeployBDCTask', "Deploy SQL Server Big Data Cluster \"{0}\"", this.model.getStringValue(VariableNames.ClusterName_VariableName));
azdata.tasks.startBackgroundOperation({
displayName: taskName,
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',
source: this.model.getCodeCellContentForNotebook()
}, 7);
});
}, (error) => {
vscode.window.showErrorMessage(error);
source: this.model.getCodeCellContentForNotebook(),
metadata: {},
execution_count: 0,
outputs: []
});
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;
}
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 {
switch (type) {
case BdcDeploymentType.NewAKS:

View File

@@ -15,14 +15,6 @@ export class DeployClusterWizardModel extends Model {
}
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 {
return this.getStringValue(VariableNames.AuthenticationMode_VariableName);
}
@@ -71,6 +63,13 @@ export class DeployClusterWizardModel extends Model {
const sourceBdcJson = Object.assign({}, this.selectedProfile!.bdcConfig);
const sourceControlJson = Object.assign({}, this.selectedProfile!.controlConfig);
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
targetDeploymentProfile.clusterName = this.getStringValue(VariableNames.ClusterName_VariableName)!;
// storage settings
@@ -111,23 +110,37 @@ export class DeployClusterWizardModel extends Model {
}
targetDeploymentProfile.includeSpark = this.getBooleanValue(VariableNames.IncludeSpark_VariableName);
targetDeploymentProfile.hadrEnabled = this.getBooleanValue(VariableNames.EnableHADR_VariableName);
// port settings
targetDeploymentProfile.gatewayPort = this.getIntegerValue(VariableNames.GateWayPort_VariableName);
targetDeploymentProfile.sqlServerPort = this.getIntegerValue(VariableNames.SQLServerPort_VariableName);
targetDeploymentProfile.controllerPort = this.getIntegerValue(VariableNames.ControllerPort_VariableName);
targetDeploymentProfile.sqlServerReadableSecondaryPort = this.getIntegerValue(VariableNames.ReadableSecondaryPort_VariableName);
// endpoint settings
targetDeploymentProfile.setGatewayEndpoint(this.getIntegerValue(VariableNames.GateWayPort_VariableName), this.getStringValue(VariableNames.GatewayDNSName_VariableName));
targetDeploymentProfile.setSqlServerEndpoint(this.getIntegerValue(VariableNames.SQLServerPort_VariableName), this.getStringValue(VariableNames.SQLServerDNSName_VariableName));
targetDeploymentProfile.setControllerEndpoint(this.getIntegerValue(VariableNames.ControllerPort_VariableName), this.getStringValue(VariableNames.ControllerDNSName_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;
}
public getCodeCellContentForNotebook(): string {
public getCodeCellContentForNotebook(): string[] {
const profile = this.createTargetProfile();
const statements: string[] = [];
if (this.deploymentTarget === BdcDeploymentType.NewAKS) {
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_vm_size = '${this.getStringValue(VariableNames.VMSize_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('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_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(`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.')`);
return statements.join(EOL);
return statements.map(line => line + EOL);
}
private escapeForNotebookCodeCell(original: string): string {

View File

@@ -9,8 +9,8 @@ import * as nls from 'vscode-nls';
import { DeployClusterWizard } from '../deployClusterWizard';
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
import { WizardPageBase } from '../../wizardPageBase';
import { createSection, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
import { SubscriptionId_VariableName, ResourceGroup_VariableName, Region_VariableName, AksName_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
import { createSection, InputComponents, setModelValues, Validator, getDropdownComponent, MissingRequiredInformationErrorMessage } from '../../modelViewUtils';
import { SubscriptionId_VariableName, ResourceGroup_VariableName, Location_VariableName, AksName_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
const localize = nls.loadMessageBundle();
export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
@@ -26,8 +26,9 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
const azureSection: SectionInfo = {
title: '',
labelPosition: LabelPosition.Left,
fields: [
{
spaceBetweenFields: '5px',
rows: [{
fields: [{
type: FieldType.Text,
label: localize('deployCluster.SubscriptionField', "Subscription id"),
required: false,
@@ -35,42 +36,100 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
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.")
}, {
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,
label: localize('deployCluster.ResourceGroupName', "New resource group name"),
required: true,
variableName: ResourceGroup_VariableName,
defaultValue: 'mssql-'
}, {
type: FieldType.Text,
label: localize('deployCluster.Region', "Region"),
}]
}, {
fields: [{
type: FieldType.Options,
label: localize('deployCluster.Location', "Location"),
required: true,
variableName: Region_VariableName,
defaultValue: 'eastus'
variableName: Location_VariableName,
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,
label: localize('deployCluster.AksName', "AKS cluster name"),
required: true,
variableName: AksName_VariableName,
defaultValue: 'mssql-',
}, {
type: FieldType.Number,
label: localize('deployCluster.VMCount', "VM count"),
required: true,
variableName: VMCount_VariableName,
defaultValue: '5',
min: 1,
max: 999
}, {
}]
}, {
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.VMCount', "VM count"),
required: true,
variableName: VMCount_VariableName,
defaultValue: '5',
min: 1,
max: 999
}
]
}, {
fields: [{
type: FieldType.Text,
label: localize('deployCluster.VMSize', "VM size"),
required: true,
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) => {
const azureGroup = createSection({
sectionInfo: azureSection,
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 {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
setModelValues(this.inputComponents, this.wizard.model);
}
}

View File

@@ -18,6 +18,8 @@ const localize = nls.loadMessageBundle();
const ConfirmPasswordName = 'ConfirmPassword';
export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
private inputComponents: InputComponents = {};
private activeDirectorySection!: azdata.FormComponent;
private formBuilder!: azdata.FormBuilder;
constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.ClusterSettingsPageTitle', "Cluster settings"),
@@ -39,11 +41,12 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
useCustomValidator: true
}, {
type: FieldType.Text,
label: localize('deployCluster.ControllerUsername', "Controller username"),
label: localize('deployCluster.AdminUsername', "Admin username"),
required: true,
variableName: VariableNames.AdminUserName_VariableName,
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,
label: localize('deployCluster.AdminPassword', "Password"),
@@ -51,7 +54,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.AdminPassword_VariableName,
defaultValue: '',
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,
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 = {
labelPosition: LabelPosition.Left,
title: localize('deployCluster.ActiveDirectorySettings', "Active Directory settings"),
fields: [
{
type: FieldType.Text,
label: localize('deployCluster.DistinguishedName', "Distinguished name"),
label: localize('deployCluster.OuDistinguishedName', "Organizational unit"),
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
}, {
type: FieldType.Text,
label: localize('deployCluster.AdminPrincipals', "Admin principals"),
label: localize('deployCluster.ClusterAdmins', "Cluster admin group"),
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
}, {
type: FieldType.Text,
label: localize('deployCluster.UserPrincipals', "User principals"),
required: true,
variableName: VariableNames.UserPrincipals_VariableName,
useCustomValidator: true
label: localize('deployCluster.AppOwers', "App owners"),
required: false,
variableName: VariableNames.AppOwners_VariableName,
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,
label: localize('deployCluster.UpstreamIPAddresses', "Upstream IP Addresses"),
required: true,
variableName: VariableNames.UpstreamIPAddresses_VariableName,
useCustomValidator: true
}, {
type: FieldType.Text,
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
label: localize('deployCluster.AppReaders', "App readers"),
required: false,
variableName: VariableNames.AppReaders_VariableName,
useCustomValidator: true,
placeHolder: localize('deployCluster.AppReadersPlaceHolder', "Use comma to separate the values."),
description: localize('deployCluster.AppReadersDescription', "The Active Directory users or groups of app readers. Use comma as separator them if there are multiple users/groups.")
}
]
};
@@ -164,11 +227,26 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
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 activeDirectoryFormItem = { title: '', component: activeDirectorySettingsGroup };
const dockerSettingsFormItem = { title: '', component: dockerSettingsGroup };
this.activeDirectorySection = { title: '', component: activeDirectorySettingsGroup };
const authModeDropdown = <azdata.DropDownComponent>this.inputComponents[VariableNames.AuthenticationMode_VariableName];
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[basicSettingsFormItem],
this.formBuilder = view.modelBuilder.formContainer().withFormItems(
[basicSettingsFormItem, dockerSettingsFormItem],
{
horizontal: false,
componentWidth: '100%'
@@ -176,76 +254,99 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
);
this.wizard.registerDisposable(authModeDropdown.onValueChanged(() => {
const isBasicAuthMode = (<azdata.CategoryValue>authModeDropdown.value).name === 'basic';
if (isBasicAuthMode) {
formBuilder.removeFormItem(activeDirectoryFormItem);
this.formBuilder.removeFormItem(this.activeDirectorySection);
} else {
formBuilder.insertFormItem(activeDirectoryFormItem);
this.formBuilder.insertFormItem(this.activeDirectorySection);
}
}));
const form = formBuilder.withLayout({ width: '100%' }).component();
const form = this.formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(form);
});
}
public onLeave() {
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) => {
return true;
});
}
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];
if (authModeDropdown) {
authModeDropdown.enabled = this.wizard.model.adAuthSupported;
}
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.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;
const adAuthSelected = (<azdata.CategoryValue>authModeDropdown.value).name === 'ad';
if (!this.wizard.model.adAuthSupported && adAuthSelected) {
this.formBuilder.removeFormItem(this.activeDirectorySection);
authModeDropdown.value = {
name: AuthenticationMode.Basic,
displayName: localize('deployCluster.AuthenticationMode.Basic', "Basic")
};
}
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: 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()
}, {
label: localize('deployCluster.defaultLogStorage', "Log storage size (GB)"),
label: localize('deployCluster.defaultLogStorage', "Log storage"),
value: profile.controllerLogsStorageSize.toString()
}, {
label: '' // line separator
}
];
}, {
label: localize('deployCluster.features', "Features"),
value: '',
fontWeight: 'bold'
}, {
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
value: ''
}];
if (profile.activeDirectorySupported) {
descriptions.push({
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"),
value: ''
});
} else {
descriptions.push({
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
value: '✅'
value: ''
});
}
if (profile.hadrEnabled) {
if (profile.sqlServerReplicas > 1) {
descriptions.push({
label: localize('deployCluster.hadr', "High Availability"),
value: ''
value: ''
});
}
@@ -114,7 +119,7 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
label: profile.profileName,
descriptions: descriptions,
width: '240px',
height: '300px',
height: '320px',
}).component();
this._cards.push(card);
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.ControllerDataStorageSize_VariableName, selectedProfile.controllerDataStorageSize);
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.GateWayPort_VariableName, selectedProfile.gatewayPort);
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.ControllerDataStorageClassName_VariableName, selectedProfile.controllerDataStorageClass);
this.wizard.model.setPropertyValue(VariableNames.ControllerLogsStorageClassName_VariableName, selectedProfile.controllerLogsStorageClass);
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.selectedProfile = selectedProfile;
}
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();
profiles.forEach(profile => {

View File

@@ -13,9 +13,9 @@ import * as VariableNames from '../constants';
import { AuthenticationMode } from '../deployClusterWizardModel';
const localize = nls.loadMessageBundle();
const PortInputWidth = '100px';
const NumberInputWidth = '100px';
const inputWidth = '180px';
const labelWidth = '150px';
const labelWidth = '200px';
const spaceBetweenFields = '5px';
export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
@@ -32,6 +32,12 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
private gatewayDNSInput!: azdata.InputBoxComponent;
private gatewayPortInput!: azdata.InputBoxComponent;
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 readableSecondaryPortInput!: azdata.InputBoxComponent;
private readableSecondaryEndpointRow!: azdata.FlexContainer;
@@ -39,7 +45,10 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
private controllerNameLabel!: azdata.TextComponent;
private SqlServerNameLabel!: azdata.TextComponent;
private gatewayNameLabel!: azdata.TextComponent;
private serviceProxyNameLabel!: azdata.TextComponent;
private appServiceProxyNameLabel!: azdata.TextComponent;
private readableSecondaryNameLabel!: azdata.TextComponent;
private endpointSection!: azdata.GroupContainer;
constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.ServiceSettingsPageTitle', "Service settings"), '', wizard);
@@ -48,37 +57,51 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
const scaleSectionInfo: SectionInfo = {
title: localize('deployCluster.scaleSectionTitle', "Scale settings"),
labelWidth: labelWidth,
inputWidth: inputWidth,
spaceBetweenFields: spaceBetweenFields,
inputWidth: NumberInputWidth,
spaceBetweenFields: '40px',
rows: [{
fields: [
{
type: FieldType.Number,
label: localize('deployCluster.ComputeText', "Compute"),
min: 1,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.ComputePoolScale_VariableName,
}
]
fields: [{
type: FieldType.Options,
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
options: ['1', '3', '4', '5', '6', '7', '8', '9'],
defaultValue: '1',
required: true,
variableName: VariableNames.SQLServerScale_VariableName,
}, {
type: FieldType.Number,
label: localize('deployCluster.ComputePoolInstances', "Compute pool instances"),
min: 1,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
variableName: VariableNames.ComputePoolScale_VariableName,
}]
}, {
fields: [{
type: FieldType.Number,
label: localize('deployCluster.DataText', "Data"),
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
min: 1,
max: 100,
defaultValue: '1',
useCustomValidator: true,
required: true,
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: [
{
type: FieldType.Number,
label: localize('deployCluster.HDFSText', "HDFS"),
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
min: 1,
max: 100,
defaultValue: '1',
@@ -87,90 +110,12 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.HDFSPoolScale_VariableName
}, {
type: FieldType.Checkbox,
label: localize('deployCluster.includeSparkInHDFSPool', "Include Spark"),
label: localize('deployCluster.IncludeSparkInStoragePool', "Include Spark in storage pool"),
defaultValue: 'true',
variableName: VariableNames.IncludeSpark_VariableName,
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: [
{
type: FieldType.Text,
label: localize('deployCluster.HDFSText', "HDFS"),
label: localize('deployCluster.StoragePool', "Storage pool (HDFS)"),
required: false,
variableName: VariableNames.HDFSDataStorageClassName_VariableName,
placeHolder: hintTextForStorageFields,
@@ -286,7 +231,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
fields: [
{
type: FieldType.Text,
label: localize('deployCluster.DataText', "Data"),
label: localize('deployCluster.DataPool', "Data pool"),
required: false,
variableName: VariableNames.DataPoolDataStorageClassName_VariableName,
labelWidth: labelWidth,
@@ -364,25 +309,22 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
});
};
const scaleSection = createSectionFunc(scaleSectionInfo);
const hadrSection = createSectionFunc(hadrSectionInfo);
const endpointSection = this.createEndpointSection(view);
this.endpointSection = this.createEndpointSection(view);
const storageSection = createSectionFunc(storageSectionInfo);
const advancedStorageSection = createSectionFunc(advancedStorageSectionInfo);
const storageContainer = createGroupContainer(view, [storageSection, advancedStorageSection], {
header: localize('deployCluster.StorageSectionTitle', "Storage settings"),
collapsible: true
});
this.setSQLServerMasterFieldEventHandler();
this.handleSparkSettingEvents();
const form = view.modelBuilder.formContainer().withFormItems([
{
title: '',
component: scaleSection
}, {
title: '',
component: hadrSection
}, {
title: '',
component: endpointSection
component: this.endpointSection
}, {
title: '',
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 {
this.endpointNameColumnHeader = createLabel(view, { text: '', width: labelWidth });
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.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.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.inputComponents[VariableNames.ControllerDNSName_VariableName] = this.controllerDNSInput;
this.inputComponents[VariableNames.ControllerPort_VariableName] = this.controllerPortInput;
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.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.inputComponents[VariableNames.SQLServerDNSName_VariableName] = this.sqlServerDNSInput;
this.inputComponents[VariableNames.SQLServerPort_VariableName] = this.sqlServerPortInput;
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.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.inputComponents[VariableNames.GatewayDNSName_VariableName] = this.gatewayDNSInput;
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.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.inputComponents[VariableNames.ReadableSecondaryDNSName_VariableName] = this.readableSecondaryDNSInput;
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"),
collapsible: true
});
}
public onEnter(): void {
this.setDropdownValue(VariableNames.SQLServerScale_VariableName);
this.setCheckboxValue(VariableNames.EnableHADR_VariableName);
this.setInputBoxValue(VariableNames.ComputePoolScale_VariableName);
this.setInputBoxValue(VariableNames.DataPoolScale_VariableName);
this.setInputBoxValue(VariableNames.HDFSPoolScale_VariableName);
this.setInputBoxValue(VariableNames.HDFSNameNodeScale_VariableName);
this.setInputBoxValue(VariableNames.SparkPoolScale_VariableName);
this.setInputBoxValue(VariableNames.SparkHeadScale_VariableName);
this.setInputBoxValue(VariableNames.ZooKeeperScale_VariableName);
this.setCheckboxValue(VariableNames.IncludeSpark_VariableName);
this.setEnableHadrCheckboxState(this.wizard.model.getIntegerValue(VariableNames.SQLServerScale_VariableName));
this.setInputBoxValue(VariableNames.ControllerPort_VariableName);
this.setInputBoxValue(VariableNames.SQLServerPort_VariableName);
this.setInputBoxValue(VariableNames.GateWayPort_VariableName);
this.setInputBoxValue(VariableNames.ServiceProxyPort_VariableName);
this.setInputBoxValue(VariableNames.AppServiceProxyPort_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.ControllerDataStorageSize_VariableName);
this.setInputBoxValue(VariableNames.ControllerLogsStorageClassName_VariableName);
this.setInputBoxValue(VariableNames.ControllerLogsStorageSize_VariableName);
this.endpointHeaderRow.clearItems();
this.endpointSection.collapsed = this.wizard.model.authenticationMode !== AuthenticationMode.ActiveDirectory;
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
this.endpointHeaderRow.addItems([this.endpointNameColumnHeader, this.dnsColumnHeader, this.portColumnHeader]);
}
this.loadEndpointRow(this.controllerEndpointRow, this.controllerNameLabel, this.controllerDNSInput, this.controllerPortInput);
this.loadEndpointRow(this.gatewayEndpointRow, this.gatewayNameLabel, this.gatewayDNSInput, this.gatewayPortInput);
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.message = { text: '' };
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.HDFSNameNodeScale_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.HDFSPoolScale_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.ControllerDataStorageSize_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.SQLServerPort_VariableName, this.inputComponents))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.GateWayPort_VariableName, this.inputComponents))
&& (!getCheckboxComponent(VariableNames.EnableHADR_VariableName, this.inputComponents).checked
|| !isInputBoxEmpty(this.readableSecondaryPortInput))
&& !isInputBoxEmpty(getInputBoxComponent(VariableNames.AppServiceProxyPort_VariableName, this.inputComponents))
&& !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
|| (!isInputBoxEmpty(this.gatewayDNSInput)
&& !isInputBoxEmpty(this.controllerDNSInput)
&& !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 = {
text: MissingRequiredInformationErrorMessage,
text: errorMessage,
level: azdata.window.MessageLevel.Error
};
}
return isValid;
return allInputFilled && sparkEnabled;
}
return true;
});
@@ -514,43 +503,6 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
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 {
row.clearItems();
const itemLayout: azdata.FlexItemLayout = { CSSStyles: { 'margin-right': '20px' } };

View File

@@ -4,24 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
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 { WizardPageBase } from '../../wizardPageBase';
import * as VariableNames from '../constants';
import * as os from 'os';
import { join } from 'path';
import * as fs from 'fs';
import { AuthenticationMode } from '../deployClusterWizardModel';
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
const localize = nls.loadMessageBundle();
export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
private formItems: azdata.FormComponent[] = [];
private form!: azdata.FormBuilder;
private view!: azdata.ModelView;
private targetDeploymentProfile!: BigDataClusterDeploymentProfile;
constructor(wizard: DeployClusterWizard) {
super(localize('deployCluster.summaryPageTitle', "Summary"), '', wizard);
@@ -30,30 +24,20 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
public initialize(): void {
this.pageObject.registerContent((view: azdata.ModelView) => {
this.view = view;
const deploymentJsonSection = createGroupContainer(view, [
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
}
]);
this.form = view.modelBuilder.formContainer();
return view.initializeModel(this.form!.withLayout({ width: '100%' }).component());
});
}
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.form!.removeFormItem(item);
});
@@ -71,13 +55,13 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText,
label: localize('deployCluster.Kubeconfig', "Kube config"),
defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterContext', "Cluster context"),
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,
label: localize('deployCluster.DeploymentProfile', "Deployment profile"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterName', "Cluster name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
}]
}, {
fields: [
@@ -110,20 +94,92 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText,
label: localize('deployCluster.ControllerUsername', "Controller username"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ?
localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") :
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 = {
labelPosition: LabelPosition.Left,
labelWidth: '150px',
@@ -135,26 +191,26 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
type: FieldType.ReadonlyText,
label: localize('deployCluster.SubscriptionId', "Subscription id"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.ResourceGroup', "Resource group"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
}
]
}, {
fields: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.Region', "Region"),
defaultValue: this.wizard.model.getStringValue(VariableNames.Region_VariableName),
fontStyle: FontStyle.Italic
label: localize('deployCluster.Location', "Location"),
defaultValue: this.wizard.model.getStringValue(VariableNames.Location_VariableName),
labelFontWeight: FontWeight.Bold
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.AksClusterName', "AKS cluster name"),
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,
label: localize('deployCluster.VMSize', "VM size"),
defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName),
fontStyle: FontStyle.Italic
labelFontWeight: FontWeight.Bold
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.VMCount', "VM count"),
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: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ComputeText', "Compute"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName),
fontStyle: FontStyle.Italic
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName),
labelFontWeight: FontWeight.Bold
}, {
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),
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: [
{
type: FieldType.ReadonlyText,
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)") : ''}`,
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
}
]
fields: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`,
labelFontWeight: FontWeight.Bold
}]
}
]
};
@@ -271,7 +293,6 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
const clusterSection = createSectionFunc(clusterSectionInfo);
const scaleSection = createSectionFunc(scaleSectionInfo);
const hadrSection = createSectionFunc(hadrSectionInfo);
const endpointSection = {
title: '',
component: this.createEndpointSection()
@@ -285,10 +306,16 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
this.formItems.push(azureSection);
}
this.formItems.push(clusterSection, scaleSection, hadrSection, endpointSection, storageSection);
this.formItems.push(clusterSection, scaleSection, endpointSection, storageSection);
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 {
const value = this.wizard.model.getStringValue(propertyName);
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.ControllerLogsStorageSize_VariableName)],
[
localize('deployCluster.HDFSText', "HDFS"),
localize('deployCluster.StoragePool', "Storage pool (HDFS)"),
this.getStorageSettingValue(VariableNames.HDFSDataStorageClassName_VariableName, VariableNames.ControllerDataStorageClassName_VariableName),
this.getStorageSettingValue(VariableNames.HDFSDataStorageSize_VariableName, VariableNames.ControllerDataStorageSize_VariableName),
this.getStorageSettingValue(VariableNames.HDFSLogsStorageClassName_VariableName, VariableNames.ControllerLogsStorageClassName_VariableName),
@@ -357,10 +384,12 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
const endpointRows = [
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.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(
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 {
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) {
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);
}
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 vscode from 'vscode';
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 { getDateTimeString } from '../utils';
const localize = nls.loadMessageBundle();
@@ -40,7 +41,6 @@ export interface WizardPageContext extends CreateContext {
container: azdata.window.Wizard;
}
export interface SectionContext extends CreateContext {
sectionInfo: SectionInfo;
view: azdata.ModelView;
@@ -70,12 +70,13 @@ export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValu
}).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>({
value: info.text,
description: info.description,
requiredIndicator: info.required,
CSSStyles: { 'font-style': info.fontStyle || 'normal' }
CSSStyles: { 'font-style': info.fontStyle || 'normal', 'font-weight': info.fontWeight || 'normal' },
links: info.links
}).component();
text.width = info.width;
return text;
@@ -101,11 +102,13 @@ export function createCheckbox(view: azdata.ModelView, info: { initialValue: boo
}).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>({
values: info.values,
value: info.defaultValue,
width: info.width
width: info.width,
editable: info.editable,
fireOnTextChange: true
}).component();
}
@@ -232,7 +235,6 @@ function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata
}
}
function processField(context: FieldContext): void {
switch (context.fieldInfo.type) {
case FieldType.Options:
@@ -263,19 +265,21 @@ function processField(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, {
values: context.fieldInfo.options,
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);
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition);
}
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 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 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 + getDateTimeString();
const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: defaultValue,
ariaLabel: context.fieldInfo.label,
@@ -289,7 +293,7 @@ function processDateTimeTextField(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, {
defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label,
@@ -304,7 +308,7 @@ function processNumberField(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, {
defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label,
@@ -317,7 +321,7 @@ function processTextField(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>({
ariaLabel: context.fieldInfo.label,
inputType: 'password',
@@ -343,7 +347,7 @@ function processPasswordField(context: FieldContext): void {
if (context.fieldInfo.confirmationRequired) {
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>({
ariaLabel: context.fieldInfo.confirmationLabel,
inputType: 'password',
@@ -373,8 +377,8 @@ function processPasswordField(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 text = createLabel(context.view, { text: context.fieldInfo.defaultValue!, description: '', required: false, width: context.fieldInfo.inputWidth, fontStyle: context.fieldInfo.fontStyle });
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, links: context.fieldInfo.links });
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) => {
const tableWidth = 1126;
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();
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();

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 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 {
text: string;
url: string;
@@ -3527,7 +3531,7 @@ declare module 'azdata' {
* Create a button which can be included in a dialog
* @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
@@ -3691,8 +3695,15 @@ declare module 'azdata' {
* Raised when the button is clicked
*/
readonly onClick: vscode.Event<void>;
/**
* Position of the button on the dialog footer
*/
position?: DialogButtonPosition;
}
export type DialogButtonPosition = 'left' | 'right';
export interface WizardPageChangeInfo {
/**
* 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 {
let buttonElement = this.addFooterButton(button.label, onSelect);
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
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 {
let buttonElement = this.addFooterButton(button.label, onSelect);
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick);

View File

@@ -84,6 +84,7 @@ export class DialogButton implements azdata.window.Button {
private _enabled: boolean;
private _hidden: boolean;
private _focused: boolean;
private _position?: azdata.window.DialogButtonPosition;
private _onClick: Emitter<void> = new Emitter<void>();
public readonly onClick: Event<void> = this._onClick.event;
private _onUpdate: Emitter<void> = new Emitter<void>();
@@ -131,6 +132,15 @@ export class DialogButton implements azdata.window.Button {
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
*/

View File

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

View File

@@ -74,7 +74,7 @@ class ModelBuilderImpl implements azdata.ModelBuilder {
groupContainer(): azdata.GroupBuilder {
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);
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 {
withToolbarItems(components: azdata.ToolbarComponent[]): azdata.ContainerBuilder<azdata.ToolbarContainer, any, any> {
this._component.itemConfigs = components.map(item => {
@@ -1291,7 +1297,7 @@ class DropDownWrapper extends ComponentWrapper implements azdata.DropDownCompone
public get value(): string | azdata.CategoryValue {
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];
}
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 {
public onClosedEmitter = new Emitter<any>();

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import {
} from '@angular/core';
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';
@@ -37,7 +37,6 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
@Input() modelStore: IModelStore;
private _containerLayout: GroupLayout;
private _collapsed: boolean;
@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(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
this._collapsed = false;
this.collapsed = false;
}
ngOnInit(): void {
@@ -63,10 +62,18 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
public setLayout(layout: GroupLayout): void {
this._containerLayout = layout;
this._collapsed = !!layout.collapsed;
this.collapsed = !!layout.collapsed;
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 {
return this._containerLayout && this._containerLayout && this._containerLayout.header !== undefined;
}
@@ -88,12 +95,12 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
}
private getContainerDisplayStyle(): string {
return !this.isCollapsible() || !this._collapsed ? 'block' : 'none';
return !this.isCollapsible() || !this.collapsed ? 'block' : 'none';
}
private getHeaderClass(): string {
if (this.isCollapsible()) {
let modifier = this._collapsed ? 'collapsed' : 'expanded';
let modifier = this.collapsed ? 'collapsed' : 'expanded';
return `modelview-group-header-collapsible ${modifier}`;
} else {
return 'modelview-group-header';
@@ -102,7 +109,7 @@ export default class GroupContainer extends ContainerBase<GroupLayout> implement
private changeState(): void {
if (this.isCollapsible()) {
this._collapsed = !this._collapsed;
this.collapsed = !this.collapsed;
this._changeRef.detectChanges();
}
}

View File

@@ -41,9 +41,7 @@ export default () => `
<div class="section deploy" style="display:${showDeploySection() ? 'block' : 'none'}">
<h2 class="caption">${escape(localize('welcomePage.deploy', "Deploy"))}</h2>
<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.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>
<li><a href="command:azdata.resource.deploy">${escape(localize('welcomePage.DeploySQLServer', "Deploy SQL Server…"))}</a></li>
</ul>
</div>
<div class="section recent">

View File

@@ -105,10 +105,6 @@ suite('ExtHostModelViewDialog Tests', () => {
});
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
let label1 = 'button_1';
let label2 = 'button_2';
@@ -117,14 +113,16 @@ suite('ExtHostModelViewDialog Tests', () => {
let clickEvents = [];
button1.onClick(() => clickEvents.push(1));
button2.onClick(() => clickEvents.push(2));
const button1Handle = extHostModelViewDialog.getHandle(button1, false);
const button2Handle = extHostModelViewDialog.getHandle(button2, false);
extHostModelViewDialog.updateButton(button1);
extHostModelViewDialog.updateButton(button2);
// If the main thread sends some notifications that the buttons have been clicked
extHostModelViewDialog.$onButtonClick(handles[0]);
extHostModelViewDialog.$onButtonClick(handles[1]);
extHostModelViewDialog.$onButtonClick(handles[1]);
extHostModelViewDialog.$onButtonClick(handles[0]);
extHostModelViewDialog.$onButtonClick(button1Handle);
extHostModelViewDialog.$onButtonClick(button2Handle);
extHostModelViewDialog.$onButtonClick(button2Handle);
extHostModelViewDialog.$onButtonClick(button1Handle);
// Then the clicks should have been handled by the expected handlers
assert.deepEqual(clickEvents, [1, 2, 2, 1]);