Added Big Data Cluster Viewlet to ADS (#6204)

This commit is contained in:
Gene Lee
2019-07-03 21:52:19 -06:00
committed by GitHub
parent 1404133283
commit cf4dd48784
53 changed files with 3628 additions and 3774 deletions

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#68217a;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="47.45" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="422" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="420.77" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="419.53" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="418.29" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="417.05" width="7.65" height="0.43"/></clipPath></defs><title>AKS</title><path class="cls-10" d="M14.66,10.18,9.79,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M5.31,5.5v5.18L9,12.1V4.16Zm.38,4.8-.19-.06V6l.19-.06.19-.06.26-.06v4.67L6,10.37A1.45,1.45,0,0,1,5.7,10.3Zm1,.26L6.4,10.5V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13.38-.13v6.08L8.13,11C8.13,11.07,7.81,10.94,7.81,10.94Z"/><path class="cls-10" d="M26.11,10.18,21.25,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M16.77,5.5v5.18l3.65,1.41V4.16Zm.38,4.8L17,10.24V6l.19-.06.19-.06.26-.06v4.67l-.19-.06A1.45,1.45,0,0,1,17.15,10.3Zm1,.26-.26-.06V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13L20,5.06v6.08L19.58,11C19.58,11.07,19.26,10.94,19.26,10.94Z"/><path class="cls-10" d="M14.66,25.92,9.79,27.78V19.9l4.86,1.73Z"/><path class="cls-10" d="M5.31,21.25v5.18L9,27.84V19.9ZM5.7,26,5.5,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C6.91,26.37,6.66,26.3,6.66,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13.38-.13v6.08l-.38-.13C8.13,26.82,7.81,26.69,7.81,26.69Z"/><path class="cls-10" d="M26.11,25.92l-4.86,1.86V19.9l4.86,1.73Z"/><path class="cls-10" d="M16.77,21.25v5.18l3.65,1.41V19.9Zm.38,4.8L17,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C18.37,26.37,18.11,26.3,18.11,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13L20,20.8v6.08l-.38-.13C19.58,26.82,19.26,26.69,19.26,26.69Z"/><path class="cls-10" d="M20.74,18,15.87,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M11.39,13.31V18.5L15,19.9V12Zm.38,4.8L11.58,18V13.76l.19-.06.19-.06.19-.06v4.67L12,18.18C12,18.18,11.78,18.11,11.78,18.11Zm1,.32-.26-.06v-4.8l.26-.06.26-.06.26-.06v5.25L13,18.56C13,18.5,12.74,18.43,12.74,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/><path class="cls-10" d="M9.34,18,4.48,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M0,13.31V18.5L3.65,19.9V12Zm.38,4.8L.19,18V13.76l.19-.06H.64l.19-.06V18.3l-.19-.13Zm1,.32-.26-.06v-4.8l.26-.13.26-.06.26-.06v5.25L1.6,18.5Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13A2.77,2.77,0,0,0,2.5,18.82Z"/><path class="cls-10" d="M32,18,27.14,19.9V12L32,13.76Z"/><path class="cls-10" d="M22.66,13.31V18.5L26.3,19.9V12Zm.38,4.8L22.85,18V13.76L23,13.7l.19-.06.19-.06v4.67l-.19-.06C23.3,18.18,23,18.11,23,18.11Zm1,.32-.26-.06v-4.8L24,13.5l.26-.06.26-.06v5.25l-.26-.06C24.26,18.5,24,18.43,24,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#326ce5;}.cls-11{fill:#fff;stroke:#fff;stroke-width:0.25px;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="85.81" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="460.36" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="459.12" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="457.88" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="456.64" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="455.4" width="7.65" height="0.43"/></clipPath></defs><title>Kubernetes</title><g id="layer1"><g id="g3052"><path id="path3055" class="cls-10" d="M15.89,0a2.09,2.09,0,0,0-.82.21L3.95,5.69A2.17,2.17,0,0,0,2.8,7.17L.05,19.47a2.21,2.21,0,0,0,.29,1.67l.12.17,7.7,9.87A2.11,2.11,0,0,0,9.83,32H22.17a2.11,2.11,0,0,0,1.66-.82l7.7-9.87a2.21,2.21,0,0,0,.41-1.84L29.2,7.17A2.17,2.17,0,0,0,28,5.69L16.92.22A2.09,2.09,0,0,0,15.89,0Z"/><path id="path3059" class="cls-11" d="M16,4.19a.72.72,0,0,0-.67.76V5c0,.06,0,.13,0,.18a5.89,5.89,0,0,0,.09.65A6.6,6.6,0,0,1,15.5,7a.75.75,0,0,1-.22.35l0,.29a8.46,8.46,0,0,0-5.55,2.75l-.24-.18a.51.51,0,0,1-.4,0,6.29,6.29,0,0,1-.9-.84,5.65,5.65,0,0,0-.44-.48l-.15-.12a.78.78,0,0,0-.46-.18.63.63,0,0,0-.53.24.74.74,0,0,0,.16,1h0l.14.11a5.51,5.51,0,0,0,.55.33,6.22,6.22,0,0,1,1,.72.78.78,0,0,1,.13.4l.21.2a9.1,9.1,0,0,0-1.36,6.19l-.28.08a.94.94,0,0,1-.29.3,6,6,0,0,1-1.19.2,5.41,5.41,0,0,0-.64.05l-.18,0h0a.73.73,0,0,0-.56.84.7.7,0,0,0,.86.5h0l.17,0a5.46,5.46,0,0,0,.6-.24,6.08,6.08,0,0,1,1.16-.35.72.72,0,0,1,.38.14l.29-.05a8.88,8.88,0,0,0,3.84,4.94l-.12.3a.7.7,0,0,1,.06.39,6.77,6.77,0,0,1-.6,1.12,5.76,5.76,0,0,0-.36.55l-.09.18a.74.74,0,0,0,.28,1,.7.7,0,0,0,.92-.39h0l.08-.17a5.85,5.85,0,0,0,.19-.63,4.88,4.88,0,0,1,.52-1.23.53.53,0,0,1,.29-.14l.15-.28a8.36,8.36,0,0,0,5,.37,8.45,8.45,0,0,0,1.14-.35l.14.26a.52.52,0,0,1,.34.21A6.52,6.52,0,0,1,20,26.54a5.87,5.87,0,0,0,.19.63l.08.18a.7.7,0,0,0,.92.39.74.74,0,0,0,.28-1l-.09-.18a5.74,5.74,0,0,0-.36-.55,6.47,6.47,0,0,1-.59-1.09.55.55,0,0,1,.05-.4,2.4,2.4,0,0,1-.11-.28,8.89,8.89,0,0,0,3.84-5l.28.05a.52.52,0,0,1,.37-.14,6.08,6.08,0,0,1,1.16.35,5.47,5.47,0,0,0,.6.24l.17,0h0a.7.7,0,0,0,.86-.5.73.73,0,0,0-.56-.84l-.19,0a5.41,5.41,0,0,0-.64-.05,6,6,0,0,1-1.19-.2.76.76,0,0,1-.29-.3l-.27-.08a9.13,9.13,0,0,0-.14-3.2,9,9,0,0,0-1.25-3l.24-.22a.55.55,0,0,1,.13-.39,6.22,6.22,0,0,1,1-.72,5.53,5.53,0,0,0,.55-.33l.15-.12a.74.74,0,0,0,.16-1,.69.69,0,0,0-1-.06l-.15.12a5.67,5.67,0,0,0-.44.48,6.31,6.31,0,0,1-.9.84.72.72,0,0,1-.4,0l-.25.19A8.63,8.63,0,0,0,16.73,7.7c0-.09,0-.26,0-.31A.54.54,0,0,1,16.5,7a6.62,6.62,0,0,1,.08-1.24,5.89,5.89,0,0,0,.09-.65c0-.06,0-.14,0-.2A.72.72,0,0,0,16,4.19Zm-.83,5.32-.2,3.6h0a.6.6,0,0,1-.59.58.57.57,0,0,1-.35-.12h0l-2.86-2.09a6.79,6.79,0,0,1,4-2Zm1.67,0a6.84,6.84,0,0,1,4,2L18,13.58h0a.58.58,0,0,1-.81-.11.61.61,0,0,1-.13-.35h0Zm-6.72,3.33,2.61,2.41h0a.62.62,0,0,1,.07.83.59.59,0,0,1-.3.21h0l-3.35,1A7.24,7.24,0,0,1,10.11,12.84Zm11.75,0A7.28,7.28,0,0,1,22.72,15a7.36,7.36,0,0,1,.15,2.3l-3.37-1h0a.61.61,0,0,1-.23-1h0l2.6-2.4Zm-6.4,2.6h1.07l.67.86L17,17.36l-1,.48-1-.48-.24-1.07Zm3.43,2.94H19l3.47.6a7,7,0,0,1-2.78,3.59l-1.35-3.35h0a.61.61,0,0,1,.28-.78.57.57,0,0,1,.22-.06Zm-5.82,0a.59.59,0,0,1,.56.47.62.62,0,0,1,0,.37h0l-1.33,3.32A7.07,7.07,0,0,1,9.52,19l3.44-.6h.12ZM16,19.84a.57.57,0,0,1,.27.06.59.59,0,0,1,.26.26h0l1.69,3.16a6.86,6.86,0,0,1-.68.2,6.69,6.69,0,0,1-3.76-.2l1.69-3.15h0A.59.59,0,0,1,16,19.84Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,94 +1,111 @@
{
"name": "big-data-cluster",
"displayName": "SQL Server big data cluster",
"description": "SQL Server big data cluster",
"version": "0.0.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "*",
"azdata": ">=1.4.0"
},
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"configuration": {
"type": "object",
"title": "Kubernetes configuration",
"properties": {
"mssql-bdc": {
"type": "object",
"description": "Kubernetes configuration",
"properties": {
"mssql-bdc.kubectl-path": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.windows": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.mac": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.linux": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubeconfig": {
"type": "string",
"description": "File path to the kubeconfig file."
},
"mssql-bdc.knownKubeconfigs": {
"type": "array",
"description": "File paths to kubeconfig files from which you can select."
},
"mssql-bdc.outputFormat": {
"enum": [
"json",
"yaml"
],
"type": "string",
"description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)."
}
},
"default": {
"mssql-bdc.namespace": "",
"mssql-bdc.kubectl-path": "",
"mssql-bdc.kubeconfig": "",
"mssql-bdc.knownKubeconfigs": []
}
}
}
},
"commands": [
{
"command": "mssql.cluster.create",
"title": "Create SQL Server big data cluster",
"category": "SQL Server"
}
]
},
"dependencies": {
"vscode-nls": "^3.2.1",
"download": "^6.2.5",
"shelljs": "^0.8.3"
},
"devDependencies": {
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7"
}
"name": "big-data-cluster",
"displayName": "%text.sqlServerBigDataClusters%",
"description": "%description%",
"version": "0.0.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"engines": {
"vscode": "*",
"azdata": "*"
},
"activationEvents": [
"*"
],
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"main": "./out/extension",
"contributes": {
"dataExplorer": {
"sqlBigDataCluster": [
{
"id": "sqlBigDataCluster",
"name": "%text.sqlServerBigDataClusters%"
}
]
},
"menus": {
"commandPalette": [
{
"command": "bigDataClusters.command.addController",
"when": "false"
},
{
"command": "bigDataClusters.command.deleteController",
"when": "false"
},
{
"command": "bigDataClusters.command.refreshController",
"when": "false"
}
],
"view/title": [
{
"command": "bigDataClusters.command.addController",
"when": "view == sqlBigDataCluster",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "bigDataClusters.command.deleteController",
"when": "viewItem == bigDataClusters.itemType.controllerNode",
"group": "navigation@1"
},
{
"command": "bigDataClusters.command.refreshController",
"when": "viewItem == bigDataClusters.itemType.controllerNode",
"group": "navigation@1"
}
]
},
"configuration": {
"type": "object",
"title": "%text.sqlServerBigDataClusters%",
"properties": {
"clusterControllers.controllers": {
"type": "array"
}
}
},
"commands": [
{
"command": "bigDataClusters.command.addController",
"title": "%command.addController.title%",
"icon": {
"light": "resources/light/add.svg",
"dark": "resources/dark/add_inverse.svg"
}
},
{
"command": "bigDataClusters.command.deleteController",
"title": "%command.deleteController.title%",
"when": "viewItem == bigDataClusters.itemType.controllerNode"
},
{
"command": "bigDataClusters.command.refreshController",
"title": "%command.refreshController.title%",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh_inverse.svg"
}
}
]
},
"dependencies": {
"request": "^2.88.0",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^8.0.24",
"mocha": "^5.2.0",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscode": "^1.1.26"
}
}

View File

@@ -0,0 +1,7 @@
{
"description": "Support for managing SQL Server Big Data Clusters",
"text.sqlServerBigDataClusters": "SQL Server Big Data Clusters",
"command.addController.title": "Connect to Controller",
"command.deleteController.title" : "Delete",
"command.refreshController.title" : "Refresh"
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1 @@
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#fff;}</style></defs><title>folder_inverse_16x16</title><polygon class="cls-1" points="13.59 2.34 13.58 2.35 13.58 2.33 13.59 2.34"/><text></text><path class="cls-2" d="M16,14.13H0v-12a1,1,0,0,1,.08-.39,1,1,0,0,1,.53-.53A1,1,0,0,1,1,1.13H4.75a2.16,2.16,0,0,1,.61.07,2.26,2.26,0,0,1,.45.18,2.14,2.14,0,0,1,.36.24l.32.24a1.8,1.8,0,0,0,.34.18,1.12,1.12,0,0,0,.43.07H15a1,1,0,0,1,.39.08,1,1,0,0,1,.53.53,1,1,0,0,1,.08.39ZM1,2.13v1H4.75a1.36,1.36,0,0,0,.33,0A1,1,0,0,0,5.34,3l.23-.16.25-.21-.25-.21-.23-.16a1,1,0,0,0-.26-.1,1.36,1.36,0,0,0-.33,0Zm14,11v-10H7.25a1.12,1.12,0,0,0-.43.07,1.8,1.8,0,0,0-.34.18l-.32.24a2.14,2.14,0,0,1-.36.24,2.26,2.26,0,0,1-.45.18,2.16,2.16,0,0,1-.61.07H1v9Z"/></svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 16 16"
data-name="Layer 1"
id="Layer_1">
<metadata
id="metadata17">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>sql_bigdata_cluster</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
</defs>
<title
id="title6">sql_bigdata_cluster</title>
<path
style="fill:#ffffff;stroke-width:1.00282443"
id="path8"
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
class="cls-1" />
<polygon
style="fill:#ffffff"
transform="translate(0.075)"
id="polygon10"
points="13.57,2.35 13.58,2.36 13.57,2.37 "
class="cls-2" />
<path
style="fill:#ffffff"
id="path12"
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1 @@
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#DCB67A;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 16 16"
data-name="Layer 1"
id="Layer_1">
<metadata
id="metadata17">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>sql_bigdata_cluster</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
</defs>
<title
id="title6">sql_bigdata_cluster</title>
<path
style="fill:#212121;stroke-width:1.00282443"
id="path8"
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
class="cls-1" />
<polygon
style="fill:#231f20"
transform="translate(0.075)"
id="polygon10"
points="13.57,2.35 13.58,2.36 13.57,2.37 "
class="cls-2" />
<path
id="path12"
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export enum BdcItemType {
controllerRoot = 'bigDataClusters.itemType.controllerRootNode',
controller = 'bigDataClusters.itemType.controllerNode',
folder = 'bigDataClusters.itemType.folderNode',
sqlMaster = 'bigDataClusters.itemType.sqlMasterNode',
EndPoint = 'bigDataClusters.itemType.endPointNode',
addController = 'bigDataClusters.itemType.addControllerNode'
}
export class IconPath {
private static extensionContext: vscode.ExtensionContext;
public static controllerNode: { dark: string, light: string };
public static folderNode: { dark: string, light: string };
public static sqlMasterNode: { dark: string, light: string };
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
IconPath.extensionContext = extensionContext;
IconPath.controllerNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
};
IconPath.folderNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/folder.svg')
};
IconPath.sqlMasterNode = {
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/sql_bigdata_cluster_inverse.svg'),
light: IconPath.extensionContext.asAbsolutePath('resources/light/sql_bigdata_cluster.svg')
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EndpointRouterApi } from './apiGenerated';
export async function getEndPoints(
url: string, username: string, password: string, ignoreSslVerification?: boolean
): Promise<IEndPointsResponse> {
if (!url || !username || !password) {
return undefined;
}
url = adjustUrl(url);
let endPointApi = new EndpointRouterApi(username, password, url);
endPointApi.ignoreSslVerification = !!ignoreSslVerification;
let controllerResponse: IEndPointsResponse = undefined;
let controllerError: IControllerError = undefined;
let request = <IEndPointsRequest>{
url: url,
username: username,
password: password,
method: 'endPointsGet'
};
try {
let result = await endPointApi.endpointsGet();
controllerResponse = <IEndPointsResponse>{
response: result.response as IHttpResponse,
endPoints: result.body as IEndPoint[],
request
};
return controllerResponse;
} catch (error) {
if ('response' in error) {
let err: IEndPointsResponse = error as IEndPointsResponse;
let errCode = `${err.response.statusCode || ''}`;
let errMessage = err.response.statusMessage;
let errUrl = err.response.url;
controllerError = <IControllerError>{
address: errUrl,
code: errCode,
errno: errCode,
message: errMessage,
name: undefined
};
} else {
controllerError = error as IControllerError;
}
throw Object.assign(controllerError, { request }) as IControllerError;
}
}
/**
* Fixes missing protocol and wrong character for port entered by user
*/
function adjustUrl(url: string): string {
if (!url) {
return undefined;
}
url = url.trim().replace(/ /g, '').replace(/,(\d+)$/, ':$1');
if (!url.includes('://')) {
url = `https://${url}`;
}
return url;
}
export interface IEndPointsRequest {
url: string;
username: string;
password?: string;
method?: string;
}
export interface IEndPointsResponse {
request?: IEndPointsRequest;
response: IHttpResponse;
endPoints: IEndPoint[];
}
export interface IHttpResponse {
method?: string;
url?: string;
statusCode?: number;
statusMessage?: string;
}
export interface IEndPoint {
name?: string;
description?: string;
endpoint?: string;
ip?: string;
port?: number;
}
export interface IControllerError extends Error {
code?: string;
errno?: string;
message: string;
request?: any;
}

View File

@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
import { TreeNode } from '../tree/treeNode';
import { showErrorMessage } from '../utils';
const localize = nls.loadMessageBundle();
export class AddControllerDialogModel {
constructor(
public treeDataProvider: ControllerTreeDataProvider,
public node?: TreeNode,
public prefilledUrl?: string,
public prefilledUsername?: string,
public prefilledPassword?: string,
public prefilledRememberPassword?: boolean
) {
this.prefilledUrl = prefilledUrl || (node && node['url']);
this.prefilledUsername = prefilledUsername || (node && node['username']);
this.prefilledPassword = prefilledPassword || (node && node['password']);
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
}
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
let response = await getEndPoints(url, username, password, true);
if (response && response.request) {
let masterInstance: IEndPoint = undefined;
if (response.endPoints) {
masterInstance = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
}
this.treeDataProvider.addController(response.request.url, response.request.username,
response.request.password, rememberPassword, masterInstance);
await this.treeDataProvider.saveControllers();
}
}
public async onError(error: IControllerError): Promise<void> {
// implement
}
public async onCancel(): Promise<void> {
if (this.node) {
this.node.refresh();
}
}
}
export class AddControllerDialog {
private dialog: azdata.window.Dialog;
private uiModelBuilder: azdata.ModelBuilder;
private urlInputBox: azdata.InputBoxComponent;
private usernameInputBox: azdata.InputBoxComponent;
private passwordInputBox: azdata.InputBoxComponent;
private rememberPwCheckBox: azdata.CheckBoxComponent;
constructor(private model: AddControllerDialogModel) {
}
public showDialog(): void {
this.createDialog();
azdata.window.openDialog(this.dialog);
}
private createDialog(): void {
this.dialog = azdata.window.createModelViewDialog(localize('textAddNewController', 'Add New Controller'));
this.dialog.registerContent(async view => {
this.uiModelBuilder = view.modelBuilder;
this.urlInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textUrlLower', 'url'),
value: this.model.prefilledUrl
}).component();
this.usernameInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textUsernameLower', 'username'),
value: this.model.prefilledUsername
}).component();
this.passwordInputBox = this.uiModelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
placeHolder: localize('textPasswordLower', 'password'),
inputType: 'password',
value: this.model.prefilledPassword
})
.component();
this.rememberPwCheckBox = this.uiModelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
label: localize('textRememberPassword', 'Remember Password'),
checked: this.model.prefilledRememberPassword
}).component();
let formModel = this.uiModelBuilder.formContainer()
.withFormItems([{
components: [{
component: this.urlInputBox,
title: localize('textUrlCapital', 'URL'),
required: true
}, {
component: this.usernameInputBox,
title: localize('textUsernameCapital', 'Username'),
required: true
}, {
component: this.passwordInputBox,
title: localize('textPasswordCapital', 'Password'),
required: true
}, {
component: this.rememberPwCheckBox,
title: ''
}
],
title: ''
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
this.dialog.registerCloseValidator(async () => await this.validate());
this.dialog.cancelButton.onClick(async () => await this.cancel());
this.dialog.okButton.label = localize('textAdd', 'Add');
this.dialog.cancelButton.label = localize('textCancel', 'Cancel');
}
private async validate(): Promise<boolean> {
let url = this.urlInputBox && this.urlInputBox.value;
let username = this.usernameInputBox && this.usernameInputBox.value;
let password = this.passwordInputBox && this.passwordInputBox.value;
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
try {
await this.model.onComplete(url, username, password, rememberPassword);
return true;
} catch (error) {
showErrorMessage(error);
if (this.model && this.model.onError) {
await this.model.onError(error as IControllerError);
}
return false;
}
}
private async cancel(): Promise<void> {
if (this.model && this.model.onCancel) {
await this.model.onCancel();
}
}
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { TreeNode } from './treeNode';
import { BdcItemType } from '../constants';
const localize = nls.loadMessageBundle();
export class AddControllerNode extends TreeNode {
private readonly nodeType: string;
constructor() {
super(localize('textBigDataClusterControllerWithDots', 'Add Big Data Cluster Controller...'));
this.nodeType = BdcItemType.addController;
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): vscode.TreeItem {
let item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.None);
item.command = {
title: localize('textConnectToController', 'Connect to Controller'),
command: 'bigDataClusters.command.addController',
arguments: [this]
};
item.contextValue = this.nodeType;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this.nodeType,
iconType: this.nodeType,
nodeSubType: undefined
};
}
}

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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeNode } from './treeNode';
export interface IControllerTreeChangeHandler {
notifyNodeChanged(node?: TreeNode): void;
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { TreeNode } from './treeNode';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { AddControllerNode } from './addControllerTreeNode';
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
import { IEndPoint } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const ConfigNamespace = 'clusterControllers';
const CredentialNamespace = 'clusterControllerCredentials';
export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeNode>, IControllerTreeChangeHandler {
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode> = new vscode.EventEmitter<TreeNode>();
public readonly onDidChangeTreeData: vscode.Event<TreeNode> = this._onDidChangeTreeData.event;
private root: ControllerRootNode;
private credentialProvider: azdata.CredentialProvider;
constructor() {
this.root = new ControllerRootNode(this);
this.loadSavedControllers();
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren();
}
if (this.root.hasChildren) {
return this.root.getChildren();
} else {
return [new AddControllerNode()];
}
}
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element.getTreeItem();
}
public addController(
url: string,
username: string,
password: string,
rememberPassword: boolean,
masterInstance?: IEndPoint
): void {
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
this.notifyNodeChanged();
}
public deleteController(url: string, username: string): ControllerNode {
let deleted = this.root.deleteControllerNode(url, username);
if (deleted) {
this.notifyNodeChanged();
}
return deleted;
}
public notifyNodeChanged(node?: TreeNode): void {
this._onDidChangeTreeData.fire(node);
}
public async loadSavedControllers(): Promise<void> {
let config = vscode.workspace.getConfiguration(ConfigNamespace);
if (config && config.controllers) {
let controllers = config.controllers;
this.root.clearChildren();
for (let c of controllers) {
let password = undefined;
if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username);
}
this.root.addChild(new ControllerNode(
c.url, c.username, password, c.rememberPassword,
undefined, this.root, this, undefined
));
}
this.notifyNodeChanged();
}
}
public async saveControllers(): Promise<void> {
let controllers = this.root.children.map(e => {
let controller = e as ControllerNode;
return {
url: controller.url,
username: controller.username,
password: controller.password,
rememberPassword: !!controller.rememberPassword
};
});
let controllersWithoutPassword = controllers.map(e => {
return {
url: e.url,
username: e.username,
rememberPassword: e.rememberPassword
};
});
try {
await vscode.workspace.getConfiguration(ConfigNamespace).update('controllers', controllersWithoutPassword, true);
} catch (error) {
showErrorMessage(error);
}
for (let e of controllers) {
if (e.rememberPassword) {
await this.savePassword(e.url, e.username, e.password);
} else {
await this.deletePassword(e.url, e.username);
}
}
}
private async savePassword(url: string, username: string, password: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.saveCredential(id, password);
return result;
}
private async deletePassword(url: string, username: string): Promise<boolean> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let result = await provider.deleteCredential(id);
return result;
}
private async getPassword(url: string, username: string): Promise<string> {
let provider = await this.getCredentialProvider();
let id = this.createId(url, username);
let credential = await provider.readCredential(id);
return credential ? credential.password : undefined;
}
private async getCredentialProvider(): Promise<azdata.CredentialProvider> {
if (!this.credentialProvider) {
this.credentialProvider = await azdata.credentials.getProvider(CredentialNamespace);
}
return this.credentialProvider;
}
private createId(url: string, username: string): string {
return `${url}::${username}`;
}
}

View File

@@ -0,0 +1,392 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
import { TreeNode } from './treeNode';
import { IconPath, BdcItemType } from '../constants';
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
import { showErrorMessage } from '../utils';
const localize = nls.loadMessageBundle();
export abstract class ControllerTreeNode extends TreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
private _treeChangeHandler: IControllerTreeChangeHandler,
private _description?: string,
private _nodeType?: string,
private _iconPath?: { dark: string, light: string }
) {
super(label, parent);
this._description = this._description || this.label;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
return this.children as ControllerTreeNode[];
}
public refresh(): void {
super.refresh();
this.treeChangeHandler.notifyNodeChanged(this);
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = {};
item.id = this.id;
item.label = this.label;
item.collapsibleState = this.isLeaf ?
vscode.TreeItemCollapsibleState.None :
vscode.TreeItemCollapsibleState.Collapsed;
item.iconPath = this._iconPath;
item.contextValue = this._nodeType;
item.tooltip = this._description;
item.iconPath = this._iconPath;
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.label,
isLeaf: this.isLeaf,
errorMessage: undefined,
metadata: undefined,
nodePath: this.nodePath,
nodeStatus: undefined,
nodeType: this._nodeType,
iconType: this._nodeType,
nodeSubType: undefined
};
}
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
}
public get nodeType(): string {
return this._nodeType;
}
public set nodeType(nodeType: string) {
this._nodeType = nodeType;
}
public set iconPath(iconPath: { dark: string, light: string }) {
this._iconPath = iconPath;
}
public get iconPath(): { dark: string, light: string } {
return this._iconPath;
}
public set treeChangeHandler(treeChangeHandler: IControllerTreeChangeHandler) {
this._treeChangeHandler = treeChangeHandler;
}
public get treeChangeHandler(): IControllerTreeChangeHandler {
return this._treeChangeHandler;
}
}
export class ControllerRootNode extends ControllerTreeNode {
private _masterNodeFactory: SqlMasterNodeFactory;
constructor(treeChangeHandler: IControllerTreeChangeHandler) {
super('root', undefined, treeChangeHandler, undefined, BdcItemType.controllerRoot);
this._masterNodeFactory = new SqlMasterNodeFactory();
}
public async getChildren(): Promise<ControllerNode[]> {
return this.children as ControllerNode[];
}
public addControllerNode(url: string, username: string, password: string, rememberPassword: boolean, masterInstance?: IEndPoint): void {
let controllerNode = this.getExistingControllerNode(url, username);
if (controllerNode) {
controllerNode.password = password;
controllerNode.rememberPassword = rememberPassword;
controllerNode.clearChildren();
} else {
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
this.addChild(controllerNode);
}
if (masterInstance) {
controllerNode.addSqlMasterNode(masterInstance.endpoint, masterInstance.description);
}
}
public deleteControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
let index = nodes.findIndex(e => e.url === url && e.username === username);
let deleted = undefined;
if (index >= 0) {
deleted = nodes.splice(index, 1);
}
return deleted;
}
private getExistingControllerNode(url: string, username: string): ControllerNode {
if (!url || !username) {
return undefined;
}
let nodes = this.children as ControllerNode[];
return nodes.find(e => e.url === url && e.username === username);
}
public get sqlMasterNodeFactory(): SqlMasterNodeFactory {
return this._masterNodeFactory;
}
}
export class ControllerNode extends ControllerTreeNode {
constructor(
private _url: string,
private _username: string,
private _password: string,
private _rememberPassword: boolean,
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPath.controllerNode);
this.label = label;
this.description = description;
}
public async getChildren(): Promise<ControllerTreeNode[]> {
if (this.children && this.children.length > 0) {
this.clearChildren();
}
if (!this._password) {
vscode.commands.executeCommand('bigDataClusters.command.addController', this);
return this.children as ControllerTreeNode[];
}
try {
let response = await getEndPoints(this._url, this._username, this._password, true);
if (response && response.endPoints) {
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
this.addSqlMasterNode(master.endpoint, master.description);
}
return this.children as ControllerTreeNode[];
} catch (error) {
showErrorMessage(error);
return this.children as ControllerTreeNode[];
}
}
private static toIpAndPort(url: string): string {
if (!url) {
return;
}
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '').replace(/:(\d+)$/, ',$1');
}
public addSqlMasterNode(endPointAddress: string, description: string): void {
let epFolder = this.getEndPointFolderNode();
let node = (this.root as ControllerRootNode).sqlMasterNodeFactory
.getSqlMasterNode(endPointAddress, epFolder, undefined, this.treeChangeHandler, description);
epFolder.addChild(node);
}
private getEndPointFolderNode(): FolderNode {
let label = localize('textSqlServers', 'SQL Servers');
let epFolderNode = this.children.find(e => e instanceof FolderNode && e.label === label);
if (!epFolderNode) {
epFolderNode = new FolderNode(label, this, this.treeChangeHandler);
this.addChild(epFolderNode);
}
return epFolderNode as FolderNode;
}
public getTreeItem(): vscode.TreeItem {
let item: vscode.TreeItem = super.getTreeItem();
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
return item;
}
public get url() {
return this._url;
}
public set url(url: string) {
this._url = url;
}
public get username() {
return this._username;
}
public set username(username: string) {
this._username = username;
}
public get password() {
return this._password;
}
public set password(pw: string) {
this._password = pw;
}
public get rememberPassword() {
return this._rememberPassword;
}
public set rememberPassword(rememberPassword: boolean) {
this._rememberPassword = rememberPassword;
}
public set label(label: string) {
super.label = label || `controller: ${ControllerNode.toIpAndPort(this._url)} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class FolderNode extends ControllerTreeNode {
constructor(
label: string,
parent: ControllerTreeNode,
treeChangeHandler: IControllerTreeChangeHandler
) {
super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPath.folderNode);
}
}
export class SqlMasterNode extends ControllerTreeNode {
private static readonly _role: string = 'sql-server-master';
private _username: string;
private _password: string;
constructor(
private _endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string,
) {
super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPath.sqlMasterNode);
this._username = 'sa';
this.label = label;
this.description = description;
}
private getControllerPassword(): string {
if (!this._password) {
let current: TreeNode = this;
while (current && !(current instanceof ControllerNode)) {
current = current.parent;
}
this._password = current && current instanceof ControllerNode ? current.password : undefined;
}
return this._password;
}
public getTreeItem(): vscode.TreeItem {
let item = super.getTreeItem();
let connectionProfile: azdata.IConnectionProfile = {
id: this.id,
connectionName: this.id,
serverName: this._endPointAddress,
databaseName: '',
userName: this._username,
password: this.getControllerPassword(),
authenticationType: 'SqlLogin',
savePassword: false,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: false,
options: {}
};
return Object.assign(item, { payload: connectionProfile, childProvider: 'MSSQL' });
}
public get role() {
return SqlMasterNode._role;
}
public get endPointAddress() {
return this._endPointAddress;
}
public set endPointAddress(endPointAddress: string) {
this._endPointAddress = endPointAddress;
}
public set label(label: string) {
super.label = label || `master: ${this._endPointAddress} (${this._username})`;
}
public get label(): string {
return super.label;
}
public set description(description: string) {
super.description = description || super.label;
}
public get description(): string {
return super.description;
}
}
export class SqlMasterNodeFactory {
private registry: {} = {};
public getSqlMasterNode(
endPointAddress: string,
parent: ControllerTreeNode,
label: string,
treeChangeHandler: IControllerTreeChangeHandler,
description?: string
): SqlMasterNode {
let id = this.createRegistryId(endPointAddress, 'sa');
if (!this.registry[id]) {
this.registry[id] = new SqlMasterNode(endPointAddress, parent, label, treeChangeHandler, description);
} else {
let node = this.registry[id] as SqlMasterNode;
node.parent = parent;
node.label = label;
node.treeChangeHandler = treeChangeHandler;
description = description;
}
return this.registry[id] as SqlMasterNode;
}
private createRegistryId(endPointAddress: string, username: string): string {
return `${endPointAddress}::${username}`;
}
}

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { generateGuid } from '../utils';
export abstract class TreeNode {
private _id: string;
private _children: TreeNode[];
private _isLeaf: boolean;
constructor(private _label: string, private _parent?: TreeNode) {
this.resetId();
}
public resetId(): void {
this._id = (this._label || '_') + `::${generateGuid()}`;
}
public get id(): string {
return this._id;
}
public set label(label: string) {
if (!this._label) {
this._label = label;
this.resetId();
} else {
this._label = label;
}
}
public get label(): string {
return this._label;
}
public set parent(parent: TreeNode) {
this._parent = parent;
}
public get parent(): TreeNode {
return this._parent;
}
public get children(): TreeNode[] {
if (!this._children) {
this._children = [];
}
return this._children;
}
public get hasChildren(): boolean {
return this.children && this.children.length > 0;
}
public set isLeaf(isLeaf: boolean) {
this._isLeaf = isLeaf;
}
public get isLeaf(): boolean {
return this._isLeaf;
}
public get root(): TreeNode {
return TreeNode.getRoot(this);
}
public equals(node: TreeNode): boolean {
if (!node) {
return undefined;
}
return this.nodePath === node.nodePath;
}
public refresh(): void {
this.resetId();
}
public static getRoot(node: TreeNode): TreeNode {
if (!node) {
return undefined;
}
let current: TreeNode = node;
while (current.parent) {
current = current.parent;
}
return current;
}
public get nodePath(): string {
return TreeNode.getNodePath(this);
}
public static getNodePath(node: TreeNode): string {
if (!node) {
return undefined;
}
let current: TreeNode = node;
let path = current._id;
while (current.parent) {
current = current.parent;
path = `${current._id}/${path}`;
}
return path;
}
public async findNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(this, condition, expandIfNeeded);
}
public static async findNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode = undefined;
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result = current;
break;
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async filterNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
return TreeNode.filterNode(this, condition, expandIfNeeded);
}
public static async filterNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
if (!node || !condition) {
return undefined;
}
let result: TreeNode[] = [];
let nodesToCheck: TreeNode[] = [node];
while (nodesToCheck.length > 0) {
let current = nodesToCheck.shift();
if (condition(current)) {
result.push(current);
}
if (current.hasChildren) {
nodesToCheck = nodesToCheck.concat(current.children);
} else if (expandIfNeeded) {
let children = await current.getChildren();
if (children && children.length > 0) {
nodesToCheck = nodesToCheck.concat(children);
}
}
}
return result;
}
public async findNodeByPath(path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNodeByPath(this, path, expandIfNeeded);
}
public static async findNodeByPath(node: TreeNode, path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
return TreeNode.findNode(node, node => {
return node.nodePath && (node.nodePath === path || node.nodePath.startsWith(path));
}, expandIfNeeded);
}
public addChild(node: TreeNode): void {
if (!this._children) {
this._children = [];
}
this._children.push(node);
}
public clearChildren(): void {
if (this._children) {
this._children = [];
}
}
public abstract async getChildren(): Promise<TreeNode[]>;
public abstract getTreeItem(): vscode.TreeItem;
public abstract getNodeInfo(): azdata.NodeInfo;
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export function generateGuid(): string {
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
let oct: string = '';
let tmp: number;
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
}
export function showErrorMessage(error: any): void {
if (error) {
let text: string = undefined;
if (typeof error === 'string') {
text = error as string;
} else if (typeof error === 'object' && error !== null) {
let message = error.message;
let code = error.code || error.errno;
text = (code ? `${code} ` : '') + message;
} else {
text = `${error}`;
}
vscode.window.showErrorMessage(text);
}
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Host } from '../kubectl/host';
import { Shell, Platform } from '../utility/shell';
const EXTENSION_CONFIG_KEY = 'mssql-bdc';
const KUBECONFIG_PATH_KEY = 'mssql-bdc.kubeconfig';
const KNOWN_KUBECONFIGS_KEY = 'mssql-bdc.knownKubeconfigs';
export async function addPathToConfig(configKey: string, value: string): Promise<void> {
await setConfigValue(configKey, value);
}
async function setConfigValue(configKey: string, value: any): Promise<void> {
await atAllConfigScopes(addValueToConfigAtScope, configKey, value);
}
async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
if (!createIfNotExist) {
if (!valueAtScope || !(valueAtScope[configKey])) {
return;
}
}
let newValue: any = {};
if (valueAtScope) {
newValue = Object.assign({}, valueAtScope);
}
newValue[configKey] = value;
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
}
async function addValueToConfigArray(configKey: string, value: string): Promise<void> {
await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value);
}
async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
if (!createIfNotExist) {
if (!valueAtScope || !(valueAtScope[configKey])) {
return;
}
}
let newValue: any = {};
if (valueAtScope) {
newValue = Object.assign({}, valueAtScope);
}
const arrayEntry: string[] = newValue[configKey] || [];
arrayEntry.push(value);
newValue[configKey] = arrayEntry;
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
}
type ConfigUpdater<T> = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise<void>;
async function atAllConfigScopes<T>(fn: ConfigUpdater<T>, configKey: string, value: T): Promise<void> {
const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY)!;
await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true);
await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false);
await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false);
}
// Functions for working with the list of known kubeconfigs
export function getKnownKubeconfigs(): string[] {
const kkcConfig = vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KNOWN_KUBECONFIGS_KEY];
if (!kkcConfig || !kkcConfig.length) {
return [];
}
return kkcConfig as string[];
}
export async function addKnownKubeconfig(kubeconfigPath: string) {
await addValueToConfigArray(KNOWN_KUBECONFIGS_KEY, kubeconfigPath);
}
// Functions for working with the active kubeconfig setting
export async function setActiveKubeconfig(kubeconfig: string): Promise<void> {
await addPathToConfig(KUBECONFIG_PATH_KEY, kubeconfig);
}
export function getActiveKubeconfig(): string {
return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY];
}
// Functions for working with tool paths
export function getToolPath(host: Host, shell: Shell, tool: string): string | undefined {
const baseKey = toolPathBaseKey(tool);
return getPathSetting(host, shell, baseKey);
}
function getPathSetting(host: Host, shell: Shell, baseKey: string): string | undefined {
const os = shell.platform();
const osOverridePath = host.getConfiguration(EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)];
return osOverridePath || host.getConfiguration(EXTENSION_CONFIG_KEY)[baseKey];
}
export function toolPathBaseKey(tool: string): string {
return `mssql-bdc.${tool}-path`;
}
function osOverrideKey(os: Platform, baseKey: string): string {
const osKey = osKeyString(os);
return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key
}
function osKeyString(os: Platform): string | null {
switch (os) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'mac';
case Platform.Linux: return 'linux';
default: return null;
}
}

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ClusterInfo } from '../interfaces';
export interface IKubeConfigParser {
parse(configPath: string): ClusterInfo[];
}
export class TestKubeConfigParser implements IKubeConfigParser {
parse(configPath: string): ClusterInfo[] {
let clusters = [];
for (let i = 0; i < 18; i++) {
let name;
if (i % 2 === 0) {
name = `kubernetes cluster ${i}`;
}
else {
name = 'cluster dev ' + i;
}
clusters.push(
{
displayName: name,
name: `kub-dev-xxxx-cluster-${i}`,
user: 'root'
}
);
}
return clusters;
}
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ControllerTreeDataProvider } from './bigDataCluster/tree/controllerTreeDataProvider';
import { IconPath } from './bigDataCluster/constants';
import { TreeNode } from './bigDataCluster/tree/treeNode';
import { AddControllerDialogModel, AddControllerDialog } from './bigDataCluster/dialog/addControllerDialog';
import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
const localize = nls.loadMessageBundle();
export function activate(extensionContext: vscode.ExtensionContext) {
IconPath.setExtensionContext(extensionContext);
let treeDataProvider = new ControllerTreeDataProvider();
registerTreeDataProvider(treeDataProvider);
registerCommands(treeDataProvider);
}
export function deactivate() {
}
function registerTreeDataProvider(treeDataProvider: ControllerTreeDataProvider): void {
vscode.window.registerTreeDataProvider('sqlBigDataCluster', treeDataProvider);
}
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
vscode.commands.registerCommand('bigDataClusters.command.addController', (node?: TreeNode) => {
addBdcController(treeDataProvider, node);
});
vscode.commands.registerCommand('bigDataClusters.command.deleteController', (node: TreeNode) => {
deleteBdcController(treeDataProvider, node);
});
vscode.commands.registerCommand('bigDataClusters.command.refreshController', (node: TreeNode) => {
if (!node) {
return;
}
treeDataProvider.notifyNodeChanged(node);
});
}
function addBdcController(treeDataProvider: ControllerTreeDataProvider, node?: TreeNode): void {
let model = new AddControllerDialogModel(treeDataProvider, node);
let dialog = new AddControllerDialog(model);
dialog.showDialog();
}
async function deleteBdcController(treeDataProvider: ControllerTreeDataProvider, node: TreeNode): Promise<boolean> {
if (!node && !(node instanceof ControllerNode)) {
return;
}
let controllerNode = node as ControllerNode;
let choices: { [id: string]: boolean } = {};
choices[localize('textYes', 'Yes')] = true;
choices[localize('textNo', 'No')] = false;
let options = {
ignoreFocusOut: false,
placeHolder: localize('textConfirmDeleteController', 'Are you sure you want to delete \'{0}\'?', controllerNode.label)
};
let result = await vscode.window.showQuickPick(Object.keys(choices), options);
let remove: boolean = !!(result && choices[result]);
if (remove) {
deleteControllerInternal(treeDataProvider, controllerNode);
}
return remove;
}
function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): void {
let deleted = treeDataProvider.deleteController(controllerNode.url, controllerNode.username);
if (deleted) {
treeDataProvider.saveControllers();
}
}

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as stream from 'stream';
import * as tmp from 'tmp';
import { succeeded, Errorable } from '../interfaces';
type DownloadFunc =
(url: string, destination?: string, options?: any)
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download
let download: DownloadFunc;
function ensureDownloadFunc() {
if (!download) {
const home = process.env['HOME'];
download = require('download');
if (home) {
process.env['HOME'] = home;
}
}
}
export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
const tempFileObj = tmp.fileSync({ prefix: 'mssql-bdc-autoinstall-' });
const downloadResult = await to(sourceUrl, tempFileObj.name);
if (succeeded(downloadResult)) {
return { succeeded: true, result: tempFileObj.name };
}
return { succeeded: false, error: downloadResult.error };
}
export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<null>> {
ensureDownloadFunc();
try {
await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) });
return { succeeded: true, result: null };
} catch (e) {
return { succeeded: false, error: [e.message] };
}
}

View File

@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as download from './download';
import * as fs from 'fs';
import mkdirp = require('mkdirp');
import * as path from 'path';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Shell, Platform } from '../utility/shell';
import { Errorable, failed } from '../interfaces';
import { addPathToConfig, toolPathBaseKey } from '../config/config';
export async function installKubectl(shell: Shell): Promise<Errorable<null>> {
const tool = 'kubectl';
const binFile = (shell.isUnix()) ? 'kubectl' : 'kubectl.exe';
const os = platformUrlString(shell.platform());
const version = await getStableKubectlVersion();
if (failed(version)) {
return { succeeded: false, error: version.error };
}
const installFolder = getInstallFolder(shell, tool);
mkdirp.sync(installFolder);
const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${version.result.trim()}/bin/${os}/amd64/${binFile}`;
const downloadFile = path.join(installFolder, binFile);
const downloadResult = await download.to(kubectlUrl, downloadFile);
if (failed(downloadResult)) {
return { succeeded: false, error: [localize('downloadKubectlFailed', 'Failed to download kubectl: {0}', downloadResult.error[0])] };
}
if (shell.isUnix()) {
fs.chmodSync(downloadFile, '0777');
}
await addPathToConfig(toolPathBaseKey(tool), downloadFile);
return { succeeded: true, result: null };
}
async function getStableKubectlVersion(): Promise<Errorable<string>> {
const downloadResult = await download.toTempFile('https://storage.googleapis.com/kubernetes-release/release/stable.txt');
if (failed(downloadResult)) {
return { succeeded: false, error: [localize('kubectlVersionCheckFailed', 'Failed to establish kubectl stable version: {0}', downloadResult.error[0])] };
}
const version = fs.readFileSync(downloadResult.result, 'utf-8');
fs.unlinkSync(downloadResult.result);
return { succeeded: true, result: version };
}
export function getInstallFolder(shell: Shell, tool: string): string {
return path.join(shell.home(), `.mssql-bdc/tools/${tool}`);
}
function platformUrlString(platform: Platform, supported?: Platform[]): string | null {
if (supported && supported.indexOf(platform) < 0) {
return null;
}
switch (platform) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'darwin';
case Platform.Linux: return 'linux';
default: return null;
}
}

View File

@@ -1,124 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface ClusterInfo {
name: string;
displayName: string;
user: string;
}
export enum TargetClusterType {
ExistingKubernetesCluster,
NewAksCluster
}
export interface Succeeded<T> {
readonly succeeded: true;
readonly result: T;
}
export interface Failed {
readonly succeeded: false;
readonly error: string[];
}
export type Errorable<T> = Succeeded<T> | Failed;
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
return e.succeeded;
}
export function failed<T>(e: Errorable<T>): e is Failed {
return !e.succeeded;
}
export interface ClusterPorts {
sql: string;
knox: string;
controller: string;
proxy: string;
grafana: string;
kibana: string;
}
export interface ContainerRegistryInfo {
registry: string;
repository: string;
imageTag: string;
}
export interface TargetClusterTypeInfo {
enabled: boolean;
type: TargetClusterType;
name: string;
fullName: string;
description: string;
iconPath: {
dark: string,
light: string
};
}
export interface ToolInfo {
name: string;
description: string;
version: string;
status: ToolInstallationStatus;
}
export enum ToolInstallationStatus {
Installed,
NotInstalled,
Installing,
FailedToInstall
}
export enum ClusterType {
Unknown = 0,
AKS,
Minikube,
Kubernetes,
Other
}
export interface ClusterProfile {
name: string;
sqlServerMasterConfiguration: SQLServerMasterConfiguration;
computePoolConfiguration: PoolConfiguration;
dataPoolConfiguration: PoolConfiguration;
storagePoolConfiguration: PoolConfiguration;
sparkPoolConfiguration: PoolConfiguration;
}
export interface PoolConfiguration {
type: ClusterPoolType;
scale: number;
maxScale?: number;
hardwareLabel?: string;
}
export interface SQLServerMasterConfiguration extends PoolConfiguration {
engineOnly: boolean;
}
export enum ClusterPoolType {
SQL,
Compute,
Data,
Storage,
Spark
}
export interface ClusterResourceSummary {
hardwareLabels: HardwareLabel[];
}
export interface HardwareLabel {
name: string;
totalNodes: number;
totalCores: number;
totalMemoryInGB: number;
totalDisks: number;
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Shell } from '../utility/shell';
import { Host } from './host';
import { FS } from '../utility/fs';
export interface BinCheckContext {
readonly host: Host;
readonly fs: FS;
readonly shell: Shell;
readonly installDependenciesCallback: () => void;
binFound: boolean;
binPath: string;
}
interface FindBinaryResult {
err: number | null;
output: string;
}
async function findBinary(shell: Shell, binName: string): Promise<FindBinaryResult> {
let cmd = `which ${binName}`;
if (shell.isWindows()) {
cmd = `where.exe ${binName}.exe`;
}
const opts = {
async: true,
env: {
HOME: process.env.HOME,
PATH: process.env.PATH
}
};
const execResult = await shell.execCore(cmd, opts);
if (execResult.code) {
return { err: execResult.code, output: execResult.stderr };
}
return { err: null, output: execResult.stdout };
}
export function execPath(shell: Shell, basePath: string): string {
let bin = basePath;
if (shell.isWindows() && bin && !(bin.endsWith('.exe'))) {
bin = bin + '.exe';
}
return bin;
}
type CheckPresentFailureReason = 'inferFailed' | 'configuredFileMissing';
const installDependenciesAction = localize('installDependenciesAction', 'Install dependencies');
const learnMoreAction = localize('learnMoreAction', 'Learn more');
function alertNoBin(host: Host, binName: string, failureReason: CheckPresentFailureReason, message: string, installDependencies: () => void): void {
switch (failureReason) {
case 'inferFailed':
host.showErrorMessage(message, installDependenciesAction, learnMoreAction).then(
(str) => {
switch (str) {
case learnMoreAction:
host.showInformationMessage(localize('moreInfoMsg', 'Add {0} directory to path, or set "mssql-bdc.{0}-path" config to {0} binary.', binName));
break;
case installDependenciesAction:
installDependencies();
break;
}
}
);
break;
case 'configuredFileMissing':
host.showErrorMessage(message, installDependenciesAction).then(
(str) => {
if (str === installDependenciesAction) {
installDependencies();
}
}
);
break;
}
}
export async function checkForBinary(context: BinCheckContext, bin: string | undefined, binName: string, inferFailedMessage: string, configuredFileMissingMessage: string, alertOnFail: boolean): Promise<boolean> {
if (!bin) {
const fb = await findBinary(context.shell, binName);
if (fb.err || fb.output.length === 0) {
if (alertOnFail) {
alertNoBin(context.host, binName, 'inferFailed', inferFailedMessage, context.installDependenciesCallback);
}
return false;
}
context.binFound = true;
return true;
}
if (context.shell.isWindows) {
context.binFound = context.fs.existsSync(bin);
} else {
const sr = await context.shell.exec(`ls ${bin}`);
context.binFound = (!!sr && sr.code === 0);
}
if (context.binFound) {
context.binPath = bin;
} else {
if (alertOnFail) {
alertNoBin(context.host, binName, 'configuredFileMissing', configuredFileMissingMessage, context.installDependenciesCallback);
}
}
return context.binFound;
}

View File

@@ -1,66 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Errorable, failed } from '../interfaces';
interface CompatibilityGuaranteed {
readonly guaranteed: true;
}
interface CompatibilityNotGuaranteed {
readonly guaranteed: false;
readonly didCheck: boolean;
readonly clientVersion: string;
readonly serverVersion: string;
}
export type Compatibility = CompatibilityGuaranteed | CompatibilityNotGuaranteed;
export function isGuaranteedCompatible(c: Compatibility): c is CompatibilityGuaranteed {
return c.guaranteed;
}
export interface Version {
readonly major: string;
readonly minor: string;
readonly gitVersion: string;
}
export async function check(kubectlLoadJSON: (cmd: string) => Promise<Errorable<any>>): Promise<Compatibility> {
const version = await kubectlLoadJSON('version -o json');
if (failed(version)) {
return {
guaranteed: false,
didCheck: false,
clientVersion: '',
serverVersion: ''
};
}
const clientVersion: Version = version.result.clientVersion;
const serverVersion: Version = version.result.serverVersion;
if (isCompatible(clientVersion, serverVersion)) {
return { guaranteed: true };
}
return {
guaranteed: false,
didCheck: true,
clientVersion: clientVersion.gitVersion,
serverVersion: serverVersion.gitVersion
};
}
function isCompatible(clientVersion: Version, serverVersion: Version): boolean {
if (clientVersion.major === serverVersion.major) {
const clientMinor = Number.parseInt(clientVersion.minor);
const serverMinor = Number.parseInt(serverVersion.minor);
if (Number.isInteger(clientMinor) && Number.isInteger(serverMinor) && Math.abs(clientMinor - serverMinor) <= 1) {
return true;
}
}
return false;
}

View File

@@ -1,42 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export interface Host {
showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined>;
showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined>;
showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined>;
getConfiguration(key: string): any;
onDidChangeConfiguration(listener: (ch: vscode.ConfigurationChangeEvent) => any): vscode.Disposable;
}
export const host: Host = {
showErrorMessage: showErrorMessage,
showWarningMessage: showWarningMessage,
showInformationMessage: showInformationMessage,
getConfiguration: getConfiguration,
onDidChangeConfiguration: onDidChangeConfiguration,
};
function showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
}
function showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showWarningMessage(message, ...items);
}
function showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showInformationMessage(message, ...items);
}
function getConfiguration(key: string): any {
return vscode.workspace.getConfiguration(key);
}
function onDidChangeConfiguration(listener: (e: vscode.ConfigurationChangeEvent) => any): vscode.Disposable {
return vscode.workspace.onDidChangeConfiguration(listener);
}

View File

@@ -1,142 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Host } from './host';
import { FS } from '../utility/fs';
import { Shell, ShellResult } from '../utility/shell';
import * as binutil from './binutil';
import { Errorable } from '../interfaces';
import * as compatibility from './compatibility';
import { getToolPath } from '../config/config';
export interface Kubectl {
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
asJson<T>(command: string): Promise<Errorable<T>>;
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined>;
getContext(): Context;
}
interface Context {
readonly host: Host;
readonly fs: FS;
readonly shell: Shell;
readonly installDependenciesCallback: () => void;
binFound: boolean;
binPath: string;
}
class KubectlImpl implements Kubectl {
constructor(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void, kubectlFound: boolean) {
this.context = { host: host, fs: fs, shell: shell, installDependenciesCallback: installDependenciesCallback, binFound: kubectlFound, binPath: 'kubectl' };
}
readonly context: Context;
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
return checkPresent(this.context, errorMessageMode);
}
asJson<T>(command: string): Promise<Errorable<T>> {
return asJson(this.context, command);
}
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined> {
return invokeAsync(this.context, command, stdin);
}
getContext(): Context {
return this.context;
}
}
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
return new KubectlImpl(host, fs, shell, installDependenciesCallback, false);
}
export enum CheckPresentMessageMode {
Command,
Activation,
Silent,
}
async function checkPresent(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
if (context.binFound) {
return true;
}
return await checkForKubectlInternal(context, errorMessageMode);
}
async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
const binName = 'kubectl';
const bin = getToolPath(context.host, context.shell, binName);
const contextMessage = getCheckKubectlContextMessage(errorMessageMode);
const inferFailedMessage = localize('binaryNotFound', 'Could not find {0} binary. {1}', binName, contextMessage);
const configuredFileMissingMessage = localize('binaryNotInstalled', '{0} is not installed. {1}', bin, contextMessage);
return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== CheckPresentMessageMode.Silent);
}
function getCheckKubectlContextMessage(errorMessageMode: CheckPresentMessageMode): string {
if (errorMessageMode === CheckPresentMessageMode.Activation) {
return localize('kubernetesRequired', ' SQL Server Big data cluster requires kubernetes.');
} else if (errorMessageMode === CheckPresentMessageMode.Command) {
return localize('cannotExecuteCmd', ' Cannot execute command.');
}
return '';
}
async function invokeAsync(context: Context, command: string, stdin?: string): Promise<ShellResult | undefined> {
if (await checkPresent(context, CheckPresentMessageMode.Command)) {
const bin = baseKubectlPath(context);
const cmd = `${bin} ${command}`;
const sr = await context.shell.exec(cmd, stdin);
if (sr && sr.code !== 0) {
checkPossibleIncompatibility(context);
}
return sr;
} else {
return { code: -1, stdout: '', stderr: '' };
}
}
// TODO: invalidate this when the context changes or if we know kubectl has changed (e.g. config)
let checkedCompatibility = false; // We don't want to spam the user (or CPU!) repeatedly running the version check
async function checkPossibleIncompatibility(context: Context): Promise<void> {
if (checkedCompatibility) {
return;
}
checkedCompatibility = true;
const compat = await compatibility.check((cmd) => asJson<compatibility.Version>(context, cmd));
if (!compatibility.isGuaranteedCompatible(compat) && compat.didCheck) {
const versionAlert = localize('kubectlVersionIncompatible', 'kubectl version ${0} may be incompatible with cluster Kubernetes version {1}', compat.clientVersion, compat.serverVersion);
context.host.showWarningMessage(versionAlert);
}
}
export function baseKubectlPath(context: Context): string {
let bin = getToolPath(context.host, context.shell, 'kubectl');
if (!bin) {
bin = 'kubectl';
}
return bin;
}
async function asJson<T>(context: Context, command: string): Promise<Errorable<T>> {
const shellResult = await invokeAsync(context, command);
if (!shellResult) {
return { succeeded: false, error: [localize('cannotRunCommand', 'Unable to run command ({0})', command)] };
}
if (shellResult.code === 0) {
return { succeeded: true, result: JSON.parse(shellResult.stdout.trim()) as T };
}
return { succeeded: false, error: [shellResult.stderr] };
}

View File

@@ -1,136 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { Kubectl } from './kubectl';
import { failed, ClusterType } from '../interfaces';
export interface KubectlContext {
readonly clusterName: string;
readonly contextName: string;
readonly userName: string;
readonly active: boolean;
}
interface Kubeconfig {
readonly apiVersion: string;
readonly 'current-context': string;
readonly clusters: {
readonly name: string;
readonly cluster: {
readonly server: string;
readonly 'certificate-authority'?: string;
readonly 'certificate-authority-data'?: string;
};
}[] | undefined;
readonly contexts: {
readonly name: string;
readonly context: {
readonly cluster: string;
readonly user: string;
readonly namespace?: string;
};
}[] | undefined;
readonly users: {
readonly name: string;
readonly user: {};
}[] | undefined;
}
export interface ClusterConfig {
readonly server: string;
readonly certificateAuthority: string | undefined;
}
async function getKubeconfig(kubectl: Kubectl): Promise<Kubeconfig | null> {
const shellResult = await kubectl.asJson<any>('config view -o json');
if (failed(shellResult)) {
vscode.window.showErrorMessage(shellResult.error[0]);
return null;
}
return shellResult.result;
}
export async function getCurrentClusterConfig(kubectl: Kubectl): Promise<ClusterConfig | undefined> {
const kubeConfig = await getKubeconfig(kubectl);
if (!kubeConfig || !kubeConfig.clusters || !kubeConfig.contexts) {
return undefined;
}
const contextConfig = kubeConfig.contexts.find((context) => context.name === kubeConfig['current-context'])!;
const clusterConfig = kubeConfig.clusters.find((cluster) => cluster.name === contextConfig.context.cluster)!;
return {
server: clusterConfig.cluster.server,
certificateAuthority: clusterConfig.cluster['certificate-authority']
};
}
export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
const kubectlConfig = await getKubeconfig(kubectl);
if (!kubectlConfig) {
return [];
}
const currentContext = kubectlConfig['current-context'];
const contexts = kubectlConfig.contexts || [];
return contexts.map((c) => {
return {
clusterName: c.context.cluster,
contextName: c.name,
userName: c.context.user,
active: c.name === currentContext
};
});
}
export async function setContext(kubectl: Kubectl, targetContext: string): Promise<void> {
const shellResult = await kubectl.invokeAsync(`config use-context ${targetContext}`);
if (!shellResult || shellResult.code !== 0) {
// TODO: Update error handling for now.
let errMsg = shellResult ? shellResult.stderr : localize('runKubectlFailed', 'Unable to run kubectl');
vscode.window.showErrorMessage(localize('setClusterFailed', 'Failed to set \'{0}\' as current cluster: {1}', targetContext, errMsg));
}
}
export async function inferCurrentClusterType(kubectl: Kubectl): Promise<ClusterType> {
let latestContextName = '';
const ctxsr = await kubectl.invokeAsync('config current-context');
if (ctxsr && ctxsr.code === 0) {
latestContextName = ctxsr.stdout.trim();
} else {
return ClusterType.Other;
}
const cisr = await kubectl.invokeAsync('cluster-info');
if (!cisr || cisr.code !== 0) {
return ClusterType.Unknown;
}
const masterInfos = cisr.stdout.split('\n')
.filter((s) => s.indexOf('master is running at') >= 0);
if (masterInfos.length === 0) {
return ClusterType.Other;
}
const masterInfo = masterInfos[0];
if (masterInfo.indexOf('azmk8s.io') >= 0 || masterInfo.indexOf('azure.com') >= 0) {
return ClusterType.AKS;
}
if (latestContextName) {
const gcsr = await kubectl.invokeAsync(`config get-contexts ${latestContextName}`);
if (gcsr && gcsr.code === 0) {
if (gcsr.stdout.indexOf('minikube') >= 0) {
return ClusterType.Minikube;
}
}
}
return ClusterType.Other;
}

View File

@@ -1,29 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export interface ISqlServerBigDataClusterChannel {
showOutput(message: any, title?: string): void;
}
const outputChannelName = localize('bigDataClusterOutputChannel', 'SQL Server big data cluster');
class SqlServerBigDataCluster implements ISqlServerBigDataClusterChannel {
private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel(outputChannelName);
showOutput(message: any, title?: string): void {
if (title) {
const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, ' ').trim(); // YYYY-MM-DD HH:mm:ss.sss
const hightlightingTitle = `[${title} ${simplifiedTime}]`;
this.channel.appendLine(hightlightingTitle);
}
this.channel.appendLine(message);
this.channel.show();
}
}
export const sqlserverbigdataclusterchannel: ISqlServerBigDataClusterChannel = new SqlServerBigDataCluster();

View File

@@ -1,60 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { MainController } from './mainController';
import { fs } from './utility/fs';
import { host } from './kubectl/host';
import { sqlserverbigdataclusterchannel } from './kubectl/sqlServerBigDataClusterChannel';
import { shell, Shell } from './utility/shell';
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
import { installKubectl } from './installer/installer';
import { Errorable, failed } from './interfaces';
const kubectl = kubectlCreate(host, fs, shell, installDependencies);
export let controller: MainController;
export function activate(context: vscode.ExtensionContext) {
controller = new MainController(context, kubectl);
controller.activate();
}
// this method is called when your extension is deactivated
export function deactivate(): void {
if (controller) {
controller.deactivate();
}
}
export async function installDependencies() {
const gotKubectl = await kubectl.checkPresent(CheckPresentMessageMode.Silent);
const installPromises = [
installDependency('kubectl', gotKubectl, installKubectl)
];
await Promise.all(installPromises);
sqlserverbigdataclusterchannel.showOutput(localize('done', 'Done'));
}
async function installDependency(name: string, alreadyGot: boolean, installFunc: (shell: Shell) => Promise<Errorable<null>>): Promise<void> {
if (alreadyGot) {
sqlserverbigdataclusterchannel.showOutput(localize('dependencyInstalled', '{0} already installed...', name));
} else {
sqlserverbigdataclusterchannel.showOutput(localize('installingDependency', 'Installing {0}...', name));
const result = await installFunc(shell);
if (failed(result)) {
sqlserverbigdataclusterchannel.showOutput(localize('installingDependencyFailed', 'Unable to install {0}: {1}', name, result.error[0]));
}
}
}

View File

@@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import { CreateClusterWizard } from './wizards/create-cluster/createClusterWizard';
import { Kubectl } from './kubectl/kubectl';
/**
* The main controller class that initializes the extension
*/
export class MainController {
protected _context: vscode.ExtensionContext;
protected _kubectl: Kubectl;
public constructor(context: vscode.ExtensionContext, kubectl: Kubectl) {
this._context = context;
this._kubectl = kubectl;
}
/**
* Activates the extension
*/
public activate(): void {
vscode.commands.registerCommand('mssql.cluster.create', () => {
let wizard = new CreateClusterWizard(this._context, this._kubectl);
wizard.open();
});
}
/**
* Deactivates the extension
*/
public deactivate(): void { }
}

View File

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { fs } from '../utility/fs';
import { Shell } from '../utility/shell';
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import mkdirp = require('mkdirp');
import { Kubectl, baseKubectlPath } from '../kubectl/kubectl';
import { KubectlContext } from '../kubectl/kubectlUtils';
export interface Scriptable {
getScriptProperties(): Promise<ScriptingDictionary<string>>;
getTargetKubectlContext(): KubectlContext;
}
export interface ScriptingDictionary<V> {
[name: string]: V;
}
const deployFilePrefix: string = 'mssql-bdc-deploy';
export class ScriptGenerator {
private _shell: Shell;
private _kubectl: Kubectl;
private _kubectlPath: string;
constructor(_kubectl: Kubectl) {
this._kubectl = _kubectl;
this._shell = this._kubectl.getContext().shell;
this._kubectlPath = baseKubectlPath(this._kubectl.getContext());
}
public async generateDeploymentScript(scriptable: Scriptable): Promise<void> {
let targetClusterName = scriptable.getTargetKubectlContext().clusterName;
let targetContextName = scriptable.getTargetKubectlContext().contextName;
let timestamp = new Date().getTime();
let deployFolder = this.getDeploymentFolder(this._shell);
let deployFileSuffix = this._shell.isWindows() ? `.bat` : `.sh`;
let deployFileName = `${deployFilePrefix}-${targetClusterName}-${timestamp}${deployFileSuffix}`;
let deployFilePath = path.join(deployFolder, deployFileName);
let envVars = '';
let propertiesDict = await scriptable.getScriptProperties();
for (let key in propertiesDict) {
let value = propertiesDict[key];
envVars += this._shell.isWindows() ? `Set ${key} = ${value}\n` : `export ${key} = ${value}\n`;
}
envVars += os.EOL;
let kubeContextcommand = `${this._kubectlPath} config use-context ${targetContextName}\n`;
// Todo: The API for mssqlctl may change per version, so need a version check to use proper syntax.
let deployCommand = `mssqlctl create cluster ${targetClusterName}\n`;
let deployContent = envVars + kubeContextcommand + deployCommand;
mkdirp.sync(deployFolder);
await fs.writeFile(deployFilePath, deployContent, handleError);
}
public getDeploymentFolder(shell: Shell): string {
return path.join(shell.home(), `.mssql-bdc/deployment`);
}
}
const handleError = (err: NodeJS.ErrnoException) => {
if (err) {
vscode.window.showErrorMessage(err.message);
}
};

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -1,65 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sysfs from 'fs';
export interface FS {
existsSync(path: string): boolean;
readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
readFileSync(filename: string, encoding: string): string;
readFileToBufferSync(filename: string): Buffer;
writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
writeFileSync(filename: string, data: any): void;
dirSync(path: string): string[];
unlinkAsync(path: string): Promise<void>;
existsAsync(path: string): Promise<boolean>;
openAsync(path: string, flags: string): Promise<void>;
statSync(path: string): sysfs.Stats;
}
export const fs: FS = {
existsSync: (path) => sysfs.existsSync(path),
readFile: (filename, encoding, callback) => sysfs.readFile(filename, encoding, callback),
readFileSync: (filename, encoding) => sysfs.readFileSync(filename, encoding),
readFileToBufferSync: (filename) => sysfs.readFileSync(filename),
writeFile: (filename, data, callback) => sysfs.writeFile(filename, data, callback),
writeFileSync: (filename, data) => sysfs.writeFileSync(filename, data),
dirSync: (path) => sysfs.readdirSync(path),
unlinkAsync: (path) => {
return new Promise((resolve, reject) => {
sysfs.unlink(path, (error) => {
if (error) {
reject();
return;
}
resolve();
});
});
},
existsAsync: (path) => {
return new Promise((resolve) => {
sysfs.exists(path, (exists) => {
resolve(exists);
});
});
},
openAsync: (path, flags) => {
return new Promise((resolve, reject) => {
sysfs.open(path, flags, (error, _fd) => {
if (error) {
reject();
return;
}
resolve();
});
});
},
statSync: (path) => sysfs.statSync(path)
};

View File

@@ -1,204 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as shelljs from 'shelljs';
import * as path from 'path';
import { getActiveKubeconfig, getToolPath } from '../config/config';
import { host } from '../kubectl/host';
export enum Platform {
Windows,
MacOS,
Linux,
Unsupported,
}
export interface ExecCallback extends shelljs.ExecCallback { }
export interface Shell {
isWindows(): boolean;
isUnix(): boolean;
platform(): Platform;
home(): string;
combinePath(basePath: string, relativePath: string): string;
fileUri(filePath: string): vscode.Uri;
execOpts(): any;
exec(cmd: string, stdin?: string): Promise<ShellResult | undefined>;
execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult>;
unquotedPath(path: string): string;
which(bin: string): string | null;
cat(path: string): string;
ls(path: string): string[];
}
export const shell: Shell = {
isWindows: isWindows,
isUnix: isUnix,
platform: platform,
home: home,
combinePath: combinePath,
fileUri: fileUri,
execOpts: execOpts,
exec: exec,
execCore: execCore,
unquotedPath: unquotedPath,
which: which,
cat: cat,
ls: ls,
};
const WINDOWS: string = 'win32';
export interface ShellResult {
readonly code: number;
readonly stdout: string;
readonly stderr: string;
}
export type ShellHandler = (code: number, stdout: string, stderr: string) => void;
function isWindows(): boolean {
return (process.platform === WINDOWS);
}
function isUnix(): boolean {
return !isWindows();
}
function platform(): Platform {
switch (process.platform) {
case 'win32': return Platform.Windows;
case 'darwin': return Platform.MacOS;
case 'linux': return Platform.Linux;
default: return Platform.Unsupported;
}
}
function concatIfBoth(s1: string | undefined, s2: string | undefined): string | undefined {
return s1 && s2 ? s1.concat(s2) : undefined;
}
function home(): string {
return process.env['HOME'] ||
concatIfBoth(process.env['HOMEDRIVE'], process.env['HOMEPATH']) ||
process.env['USERPROFILE'] ||
'';
}
function combinePath(basePath: string, relativePath: string) {
let separator = '/';
if (isWindows()) {
relativePath = relativePath.replace(/\//g, '\\');
separator = '\\';
}
return basePath + separator + relativePath;
}
function isWindowsFilePath(filePath: string) {
return filePath[1] === ':' && filePath[2] === '\\';
}
function fileUri(filePath: string): vscode.Uri {
if (isWindowsFilePath(filePath)) {
return vscode.Uri.parse('file:///' + filePath.replace(/\\/g, '/'));
}
return vscode.Uri.parse('file://' + filePath);
}
function execOpts(): any {
let env = process.env;
if (isWindows()) {
env = Object.assign({}, env, { HOME: home() });
}
env = shellEnvironment(env);
const opts = {
cwd: vscode.workspace.rootPath,
env: env,
async: true
};
return opts;
}
async function exec(cmd: string, stdin?: string): Promise<ShellResult | undefined> {
try {
return await execCore(cmd, execOpts(), stdin);
} catch (ex) {
vscode.window.showErrorMessage(ex);
return undefined;
}
}
function execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult> {
return new Promise<ShellResult>((resolve) => {
const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({ code: code, stdout: stdout, stderr: stderr }));
if (stdin) {
proc.stdin.end(stdin);
}
});
}
function unquotedPath(path: string): string {
if (isWindows() && path && path.length > 1 && path.startsWith('"') && path.endsWith('"')) {
return path.substring(1, path.length - 1);
}
return path;
}
export function shellEnvironment(baseEnvironment: any): any {
const env = Object.assign({}, baseEnvironment);
const pathVariable = pathVariableName(env);
for (const tool of ['kubectl']) {
const toolPath = getToolPath(host, shell, tool);
if (toolPath) {
const toolDirectory = path.dirname(toolPath);
const currentPath = env[pathVariable];
env[pathVariable] = toolDirectory + (currentPath ? `${pathEntrySeparator()}${currentPath}` : '');
}
}
const kubeconfig = getActiveKubeconfig();
if (kubeconfig) {
env['KUBECONFIG'] = kubeconfig;
}
return env;
}
function pathVariableName(env: any): string {
if (isWindows()) {
for (const v of Object.keys(env)) {
if (v.toLowerCase() === 'path') {
return v;
}
}
}
return 'PATH';
}
function pathEntrySeparator() {
return isWindows() ? ';' : ':';
}
function which(bin: string): string | null {
return shelljs.which(bin);
}
function cat(path: string): string {
return shelljs.cat(path);
}
function ls(path: string): string[] {
return shelljs.ls(path);
}
export function shellMessage(sr: ShellResult | undefined, invocationFailureMessage: string): string {
if (!sr) {
return invocationFailureMessage;
}
return sr.code === 0 ? sr.stdout : sr.stderr;
}

View File

@@ -1,328 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TargetClusterType, ClusterPorts, ClusterType, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus, ClusterProfile, PoolConfiguration, SQLServerMasterConfiguration, ClusterPoolType, ClusterResourceSummary } from '../../interfaces';
import { getContexts, KubectlContext, setContext, inferCurrentClusterType } from '../../kubectl/kubectlUtils';
import { Kubectl } from '../../kubectl/kubectl';
import { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
import * as nls from 'vscode-nls';
import * as os from 'os';
import * as path from 'path';
const localize = nls.loadMessageBundle();
export class CreateClusterModel implements Scriptable {
private _tmp_tools_installed: boolean = false;
private scriptingProperties: ScriptingDictionary<string> = {};
constructor(private _kubectl: Kubectl) {
}
public async loadClusters(): Promise<KubectlContext[]> {
return await getContexts(this._kubectl);
}
public async changeKubernetesContext(targetContext: string): Promise<void> {
await setContext(this._kubectl, targetContext);
}
public getDefaultPorts(): Thenable<ClusterPorts> {
let promise = new Promise<ClusterPorts>(resolve => {
resolve({
sql: '31433',
knox: '30443',
controller: '30888',
proxy: '30909',
grafana: '30119',
kibana: '30999'
});
});
return promise;
}
public getDefaultContainerRegistryInfo(): Thenable<ContainerRegistryInfo> {
let promise = new Promise<ContainerRegistryInfo>(resolve => {
resolve({
registry: 'private-repo.microsoft.com',
repository: 'mssql-private-preview',
imageTag: 'latest'
});
});
return promise;
}
public getAllTargetClusterTypeInfo(): Thenable<TargetClusterTypeInfo[]> {
let promise = new Promise<TargetClusterTypeInfo[]>(resolve => {
let aksCluster: TargetClusterTypeInfo = {
enabled: false,
type: TargetClusterType.NewAksCluster,
name: localize('bdc-create.AKSClusterCardText', 'New AKS Cluster'),
fullName: localize('bdc-create.AKSClusterFullName', 'New Azure Kubernetes Service cluster'),
description: localize('bdc-create.AKSClusterDescription',
'This option configures new Azure Kubernetes Service (AKS) for SQL Server big data cluster deployments. AKS makes it simple to create, configure and manage a cluster of virutal machines that are preconfigured with a Kubernetes cluster to run containerized applications.'),
iconPath: {
dark: 'images/aks.svg',
light: 'images/aks.svg'
}
};
let existingCluster: TargetClusterTypeInfo = {
enabled: true,
type: TargetClusterType.ExistingKubernetesCluster,
name: localize('bdc-create.ExistingClusterCardText', 'Existing Cluster'),
fullName: localize('bdc-create.ExistingClusterFullName', 'Existing Kubernetes cluster'),
description: localize('bdc-create.ExistingClusterDescription', 'This option assumes you already have a Kubernetes cluster installed, Once a prerequisite check is done, ensure the correct cluster context is selected.'),
iconPath: {
dark: 'images/kubernetes.svg',
light: 'images/kubernetes.svg'
}
};
resolve([aksCluster, existingCluster]);
});
return promise;
}
public getRequiredToolStatus(): Thenable<ToolInfo[]> {
let kubeCtl = {
name: 'kubectl',
description: 'Tool used for managing the Kubernetes cluster',
version: '',
status: ToolInstallationStatus.Installed
};
let mssqlCtl = {
name: 'mssqlctl',
description: 'Command-line tool for installing and managing the SQL Server big data cluster',
version: '',
status: ToolInstallationStatus.Installed
};
let azureCli = {
name: 'Azure CLI',
description: 'Tool used for managing Azure services',
version: '',
status: this._tmp_tools_installed ? ToolInstallationStatus.Installed : ToolInstallationStatus.NotInstalled
};
let promise = new Promise<ToolInfo[]>(resolve => {
setTimeout(() => {
let tools = this.targetClusterType === TargetClusterType.ExistingKubernetesCluster ? [kubeCtl, mssqlCtl] : [kubeCtl, mssqlCtl, azureCli];
resolve(tools);
}, 1000);
});
return promise;
}
public installTool(tool: ToolInfo): Thenable<void> {
let promise = new Promise<void>(resolve => {
setTimeout(() => {
tool.status = ToolInstallationStatus.Installed;
this._tmp_tools_installed = true;
resolve();
}, 1000);
});
return promise;
}
public getDefaultKubeConfigPath(): string {
return path.join(os.homedir(), '.kube', 'config');
}
public clusterName: string;
public targetClusterType: TargetClusterType;
public selectedCluster: KubectlContext;
public adminUserName: string;
public adminPassword: string;
public sqlPort: string;
public knoxPort: string;
public controllerPort: string;
public proxyPort: string;
public grafanaPort: string;
public kibanaPort: string;
public containerRegistry: string;
public containerRepository: string;
public containerImageTag: string;
public containerRegistryUserName: string;
public containerRegistryPassword: string;
public profile: ClusterProfile;
public async getTargetClusterPlatform(targetContextName: string): Promise<string> {
await setContext(this._kubectl, targetContextName);
let clusterType = await inferCurrentClusterType(this._kubectl);
switch (clusterType) {
case ClusterType.AKS:
return 'aks';
case ClusterType.Minikube:
return 'minikube';
case ClusterType.Other:
default:
return 'kubernetes';
}
}
public async getScriptProperties(): Promise<ScriptingDictionary<string>> {
// Cluster settings
this.scriptingProperties['CLUSTER_NAME'] = this.selectedCluster.clusterName;
this.scriptingProperties['CLUSTER_PLATFORM'] = await this.getTargetClusterPlatform(this.selectedCluster.contextName);
// Default pool count for now. TODO: Update from user input
this.scriptingProperties['CLUSTER_DATA_POOL_REPLICAS'] = '1';
this.scriptingProperties['CLUSTER_COMPUTE_POOL_REPLICAS'] = '2';
this.scriptingProperties['CLUSTER_STORAGE_POOL_REPLICAS'] = '3';
// SQL Server settings
this.scriptingProperties['CONTROLLER_USERNAME'] = this.adminUserName;
this.scriptingProperties['CONTROLLER_PASSWORD'] = this.adminPassword;
this.scriptingProperties['KNOX_PASSWORD'] = this.adminPassword;
this.scriptingProperties['MSSQL_SA_PASSWORD'] = this.adminPassword;
// docker settings
this.scriptingProperties['DOCKER_REPOSITORY'] = this.containerRepository;
this.scriptingProperties['DOCKER_REGISTRY'] = this.containerRegistry;
this.scriptingProperties['DOCKER_PASSWORD'] = this.containerRegistryPassword;
this.scriptingProperties['DOCKER_USERNAME'] = this.containerRegistryUserName;
this.scriptingProperties['DOCKER_IMAGE_TAG'] = this.containerImageTag;
// port settings
this.scriptingProperties['MASTER_SQL_PORT'] = this.sqlPort;
this.scriptingProperties['KNOX_PORT'] = this.knoxPort;
this.scriptingProperties['GRAFANA_PORT'] = this.grafanaPort;
this.scriptingProperties['KIBANA_PORT'] = this.kibanaPort;
return this.scriptingProperties;
}
public getTargetKubectlContext(): KubectlContext {
return this.selectedCluster;
}
public getClusterResource(): Thenable<ClusterResourceSummary> {
let promise = new Promise<ClusterResourceSummary>(resolve => {
setTimeout(() => {
let resoureSummary: ClusterResourceSummary = {
hardwareLabels: [
{
name: '<Default>',
totalNodes: 10,
totalCores: 22,
totalDisks: 128,
totalMemoryInGB: 77
},
{
name: '#data',
totalNodes: 4,
totalCores: 22,
totalDisks: 200,
totalMemoryInGB: 100
},
{
name: '#compute',
totalNodes: 12,
totalCores: 124,
totalDisks: 24,
totalMemoryInGB: 100
},
{
name: '#premium',
totalNodes: 10,
totalCores: 100,
totalDisks: 200,
totalMemoryInGB: 770
}
]
};
resolve(resoureSummary);
}, 1000);
});
return promise;
}
public getProfiles(): Thenable<ClusterProfile[]> {
let promise = new Promise<ClusterProfile[]>(resolve => {
setTimeout(() => {
let profiles: ClusterProfile[] = [];
profiles.push({
name: 'Basic',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(1, 1),
computePoolConfiguration: this.createComputePoolConfiguration(2),
dataPoolConfiguration: this.createDataPoolConfiguration(2),
storagePoolConfiguration: this.createStoragePoolConfiguration(2),
sparkPoolConfiguration: this.createSparkPoolConfiguration(2)
});
profiles.push({
name: 'Standard',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(3, 9),
computePoolConfiguration: this.createComputePoolConfiguration(5),
dataPoolConfiguration: this.createDataPoolConfiguration(5),
storagePoolConfiguration: this.createStoragePoolConfiguration(5),
sparkPoolConfiguration: this.createSparkPoolConfiguration(5)
});
profiles.push({
name: 'Premium',
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(5, 9),
computePoolConfiguration: this.createComputePoolConfiguration(7),
dataPoolConfiguration: this.createDataPoolConfiguration(7),
storagePoolConfiguration: this.createStoragePoolConfiguration(7),
sparkPoolConfiguration: this.createSparkPoolConfiguration(7)
});
resolve(profiles);
}, 1000);
});
return promise;
}
private createSQLPoolConfiguration(scale: number, maxScale: number): SQLServerMasterConfiguration {
return <SQLServerMasterConfiguration>{
type: ClusterPoolType.SQL,
engineOnly: false,
scale: scale,
maxScale: maxScale
};
}
private createComputePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Compute,
scale: scale
};
}
private createDataPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Data,
scale: scale
};
}
private createStoragePoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Storage,
scale: scale
};
}
private createSparkPoolConfiguration(scale: number): PoolConfiguration {
return {
type: ClusterPoolType.Spark,
scale: scale
};
}
}

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { CreateClusterModel } from './createClusterModel';
import { SelectExistingClusterPage } from './pages/selectExistingClusterPage';
import { SummaryPage } from './pages/summaryPage';
import { SettingsPage } from './pages/settingsPage';
import { ClusterProfilePage } from './pages/clusterProfilePage';
import { ExtensionContext } from 'vscode';
import { WizardBase } from '../wizardBase';
import * as nls from 'vscode-nls';
import { Kubectl } from '../../kubectl/kubectl';
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
import { ScriptGenerator } from '../../scripting/scripting';
const localize = nls.loadMessageBundle();
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
private scripter: ScriptGenerator;
constructor(context: ExtensionContext, kubectl: Kubectl) {
let model = new CreateClusterModel(kubectl);
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
this.scripter = new ScriptGenerator(kubectl);
}
protected initialize(): void {
let settingsPage = new SettingsPage(this);
let clusterProfilePage = new ClusterProfilePage(this);
let selectTargetClusterPage = new SelectExistingClusterPage(this);
let summaryPage = new SummaryPage(this);
let targetClusterTypePage = new SelectTargetClusterTypePage(this);
this.setPages([targetClusterTypePage, selectTargetClusterPage, settingsPage, clusterProfilePage, summaryPage]);
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
this.wizardObject.generateScriptButton.hidden = false;
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
this.registerDisposable(this.wizardObject.generateScriptButton.onClick(async () => {
this.wizardObject.generateScriptButton.enabled = false;
this.scripter.generateDeploymentScript(this.model).then(() => {
this.wizardObject.generateScriptButton.enabled = true;
//TODO: Add error handling.
});
}));
}
protected onCancel(): void {
}
protected onOk(): void {
}
}

View File

@@ -1,389 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
import { ClusterProfile, PoolConfiguration, ClusterPoolType, SQLServerMasterConfiguration, ClusterResourceSummary } from '../../../interfaces';
const localize = nls.loadMessageBundle();
const LabelWidth = '200px';
const InputWidth = '300px';
export class ClusterProfilePage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private clusterProfiles: ClusterProfile[];
private poolList: azdata.FlexContainer;
private detailContainer: azdata.FlexContainer;
private clusterResourceView: azdata.GroupContainer;
private poolListMap = {};
private clusterResourceContainer: azdata.FlexContainer;
private clusterResourceLoadingComponent: azdata.LoadingComponent;
private clusterResource: ClusterResourceSummary;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.clusterProfilePageTitle', 'Select a cluster profile'),
localize('bdc-create.clusterProfilePageDescription', 'Select your requirement and we will provide you a pre-defined default scaling. You can later go to cluster configuration and customize it.'),
wizard);
}
public onEnter(): void {
this.updatePoolList();
this.clusterResourceLoadingComponent.loading = true;
this.wizard.model.getClusterResource().then((resource) => {
this.clusterResource = resource;
this.initializeClusterResourceView();
});
this.wizard.wizardObject.registerNavigationValidator(() => {
return true;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
let fetchProfilePromise = this.wizard.model.getProfiles().then(p => { this.clusterProfiles = p; });
return Promise.all([fetchProfilePromise]).then(() => {
this.wizard.model.profile = this.clusterProfiles[0];
this.clusterResourceView = this.view.modelBuilder.groupContainer().withLayout({
header: localize('bdc-create.TargetClusterOverview', 'Target cluster scale overview'),
collapsed: true,
collapsible: true
}).component();
this.clusterResourceContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.clusterResourceLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterResourceContainer).component();
this.clusterResourceView.addItem(this.clusterResourceLoadingComponent);
let profileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.clusterProfileLabel', 'Deployment profile') }).component();
let profileDropdown = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
values: this.clusterProfiles.map(profile => profile.name),
width: '300px'
}).component();
let dropdownRow = this.view.modelBuilder.flexContainer().withItems([profileLabel, profileDropdown], { CSSStyles: { 'margin-right': '30px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
let poolContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', width: '100%', height: '100%' }).component();
this.poolList = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '300px', height: '100%' }).component();
poolContainer.addItem(this.poolList, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-right-style': 'solid',
'border-right-width': '2px',
'border-color': 'lightgray'
}
});
this.detailContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '760px', height: '100%' }).component();
poolContainer.addItem(this.detailContainer, {
CSSStyles: {
'border-top-style': 'solid',
'border-top-width': '2px',
'border-color': 'lightgray'
}
});
this.wizard.registerDisposable(profileDropdown.onValueChanged(() => {
let profiles = this.clusterProfiles.filter(p => profileDropdown.value === p.name);
if (profiles && profiles.length === 1) {
this.wizard.model.profile = profiles[0];
this.updatePoolList();
this.clearPoolDetail();
}
}));
this.initializePoolList();
let pageContainer = this.view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
height: '800px'
}).component();
pageContainer.addItem(this.clusterResourceView, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px',
'padding-bottom': '5px',
'padding-top': '5px'
}
});
pageContainer.addItem(dropdownRow, {
flex: '0 0 auto',
CSSStyles: { 'margin-bottom': '10px' }
});
pageContainer.addItem(poolContainer, {
flex: '1 1 auto',
CSSStyles: {
'display': 'flex'
}
});
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.withFormItems([{
title: '',
component: pageContainer
}], {
horizontal: false,
componentWidth: '100%'
}).component();
return view.initializeModel(form);
});
}
private initializeClusterResourceView(): void {
this.clusterResourceContainer.clearItems();
let text = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileText', 'Hardware profile') }).component();
let height = (this.clusterResource.hardwareLabels.length * 25) + 30;
let labelColumn: azdata.TableColumn = {
value: localize('bdc-create.HardwareLabelColumnName', 'Label'),
width: 100
};
let totalNodesColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalNodesColumnName', 'Nodes'),
width: 50
};
let totalCoresColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalCoresColumnName', 'Cores'),
width: 50
};
let totalMemoryColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalMemoryColumnName', 'Memory'),
width: 50
};
let totalDisksColumn: azdata.TableColumn = {
value: localize('bdc-create.TotalDisksColumnName', 'Disks'),
width: 50
};
let table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
height: `${height}px`,
data: this.clusterResource.hardwareLabels.map(label => [label.name, label.totalNodes, label.totalCores, label.totalMemoryInGB, label.totalDisks]),
columns: [labelColumn, totalNodesColumn, totalCoresColumn, totalMemoryColumn, totalDisksColumn],
width: '300px'
}).component();
this.clusterResourceContainer.addItems([text, table]);
this.clusterResourceLoadingComponent.loading = false;
}
private initializePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let poolSummaryButton = this.view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: true }).component();
let container = this.view.modelBuilder.flexContainer().component();
this.wizard.registerDisposable(poolSummaryButton.onDidClick(() => {
this.clearPoolDetail();
let currentPool: PoolConfiguration;
switch (pool.type) {
case ClusterPoolType.SQL:
currentPool = this.wizard.model.profile.sqlServerMasterConfiguration;
break;
case ClusterPoolType.Compute:
currentPool = this.wizard.model.profile.computePoolConfiguration;
break;
case ClusterPoolType.Data:
currentPool = this.wizard.model.profile.dataPoolConfiguration;
break;
case ClusterPoolType.Storage:
currentPool = this.wizard.model.profile.storagePoolConfiguration;
break;
case ClusterPoolType.Spark:
currentPool = this.wizard.model.profile.sparkPoolConfiguration;
break;
default:
break;
}
if (currentPool) {
this.detailContainer.addItem(this.createPoolConfigurationPart(currentPool), { CSSStyles: { 'margin-left': '10px' } });
}
}));
let text = this.view.modelBuilder.text().component();
this.poolListMap[pool.type] = text;
text.width = '250px';
let chrevron = this.view.modelBuilder.text().withProperties({ value: '>' }).component();
chrevron.width = '30px';
container.addItem(text);
container.addItem(chrevron, {
CSSStyles: {
'font-size': '20px',
'line-height': '0px'
}
});
poolSummaryButton.addItem(container);
this.poolList.addItem(poolSummaryButton, {
CSSStyles: {
'border-bottom-style': 'solid',
'border-bottom-width': '1px',
'border-color': 'lightgray',
'cursor': 'pointer'
}
});
});
}
private createPoolConfigurationPart(configuration: PoolConfiguration): azdata.Component {
let container = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
switch (configuration.type) {
case ClusterPoolType.SQL:
this.createSQLConfigurationPart(container, configuration as SQLServerMasterConfiguration);
break;
default:
this.createDefaultPoolConfigurationPart(container, configuration);
break;
}
return container;
}
private createSQLConfigurationPart(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
this.createDefaultPoolConfigurationPart(container, configuration);
this.addFeatureSetRow(container, configuration);
}
private createDefaultPoolConfigurationPart(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
this.addPoolNameLabel(container, this.getPoolDisplayName(configuration.type));
this.addPoolDescriptionLabel(container, this.getPoolDescription(configuration.type));
this.addScaleRow(container, configuration);
this.addHardwareLabelRow(container, configuration);
}
private addPoolNameLabel(container: azdata.FlexContainer, text: string): void {
let poolNameLabel = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(poolNameLabel, {
flex: '0 0 auto', CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
}
});
}
private addPoolDescriptionLabel(container: azdata.FlexContainer, text: string): void {
let label = this.view.modelBuilder.text().withProperties({ value: text }).component();
container.addItem(label, {
flex: '0 0 auto',
CSSStyles: {
'margin-bottom': '20px'
}
});
}
private addScaleRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.ScaleLabel', 'Scale') }).component();
label.width = LabelWidth;
let input = this.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
inputType: 'number',
value: configuration.scale.toString(),
min: 1,
max: configuration.maxScale
}).component();
this.wizard.registerDisposable(input.onTextChanged(() => {
configuration.scale = Number(input.value);
this.updatePoolList();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addHardwareLabelRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileLabel', 'Hardware profile label') }).component();
label.width = LabelWidth;
let optionalValues = this.clusterResource.hardwareLabels.map(label => label.name);
configuration.hardwareLabel = configuration.hardwareLabel ? configuration.hardwareLabel : optionalValues[0];
let input = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ value: configuration.hardwareLabel, values: optionalValues }).component();
this.wizard.registerDisposable(input.onValueChanged(() => {
configuration.hardwareLabel = input.value.toString();
}));
input.width = InputWidth;
let row = this.createRow([label, input]);
container.addItem(row);
}
private addFeatureSetRow(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
const radioGroupName = 'featureset';
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.FeatureSetLabel', 'Feature set') }).component();
label.width = LabelWidth;
let engineOnlyOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineOnlyText', 'Engine only'), name: radioGroupName, checked: configuration.engineOnly }).component();
let engineWithFeaturesOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineWithFeaturesText', 'Engine with optional features'), name: radioGroupName, checked: !configuration.engineOnly }).component();
let optionContainer = this.view.modelBuilder.divContainer().component();
optionContainer.width = InputWidth;
optionContainer.addItems([engineOnlyOption, engineWithFeaturesOption]);
container.addItem(this.createRow([label, optionContainer]));
this.wizard.registerDisposable(engineOnlyOption.onDidClick(() => {
configuration.engineOnly = true;
}));
this.wizard.registerDisposable(engineWithFeaturesOption.onDidClick(() => {
configuration.engineOnly = false;
}));
}
private createRow(items: azdata.Component[]): azdata.FlexContainer {
return this.view.modelBuilder.flexContainer().withItems(items, {
CSSStyles: {
'margin-right': '5px'
}
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
}
private getPoolDisplayName(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDisplayName', 'SQL Server master');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDisplayName', 'Compute pool');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDisplayName', 'Data pool');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDisplayName', 'Storage pool');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDisplayName', 'Spark pool');
default:
throw new Error('unknown pool type');
}
}
private getPoolDescription(poolType: ClusterPoolType): string {
switch (poolType) {
case ClusterPoolType.SQL:
return localize('bdc-create.SQLServerMasterDescription', 'The SQL Server instance provides an externally accessible TDS endpoint for the cluster');
case ClusterPoolType.Compute:
return localize('bdc-create.ComputePoolDescription', 'TODO: Add description');
case ClusterPoolType.Data:
return localize('bdc-create.DataPoolDescription', 'TODO: Add description');
case ClusterPoolType.Storage:
return localize('bdc-create.StoragePoolDescription', 'TODO: Add description');
case ClusterPoolType.Spark:
return localize('bdc-create.SparkPoolDescription', 'TODO: Add description');
default:
throw new Error('unknown pool type');
}
}
private updatePoolList(): void {
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
this.wizard.model.profile.computePoolConfiguration,
this.wizard.model.profile.dataPoolConfiguration,
this.wizard.model.profile.sparkPoolConfiguration,
this.wizard.model.profile.storagePoolConfiguration];
pools.forEach(pool => {
let text = this.poolListMap[pool.type] as azdata.TextComponent;
if (text) {
text.value = localize({
key: 'bdc-create.poolLabelTemplate',
comment: ['{0} is the pool name, {1} is the scale number']
}, '{0} ({1})', this.getPoolDisplayName(pool.type), pool.scale);
}
});
}
private clearPoolDetail(): void {
this.detailContainer.clearItems();
}
}

View File

@@ -1,164 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import * as fs from 'fs';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import { setActiveKubeconfig } from '../../../config/config';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const ClusterRadioButtonGroupName = 'cluster';
export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizard> {
private existingClusterControl: azdata.FlexContainer;
private clusterContextsLabel: azdata.TextComponent;
private errorLoadingClustersLabel: azdata.TextComponent;
private clusterContextList: azdata.DivContainer;
private clusterContextLoadingComponent: azdata.LoadingComponent;
private configFileInput: azdata.InputBoxComponent;
private browseFileButton: azdata.ButtonComponent;
private loadDefaultKubeConfigFile: boolean = true;
private view: azdata.ModelView;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
localize('bdc-create.selectTargetClusterPageDescription', 'Select the kubeconfig file and then select a cluster context from the list'),
wizard);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
this.initExistingClusterControl();
let formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: this.existingClusterControl,
title: ''
}
],
{
horizontal: false
}
).withLayout({ width: '100%', height: '100%' });
let form = formBuilder.component();
return view.initializeModel(form);
}
public onEnter() {
if (this.loadDefaultKubeConfigFile) {
let defaultKubeConfigPath = this.wizard.model.getDefaultKubeConfigPath();
if (fs.existsSync(defaultKubeConfigPath)) {
this.loadClusterContexts(defaultKubeConfigPath);
}
this.loadDefaultKubeConfigFile = false;
}
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
let clusterSelected = this.wizard.model.selectedCluster !== undefined;
if (!clusterSelected) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.ClusterContextNotSelectedMessage', 'Please select a cluster context.'),
level: azdata.window.MessageLevel.Error
};
}
return clusterSelected;
});
}
private initExistingClusterControl(): void {
let self = this;
const labelWidth = '150px';
let configFileLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'Kube config file path') }).component();
configFileLabel.width = labelWidth;
this.configFileInput = this.view.modelBuilder.inputBox().withProperties({ width: '300px' }).component();
this.configFileInput.enabled = false;
this.browseFileButton = this.view.modelBuilder.button().withProperties({ label: localize('bdc-browseText', 'Browse'), width: '100px' }).component();
let configFileContainer = this.view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', alignItems: 'baseline' })
.withItems([configFileLabel, this.configFileInput, this.browseFileButton], { CSSStyles: { 'margin-right': '10px' } }).component();
this.clusterContextsLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-clusterContextsLabelText', 'Cluster Contexts') }).component();
this.clusterContextsLabel.width = labelWidth;
this.errorLoadingClustersLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
this.clusterContextList = this.view.modelBuilder.divContainer().component();
this.clusterContextLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterContextList).component();
this.existingClusterControl = this.view.modelBuilder.divContainer().component();
let clusterContextContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'start' }).component();
clusterContextContainer.addItem(this.clusterContextsLabel, { flex: '0 0 auto' });
clusterContextContainer.addItem(this.clusterContextLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'width': '400px', 'margin-left': '10px', 'margin-top': '10px' } });
this.existingClusterControl.addItem(configFileContainer, { CSSStyles: { 'margin-top': '0px' } });
this.existingClusterControl.addItem(clusterContextContainer, {
CSSStyles: { 'margin- top': '10px' }
});
this.wizard.registerDisposable(this.browseFileButton.onDidClick(async () => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(os.homedir()),
openLabel: localize('bdc-selectKubeConfigFileText', 'Select'),
filters: {
'KubeConfig Files': ['*'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
self.clusterContextList.clearItems();
let fileUri = fileUris[0];
self.loadClusterContexts(fileUri.fsPath);
}));
}
private async loadClusterContexts(configPath: string): Promise<void> {
this.clusterContextLoadingComponent.loading = true;
let self = this;
this.configFileInput.value = configPath;
await setActiveKubeconfig(configPath);
let clusters = await this.wizard.model.loadClusters();
if (clusters.length !== 0) {
let options = clusters.map(cluster => {
let option = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: cluster.contextName,
checked: cluster.active,
name: ClusterRadioButtonGroupName
}).component();
if (cluster.active) {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
}
this.wizard.registerDisposable(option.onDidClick(() => {
self.wizard.model.selectedCluster = cluster;
self.wizard.wizardObject.message = null;
}));
return option;
});
self.clusterContextList.addItems(options);
} else {
self.clusterContextList.addItem(this.errorLoadingClustersLabel);
}
this.clusterContextLoadingComponent.loading = false;
}
}

View File

@@ -1,259 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { TargetClusterTypeInfo, ToolInstallationStatus, ToolInfo } from '../../../interfaces';
import * as nls from 'vscode-nls';
import { CreateClusterWizard } from '../createClusterWizard';
const localize = nls.loadMessageBundle();
const InstallToolsButtonText = localize('bdc-create.InstallToolsText', 'Install Tools');
const InstallingButtonText = localize('bdc-create.InstallingButtonText', 'Installing...');
export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWizard> {
private cards: azdata.CardComponent[];
private toolsTable: azdata.TableComponent;
private formBuilder: azdata.FormBuilder;
private form: azdata.FormContainer;
private installToolsButton: azdata.window.Button;
private toolsLoadingWrapper: azdata.LoadingComponent;
private refreshToolsButton: azdata.window.Button;
private targetDescriptionText: azdata.TextComponent;
private targetDescriptionGroup: azdata.FormComponent;
private isValid: boolean = false;
private isLoading: boolean = false;
private requiredTools: ToolInfo[];
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.selectTargetClusterTypePageTitle', 'What is your target cluster environment?'),
localize('bdc-create.selectTargetClusterTypePageDescription', 'Choose the target environment and then install the required tools for it.'),
wizard);
this.installToolsButton = azdata.window.createButton(InstallToolsButtonText);
this.installToolsButton.hidden = true;
this.wizard.registerDisposable(this.installToolsButton.onClick(async () => {
this.wizard.wizardObject.message = null;
this.installToolsButton.label = InstallingButtonText;
this.installToolsButton.enabled = false;
this.refreshToolsButton.enabled = false;
if (this.requiredTools) {
for (let i = 0; i < this.requiredTools.length; i++) {
let tool = this.requiredTools[i];
if (tool.status === ToolInstallationStatus.NotInstalled) {
tool.status = ToolInstallationStatus.Installing;
this.updateToolStatusTable();
await this.wizard.model.installTool(tool);
}
}
}
this.installToolsButton.label = InstallToolsButtonText;
this.updateRequiredToolStatus();
}));
this.wizard.addButton(this.installToolsButton);
this.refreshToolsButton = azdata.window.createButton(localize('bdc-create.RefreshToolsButtonText', 'Refresh Status'));
this.refreshToolsButton.hidden = true;
this.wizard.registerDisposable(this.refreshToolsButton.onClick(() => {
this.updateRequiredToolStatus();
}));
this.wizard.addButton(this.refreshToolsButton);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let self = this;
self.registerNavigationValidator();
return self.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
self.cards = [];
clusterTypes.forEach(clusterType => {
let card = self.createCard(view, clusterType);
self.cards.push(card);
});
let cardsContainer = view.modelBuilder.flexContainer().withItems(self.cards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
self.targetDescriptionText = view.modelBuilder.text().component();
let toolColumn: azdata.TableColumn = {
value: localize('bdc-create.toolNameColumnHeader', 'Tool'),
width: 100
};
let descriptionColumn: azdata.TableColumn = {
value: localize('bdc-create.toolDescriptionColumnHeader', 'Description'),
width: 500
};
let versionColumn: azdata.TableColumn = {
value: localize('bdc-create.toolVersionColumnHeader', 'Version'),
width: 200
};
let statusColumn: azdata.TableColumn = {
value: localize('bdc-create.toolStatusColumnHeader', 'Status'),
width: 200
};
self.toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
height: 150,
data: [],
columns: [toolColumn, descriptionColumn, versionColumn, statusColumn],
width: 1000
}).component();
self.toolsLoadingWrapper = view.modelBuilder.loadingComponent().withItem(self.toolsTable).component();
self.formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: cardsContainer,
title: localize('bdc-create.PickTargetEnvironmentText', 'Pick target environment')
}
],
{
horizontal: false
}
);
self.form = self.formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(self.form);
});
}
public onEnter(): void {
this.installToolsButton.hidden = false;
this.refreshToolsButton.hidden = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = false;
this.registerNavigationValidator();
}
private registerNavigationValidator(): void {
this.wizard.wizardObject.registerNavigationValidator(() => {
if (this.isLoading) {
let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.');
let messageLevel = azdata.window.MessageLevel.Information;
this.wizard.wizardObject.message = {
level: messageLevel,
text: messageText
};
return false;
}
if (!this.isValid) {
let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ?
localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type.') :
localize('bdc-create.MissingToolsText', 'Please install the required tools.');
this.wizard.wizardObject.message = {
level: azdata.window.MessageLevel.Error,
text: messageText
};
}
return this.isValid;
});
}
public onLeave(): void {
this.installToolsButton.hidden = true;
this.refreshToolsButton.hidden = true;
}
private createCard(view: azdata.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): azdata.CardComponent {
let self = this;
let descriptions = targetClusterTypeInfo.enabled ? [] : [localize('bdc-create.ComingSoonText', '(Coming Soon)')];
let card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
cardType: azdata.CardType.VerticalButton,
iconPath: {
dark: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.dark),
light: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.light)
},
label: targetClusterTypeInfo.name,
descriptions: descriptions
}).component();
card.enabled = targetClusterTypeInfo.enabled;
self.wizard.registerDisposable(card.onCardSelectedChanged(() => {
self.onCardSelected(card, targetClusterTypeInfo);
}));
return card;
}
private onCardSelected(card: azdata.CardComponent, targetClusterTypeInfo: TargetClusterTypeInfo): void {
let self = this;
if (card.selected) {
self.wizard.wizardObject.message = null;
self.wizard.model.targetClusterType = targetClusterTypeInfo.type;
self.cards.forEach(c => {
if (c !== card) {
c.selected = false;
}
});
self.targetDescriptionText.value = targetClusterTypeInfo.description;
if (self.form.items.length === 1) {
self.formBuilder.addFormItem({
title: localize('bdc-create.RequiredToolsText', 'Required tools'),
component: self.toolsLoadingWrapper
});
} else {
self.formBuilder.removeFormItem(self.targetDescriptionGroup);
}
self.targetDescriptionGroup = {
title: targetClusterTypeInfo.fullName,
component: self.targetDescriptionText
};
self.formBuilder.insertFormItem(self.targetDescriptionGroup, 1);
self.updateRequiredToolStatus();
} else {
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
card.selected = true;
}
}
}
private updateRequiredToolStatus(): Thenable<void> {
this.isLoading = true;
this.installToolsButton.hidden = false;
this.refreshToolsButton.hidden = false;
this.toolsLoadingWrapper.loading = true;
this.refreshToolsButton.enabled = false;
this.installToolsButton.enabled = false;
return this.wizard.model.getRequiredToolStatus().then(tools => {
this.requiredTools = tools;
this.isLoading = false;
this.toolsLoadingWrapper.loading = false;
this.refreshToolsButton.enabled = true;
this.installToolsButton.enabled = tools.filter(tool => tool.status !== ToolInstallationStatus.Installed).length !== 0;
this.isValid = !this.installToolsButton.enabled;
this.wizard.wizardObject.message = null;
this.updateToolStatusTable();
});
}
private getStatusText(status: ToolInstallationStatus): string {
switch (status) {
case ToolInstallationStatus.Installed:
return '✔️ ' + localize('bdc-create.InstalledText', 'Installed');
case ToolInstallationStatus.NotInstalled:
return '❌ ' + localize('bdc-create.NotInstalledText', 'Not Installed');
case ToolInstallationStatus.Installing:
return '⌛ ' + localize('bdc-create.InstallingText', 'Installing...');
case ToolInstallationStatus.FailedToInstall:
return '❌ ' + localize('bdc-create.FailedToInstallText', 'Install Failed');
default:
return 'unknown status';
}
}
private updateToolStatusTable(): void {
if (this.requiredTools) {
let tableData = this.requiredTools.map(tool => {
return [tool.name, tool.description, tool.version, this.getStatusText(tool.status)];
});
this.toolsTable.data = tableData;
}
}
}

View File

@@ -1,278 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import * as nls from 'vscode-nls';
import { ClusterPorts, ContainerRegistryInfo } from '../../../interfaces';
import { CreateClusterWizard } from '../createClusterWizard';
const localize = nls.loadMessageBundle();
const UserNameInputWidth = '300px';
const PortInputWidth = '100px';
const RestoreDefaultValuesText = localize('bdc-create.RestoreDefaultValuesText', 'Restore Default Values');
export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
private acceptEulaCheckbox: azdata.CheckBoxComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.settingsPageTitle', 'Settings'),
localize('bdc-create.settingsPageDescription', 'Configure the settings required for deploying SQL Server big data cluster'),
wizard);
}
public onEnter(): void {
this.wizard.wizardObject.registerNavigationValidator((e) => {
if (e.lastPage > e.newPage) {
this.wizard.wizardObject.message = null;
return true;
}
if (!this.acceptEulaCheckbox.checked) {
this.wizard.wizardObject.message = {
text: localize('bdc-create.EulaNotAccepted', 'You need to accept the terms of services and privacy policy in order to proceed'),
level: azdata.window.MessageLevel.Error
};
} else {
this.wizard.wizardObject.message = null;
}
return this.acceptEulaCheckbox.checked;
});
}
protected initialize(view: azdata.ModelView): Thenable<void> {
let clusterPorts: ClusterPorts;
let containerRegistryInfo: ContainerRegistryInfo;
let clusterPortsPromise = this.wizard.model.getDefaultPorts().then(ports => {
clusterPorts = ports;
});
let containerRegistryPromise = this.wizard.model.getDefaultContainerRegistryInfo().then(containerRegistry => {
containerRegistryInfo = containerRegistry;
});
return Promise.all([clusterPortsPromise, containerRegistryPromise]).then(() => {
let formBuilder = view.modelBuilder.formContainer();
// User settings
let clusterNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ClusterName', 'Cluster name'),
inputWidth: UserNameInputWidth,
isRequiredField: true
}, (input) => {
this.wizard.model.clusterName = input.value;
});
let adminUserNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.AdminUsernameText', 'Admin username'),
isRequiredField: true,
inputWidth: UserNameInputWidth
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.adminUserName = inputBox.value;
});
let adminPasswordInput = this.createInputWithLabel(view, {
label: localize('bdc-create.AdminUserPasswordText', 'Password'),
isRequiredField: true,
inputType: 'password',
inputWidth: UserNameInputWidth
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.adminPassword = inputBox.value;
});
// Port settings
let sqlPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.SQLPortText', 'SQL Server master'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.sql
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.sqlPort = inputBox.value;
});
let knoxPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.KnoxPortText', 'Knox'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.knox
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.knoxPort = inputBox.value;
});
let controllerPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ControllerPortText', 'Controller'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.controller
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.controllerPort = inputBox.value;
});
let proxyPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ProxyPortText', 'Proxy'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.proxy
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.proxyPort = inputBox.value;
});
let grafanaPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.GrafanaPortText', 'Grafana dashboard'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.grafana
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.grafanaPort = inputBox.value;
});
let kibanaPortInput = this.createInputWithLabel(view, {
label: localize('bdc-create.KibanaPortText', 'Kibana dashboard'),
isRequiredField: true,
inputWidth: PortInputWidth,
initialValue: clusterPorts.kibana
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.kibanaPort = inputBox.value;
});
let restorePortSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: RestoreDefaultValuesText,
width: 200
}).component();
this.wizard.registerDisposable(restorePortSettingsButton.onDidClick(() => {
sqlPortInput.input.value = clusterPorts.sql;
knoxPortInput.input.value = clusterPorts.knox;
controllerPortInput.input.value = clusterPorts.controller;
proxyPortInput.input.value = clusterPorts.proxy;
grafanaPortInput.input.value = clusterPorts.grafana;
kibanaPortInput.input.value = clusterPorts.kibana;
}));
// Container Registry Settings
const registryUserNamePasswordHintText = localize('bdc-create.RegistryUserNamePasswordHintText', 'only required for private registries');
let registryInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryText', 'Registry'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.registry
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistry = inputBox.value;
});
let repositoryInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RepositoryText', 'Repository'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.repository
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRepository = inputBox.value;
});
let imageTagInput = this.createInputWithLabel(view, {
label: localize('bdc-create.ImageTagText', 'Image tag'),
isRequiredField: true,
inputWidth: UserNameInputWidth,
initialValue: containerRegistryInfo.imageTag
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistry = inputBox.value;
});
let registryUserNameInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryUserNameText', 'Username'),
isRequiredField: false,
inputWidth: UserNameInputWidth,
placeHolder: registryUserNamePasswordHintText
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistryUserName = inputBox.value;
});
let registryPasswordInput = this.createInputWithLabel(view, {
label: localize('bdc-create.RegistryPasswordText', 'Password'),
isRequiredField: false,
inputWidth: UserNameInputWidth,
placeHolder: registryUserNamePasswordHintText,
inputType: 'password'
}, (inputBox: azdata.InputBoxComponent) => {
this.wizard.model.containerRegistryPassword = inputBox.value;
});
let restoreContainerSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: RestoreDefaultValuesText,
width: 200
}).component();
this.wizard.registerDisposable(restoreContainerSettingsButton.onDidClick(() => {
registryInput.input.value = containerRegistryInfo.registry;
repositoryInput.input.value = containerRegistryInfo.repository;
imageTagInput.input.value = containerRegistryInfo.imageTag;
}));
let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([clusterNameInput.row, adminUserNameInput.row, adminPasswordInput.row]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component();
let containerSettingsGroup = view.modelBuilder.groupContainer().withItems([registryInput.row, repositoryInput.row, imageTagInput.row, registryUserNameInput.row, registryPasswordInput.row, restoreContainerSettingsButton]).withLayout({ header: localize('bdc-create.ContainerRegistrySettings', 'Container Registry Settings'), collapsible: true }).component();
let portSettingsGroup = view.modelBuilder.groupContainer().withItems([sqlPortInput.row, knoxPortInput.row, controllerPortInput.row, proxyPortInput.row, grafanaPortInput.row, kibanaPortInput.row, restorePortSettingsButton]).withLayout({ header: localize('bdc-create.PortSettings', 'Port Settings (Optional)'), collapsible: true, collapsed: true }).component();
this.acceptEulaCheckbox = view.modelBuilder.checkBox().component();
this.acceptEulaCheckbox.checked = false;
let eulaLink: azdata.LinkArea = {
text: localize('bdc-create.LicenseTerms', 'license terms'),
url: 'https://go.microsoft.com/fwlink/?LinkId=2002534'
};
let privacyPolicyLink: azdata.LinkArea = {
text: localize('bdc-create.PrivacyPolicyText', 'privacy policy'),
url: 'https://go.microsoft.com/fwlink/?LinkId=853010'
};
let checkboxText = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: localize({
key: 'bdc-create.AcceptTermsText',
comment: ['{0} is the place holder for license terms, {1} is the place holder for privacy policy']
}, 'I accept the {0} and {1}.'),
links: [eulaLink, privacyPolicyLink]
}).component();
let eulaContainer = this.createRow(view, [this.acceptEulaCheckbox, checkboxText]);
let form = formBuilder.withFormItems([
{
title: '',
component: eulaContainer
}, {
title: '',
component: basicSettingsGroup
}, {
title: '',
component: containerSettingsGroup
}, {
title: '',
component: portSettingsGroup
}]).component();
return view.initializeModel(form);
});
}
private createInputWithLabel(view: azdata.ModelView, options: {
label: string,
isRequiredField: boolean,
inputWidth: string,
inputType?: string,
initialValue?: string,
placeHolder?: string
}, textChangedHandler: (inputBox: azdata.InputBoxComponent) => void): { row: azdata.FlexContainer, input: azdata.InputBoxComponent } {
let inputType = !!options.inputType ? options.inputType : 'text';
let input = view.modelBuilder.inputBox().withProperties({
required: options.isRequiredField,
inputType: inputType
}).component();
let text = view.modelBuilder.text().withProperties({ value: options.label }).component();
input.width = options.inputWidth;
text.width = '150px';
input.placeHolder = options.placeHolder;
this.wizard.registerDisposable(input.onTextChanged(() => {
textChangedHandler(input);
}));
input.value = options.initialValue;
let row = this.createRow(view, [text, input]);
return {
input: input,
row: row
};
}
private createRow(view: azdata.ModelView, items: azdata.Component[]): azdata.FlexContainer {
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
}
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { CreateClusterWizard } from '../createClusterWizard';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const LabelWidth = '250px';
export class SummaryPage extends WizardPageBase<CreateClusterWizard> {
private view: azdata.ModelView;
private targetTypeText: azdata.TextComponent;
private targetClusterContextText: azdata.TextComponent;
private clusterNameText: azdata.TextComponent;
private clusterAdminUsernameText: azdata.TextComponent;
private acceptEulaText: azdata.TextComponent;
private deploymentProfileText: azdata.TextComponent;
private sqlServerMasterScaleText: azdata.TextComponent;
private storagePoolScaleText: azdata.TextComponent;
private computePoolScaleText: azdata.TextComponent;
private dataPoolScaleText: azdata.TextComponent;
private sparkPoolScaleText: azdata.TextComponent;
constructor(wizard: CreateClusterWizard) {
super(localize('bdc-create.summaryPageTitle', 'Summary'), '', wizard);
}
protected initialize(view: azdata.ModelView): Thenable<void> {
this.view = view;
let targetClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
let bdcClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this.targetTypeText = this.view.modelBuilder.text().component();
this.targetClusterContextText = this.view.modelBuilder.text().component();
this.clusterNameText = this.view.modelBuilder.text().component();
this.clusterAdminUsernameText = this.view.modelBuilder.text().component();
this.acceptEulaText = this.view.modelBuilder.text().component();
this.deploymentProfileText = this.view.modelBuilder.text().component();
this.sqlServerMasterScaleText = this.view.modelBuilder.text().component();
this.storagePoolScaleText = this.view.modelBuilder.text().component();
this.computePoolScaleText = this.view.modelBuilder.text().component();
this.dataPoolScaleText = this.view.modelBuilder.text().component();
this.sparkPoolScaleText = this.view.modelBuilder.text().component();
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.TargetClusterTypeText', 'Cluster type'), this.targetTypeText));
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterContextText', 'Cluster context'), this.targetClusterContextText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterNameText', 'Cluster name'), this.clusterNameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterAdminUsernameText', 'Cluster Admin username'), this.clusterAdminUsernameText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.AcceptEulaText', 'Accept license agreement'), this.acceptEulaText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DeploymentProfileText', 'Deployment profile'), this.deploymentProfileText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SqlServerMasterScaleText', 'SQL Server master scale'), this.sqlServerMasterScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ComputePoolScaleText', 'Compute pool scale'), this.computePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DataPoolScaleText', 'Data pool scale'), this.dataPoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.StoragePoolScaleText', 'Storage pool scale'), this.storagePoolScaleText));
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SparkPoolScaleText', 'Spark pool scale'), this.sparkPoolScaleText));
let formBuilder = view.modelBuilder.formContainer();
let form = formBuilder.withFormItems([{
title: localize('bdc-create.TargetClusterGroupTitle', 'TARGET CLUSTER'),
component: targetClusterInfoGroup
}, {
title: localize('bdc-create.BigDataClusterGroupTitle', 'SQL SERVER BIG DATA CLUSTER'),
component: bdcClusterInfoGroup
}]).component();
return view.initializeModel(form);
}
public onEnter(): void {
this.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
let selectedClusterType = clusterTypes.filter(clusterType => clusterType.type === this.wizard.model.targetClusterType)[0];
this.targetTypeText.value = selectedClusterType.fullName;
this.targetClusterContextText.value = this.wizard.model.selectedCluster.contextName;
this.clusterNameText.value = this.wizard.model.clusterName;
this.clusterAdminUsernameText.value = this.wizard.model.adminUserName;
this.acceptEulaText.value = localize('bdc-create.YesText', 'Yes');
this.deploymentProfileText.value = this.wizard.model.profile.name;
this.sqlServerMasterScaleText.value = this.wizard.model.profile.sqlServerMasterConfiguration.scale.toString();
this.computePoolScaleText.value = this.wizard.model.profile.computePoolConfiguration.scale.toString();
this.dataPoolScaleText.value = this.wizard.model.profile.dataPoolConfiguration.scale.toString();
this.storagePoolScaleText.value = this.wizard.model.profile.storagePoolConfiguration.scale.toString();
this.sparkPoolScaleText.value = this.wizard.model.profile.sparkPoolConfiguration.scale.toString();
});
this.wizard.wizardObject.generateScriptButton.hidden = false;
}
public onLeave(): void {
this.wizard.wizardObject.generateScriptButton.hidden = true;
}
private createRow(label: string, textComponent: azdata.TextComponent): azdata.FlexContainer {
let row = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'baseline' }).component();
let labelComponent = this.view.modelBuilder.text().withProperties({ value: label }).component();
labelComponent.width = LabelWidth;
textComponent.width = LabelWidth;
row.addItems([labelComponent, textComponent]);
return row;
}
}

View File

@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { ExtensionContext, Disposable } from 'vscode';
import { WizardPageBase } from './wizardPageBase';
export abstract class WizardBase<T, W> {
public wizardObject: azdata.window.Wizard;
private customButtons: azdata.window.Button[];
private pages: WizardPageBase<W>[];
private toDispose: Disposable[] = [];
constructor(public model: T, public context: ExtensionContext, private title: string) {
this.customButtons = [];
}
public open(): Thenable<void> {
this.wizardObject = azdata.window.createWizard(this.title);
this.initialize();
this.wizardObject.customButtons = this.customButtons;
this.toDispose.push(this.wizardObject.onPageChanged((e) => {
let previousPage = this.pages[e.lastPage];
let newPage = this.pages[e.newPage];
previousPage.onLeave();
newPage.onEnter();
}));
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
this.onOk();
this.dispose();
}));
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
this.onCancel();
this.dispose();
}));
return this.wizardObject.open().then(() => {
if (this.pages && this.pages.length > 0) {
this.pages[0].onEnter();
}
});
}
protected abstract initialize(): void;
protected abstract onOk(): void;
protected abstract onCancel(): void;
public addButton(button: azdata.window.Button) {
this.customButtons.push(button);
}
protected setPages(pages: WizardPageBase<W>[]) {
this.wizardObject.pages = pages.map(p => p.pageObject);
this.pages = pages;
}
private dispose() {
this.toDispose.forEach((disposable: Disposable) => {
try {
disposable.dispose();
}
catch{ }
});
}
public registerDisposable(disposable: Disposable): void {
this.toDispose.push(disposable);
}
}

View File

@@ -1,33 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
export abstract class WizardPageBase<T> {
private _page: azdata.window.WizardPage;
public get pageObject(): azdata.window.WizardPage {
return this._page;
}
public get wizard(): T {
return this._wizard;
}
constructor(title: string, description: string, private _wizard: T) {
this._page = azdata.window.createWizardPage(title);
this._page.description = description;
this._page.registerContent((view: azdata.ModelView) => {
return this.initialize(view);
});
}
protected abstract initialize(view: azdata.ModelView): Thenable<void>;
public onEnter(): void { }
public onLeave(): void { }
}

View File

@@ -1,15 +1,23 @@
{
"extends": "../shared.tsconfig.json",
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "./out",
"strict": false,
"alwaysStrict": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noUnusedLocals": false,
"noUnusedParameters": false
"lib": [
"es6",
"es2015.promise"
],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"declaration": false,
"typeRoots": [
"./node_modules/@types"
]
},
"include": [
"src/**/*"
"exclude": [
"node_modules"
]
}
}

File diff suppressed because it is too large Load Diff