Remove all Big Data Cluster features (#21369)
@@ -24,8 +24,6 @@
|
||||
**/node_modules/**
|
||||
**/extensions/**/out/**
|
||||
**/extensions/**/build/**
|
||||
/extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
|
||||
/extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts
|
||||
**/extensions/**/colorize-fixtures/**
|
||||
**/extensions/html-language-features/server/lib/jquery.d.ts
|
||||
/extensions/markdown-language-features/media/**
|
||||
|
||||
@@ -1147,7 +1147,6 @@
|
||||
"extensions/azuremonitor/src/prompts/**",
|
||||
"extensions/azuremonitor/src/typings/findRemove.d.ts",
|
||||
"extensions/kusto/src/prompts/**",
|
||||
"extensions/mssql/src/hdfs/webhdfs.ts",
|
||||
"extensions/mssql/src/prompts/**",
|
||||
"extensions/mssql/src/typings/bufferStreamReader.d.ts",
|
||||
"extensions/mssql/src/typings/findRemove.d.ts",
|
||||
|
||||
1
.github/CODEOWNERS
vendored
@@ -6,7 +6,6 @@
|
||||
/extensions/arc/ @Charles-Gagnon @swells @candiceye
|
||||
/extensions/azcli/ @Charles-Gagnon @swells @candiceye
|
||||
/extensions/azurecore/ @cssuh @cheenamalhotra
|
||||
/extensions/big-data-cluster/ @Charles-Gagnon
|
||||
/extensions/dacpac/ @kisantia
|
||||
/extensions/notebook @azure-data-studio-notebook-devs
|
||||
/extensions/query-history/ @Charles-Gagnon
|
||||
|
||||
@@ -41,7 +41,7 @@ steps:
|
||||
inputs:
|
||||
azureSubscription: 'ClientToolsInfra_670062 (88d5392f-a34f-4769-b405-f597fc533613)'
|
||||
KeyVaultName: SqlToolsSecretStore
|
||||
SecretsFilter: 'ads-integration-test-azure-server,ads-integration-test-azure-server-password,ads-integration-test-azure-server-username,ads-integration-test-bdc-server,ads-integration-test-bdc-server-password,ads-integration-test-bdc-server-username,ads-integration-test-standalone-server,ads-integration-test-standalone-server-password,ads-integration-test-standalone-server-username,ads-integration-test-standalone-server-2019,ads-integration-test-standalone-server-password-2019,ads-integration-test-standalone-server-username-2019'
|
||||
SecretsFilter: 'ads-integration-test-azure-server,ads-integration-test-azure-server-password,ads-integration-test-azure-server-username,ads-integration-test-standalone-server,ads-integration-test-standalone-server-password,ads-integration-test-standalone-server-username,ads-integration-test-standalone-server-2019,ads-integration-test-standalone-server-password-2019,ads-integration-test-standalone-server-username-2019'
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
@@ -54,9 +54,6 @@ steps:
|
||||
condition: and(succeeded(), and(eq(variables['RUN_TESTS'], 'true'), ne(variables['RUN_INTEGRATION_TESTS'], 'false')))
|
||||
displayName: Run stable tests
|
||||
env:
|
||||
BDC_BACKEND_USERNAME: $(ads-integration-test-bdc-server-username)
|
||||
BDC_BACKEND_PWD: $(ads-integration-test-bdc-server-password)
|
||||
BDC_BACKEND_HOSTNAME: $(ads-integration-test-bdc-server)
|
||||
STANDALONE_SQL_USERNAME: $(ads-integration-test-standalone-server-username)
|
||||
STANDALONE_SQL_PWD: $(ads-integration-test-standalone-server-password)
|
||||
STANDALONE_SQL: $(ads-integration-test-standalone-server)
|
||||
|
||||
@@ -152,8 +152,6 @@ module.exports.indentationFilter = [
|
||||
'!extensions/sql-database-projects/src/test/baselines/*.json',
|
||||
'!extensions/sql-database-projects/src/test/baselines/*.sqlproj',
|
||||
'!extensions/sql-database-projects/BuildDirectory/SystemDacpacs/**',
|
||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts',
|
||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts',
|
||||
'!resources/linux/snap/electron-launch',
|
||||
'!extensions/markdown-language-features/media/*.js',
|
||||
'!extensions/simple-browser/media/*.js',
|
||||
@@ -202,7 +200,6 @@ module.exports.copyrightFilter = [
|
||||
'!extensions/import/flatfileimportservice/**',
|
||||
'!extensions/kusto/src/prompts/**',
|
||||
'!extensions/mssql/sqltoolsservice/**',
|
||||
'!extensions/mssql/src/hdfs/webhdfs.ts',
|
||||
'!extensions/mssql/src/prompts/**',
|
||||
'!extensions/notebook/resources/jupyter_config/**',
|
||||
'!extensions/notebook/src/intellisense/text.ts',
|
||||
@@ -257,8 +254,6 @@ module.exports.tsFormattingFilter = [
|
||||
'!extensions/html-language-features/server/lib/jquery.d.ts',
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts',
|
||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/tokenApiGenerated.ts',
|
||||
'!src/vs/workbench/services/themes/common/textMateScopeMatcher.ts', // skip this because we have no plans on touching this and its not ours
|
||||
'!src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts', // skip this because known issue
|
||||
'!build/**/*'
|
||||
|
||||
@@ -448,7 +448,7 @@ function packagePkgTask(platform, arch, pkgTarget) {
|
||||
// rebuild extensions that contain native npm modules or have conditional webpack rules
|
||||
// when building with the web .yarnrc settings (e.g. runtime=node, etc.)
|
||||
// this is needed to have correct module set published with desired ABI
|
||||
const rebuildExtensions = ['big-data-cluster', 'mssql', 'notebook'];
|
||||
const rebuildExtensions = ['mssql', 'notebook'];
|
||||
const EXTENSIONS = path.join(REPO_ROOT, 'extensions');
|
||||
function exec(cmdLine, cwd) {
|
||||
console.log(cmdLine);
|
||||
|
||||
@@ -125,7 +125,6 @@ const extensionsFilter = filter([
|
||||
'**/azcli.xlf',
|
||||
'**/azurecore.xlf',
|
||||
'**/azurehybridtoolkit.xlf',
|
||||
'**/big-data-cluster.xlf',
|
||||
'**/cms.xlf',
|
||||
'**/dacpac.xlf',
|
||||
'**/git.xlf',
|
||||
|
||||
@@ -328,7 +328,6 @@ export const vscodeExternalExtensions = [
|
||||
|
||||
// extensions that require a rebuild since they have native parts
|
||||
const rebuildExtensions = [
|
||||
'big-data-cluster',
|
||||
'mssql'
|
||||
];
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ exports.dirs = [
|
||||
'extensions/azurecore',
|
||||
'extensions/azurehybridtoolkit',
|
||||
'extensions/azuremonitor',
|
||||
'extensions/big-data-cluster',
|
||||
'extensions/cms',
|
||||
'extensions/configuration-editing',
|
||||
'extensions/dacpac',
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"project": "./extensions/big-data-cluster/tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
// Disabled until the issues can be fixed
|
||||
"@typescript-eslint/explicit-function-return-type": ["off"]
|
||||
}
|
||||
}
|
||||
1
extensions/big-data-cluster/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.vsix
|
||||
@@ -1,14 +0,0 @@
|
||||
.gitignore
|
||||
instructions.txt
|
||||
src/**
|
||||
out/**
|
||||
extension.webpack.config.js
|
||||
tsconfig.json
|
||||
yarn.lock
|
||||
|
||||
node_modules
|
||||
!node_modules/@microsoft/ads-kerberos/package.json
|
||||
!node_modules/@microsoft/ads-kerberos/LICENSE
|
||||
!node_modules/@microsoft/ads-kerberos/lib
|
||||
!node_modules/@microsoft/ads-kerberos/index.js
|
||||
!node_modules/@microsoft/ads-kerberos/build/Release/kerberos.node
|
||||
@@ -1,17 +0,0 @@
|
||||
# Microsoft SQL Server Big Data Cluster Extension for Azure Data Studio
|
||||
|
||||
Welcome to Microsoft SQL Server Big Data Cluster Extension for Azure Data Studio!
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Privacy Statement
|
||||
|
||||
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/privacystatement) describes the privacy statement of this software.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt).
|
||||
@@ -1,20 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts'
|
||||
},
|
||||
externals: {
|
||||
'@microsoft/ads-kerberos': 'commonjs @microsoft/ads-kerberos'
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,38 +0,0 @@
|
||||
<svg id="a8b9457a-0277-4e4a-af7b-2a35817c767b" data-name="icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="acf91304-a8c9-4c1f-b6a5-36f9b8128409" x1="2.59" y1="-314.16" x2="15.41" y2="-314.16" gradientTransform="matrix(1, 0, 0, -1, 0, -304)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#005ba1"/>
|
||||
<stop offset="0.15" stop-color="#0078d4"/>
|
||||
<stop offset="0.242" stop-color="#1c84dc"/>
|
||||
<stop offset="0.414" stop-color="#4c98ea"/>
|
||||
<stop offset="0.5" stop-color="#5ea0ef"/>
|
||||
<stop offset="0.586" stop-color="#4c98ea"/>
|
||||
<stop offset="0.758" stop-color="#1c84dc"/>
|
||||
<stop offset="0.85" stop-color="#0078d4"/>
|
||||
<stop offset="1" stop-color="#005ba1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="ea6a9244-85f4-45fd-be4c-117e3f7d4184" x1="93.192" y1="-20.889" x2="94.554" y2="-19.014" gradientTransform="translate(-76.373 -31.311) rotate(36)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.19" stop-color="#9cebff"/>
|
||||
<stop offset="0.91" stop-color="#50e6ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b24253bf-9577-4f4b-8c38-5fd96995c4b7" x1="-75.25" y1="-20.085" x2="-76.612" y2="-18.21" gradientTransform="translate(79.811 -20.731) rotate(-36)" xlink:href="#ea6a9244-85f4-45fd-be4c-117e3f7d4184"/>
|
||||
<linearGradient id="ba4dd4c3-07ad-4d29-9073-416b60bf6972" x1="9.026" y1="-279.302" x2="9.026" y2="-281.619" gradientTransform="translate(18 -266.363) rotate(180)" xlink:href="#ea6a9244-85f4-45fd-be4c-117e3f7d4184"/>
|
||||
<linearGradient id="f7bc97d7-e820-4bb7-bdc1-4286127d66c0" x1="-127.278" y1="-180.991" x2="-129.482" y2="-181.707" gradientTransform="translate(138.136 -166.001) rotate(-108)" xlink:href="#ea6a9244-85f4-45fd-be4c-117e3f7d4184"/>
|
||||
<linearGradient id="b0b2087b-b12e-437b-a3ec-e7ad6bbea89b" x1="145.294" y1="-180.259" x2="147.498" y2="-180.975" gradientTransform="translate(-114.574 -183.12) rotate(108)" xlink:href="#ea6a9244-85f4-45fd-be4c-117e3f7d4184"/>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M9,5.14c-3.54,0-6.41-1-6.41-2.32V15.18c0,1.27,2.82,2.3,6.32,2.32H9c3.54,0,6.41-1,6.41-2.32V2.82C15.41,4.11,12.54,5.14,9,5.14Z" fill="url(#acf91304-a8c9-4c1f-b6a5-36f9b8128409)"/>
|
||||
<path d="M15.41,2.82c0,1.29-2.87,2.32-6.41,2.32s-6.41-1-6.41-2.32S5.46.5,9,.5s6.41,1,6.41,2.32" fill="#e8e8e8"/>
|
||||
<path d="M13.92,2.63c0,.82-2.21,1.48-4.92,1.48S4.08,3.45,4.08,2.63,6.29,1.16,9,1.16s4.92.66,4.92,1.47" fill="#b3b3b3"/>
|
||||
<path d="M9,3a11.541,11.541,0,0,0-3.89.57A11.428,11.428,0,0,0,9,4.11a11.149,11.149,0,0,0,3.89-.58A11.839,11.839,0,0,0,9,3Z" fill="#767676"/>
|
||||
</g>
|
||||
<polyline points="11.299 7.724 10.806 8.41 8.974 10.953 7.143 8.41" fill="none" stroke="#f2f2f2" stroke-miterlimit="10" stroke-width="0.75"/>
|
||||
<circle cx="11.299" cy="7.724" r="1.158" fill="url(#ea6a9244-85f4-45fd-be4c-117e3f7d4184)"/>
|
||||
<circle cx="7.126" cy="8.41" r="1.158" fill="url(#b24253bf-9577-4f4b-8c38-5fd96995c4b7)"/>
|
||||
<line x1="8.974" y1="10.953" x2="8.974" y2="14.097" fill="none" stroke="#f2f2f2" stroke-miterlimit="10" stroke-width="0.75"/>
|
||||
<polyline points="8.974 10.953 5.984 11.925 5.335 12.136" fill="none" stroke="#f2f2f2" stroke-miterlimit="10" stroke-width="0.75"/>
|
||||
<line x1="11.964" y1="11.925" x2="8.974" y2="10.953" fill="none" stroke="#f2f2f2" stroke-miterlimit="10" stroke-width="0.75"/>
|
||||
<circle cx="8.974" cy="14.097" r="1.158" fill="url(#ba4dd4c3-07ad-4d29-9073-416b60bf6972)"/>
|
||||
<circle cx="5.335" cy="12.136" r="1.158" fill="url(#f7bc97d7-e820-4bb7-bdc1-4286127d66c0)"/>
|
||||
<circle cx="11.964" cy="11.925" r="1.158" fill="url(#b0b2087b-b12e-437b-a3ec-e7ad6bbea89b)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,25 +0,0 @@
|
||||
How to update the Swagger-generated API to contact the controller
|
||||
|
||||
## BdcRouter API:
|
||||
1. You need to get the API specification. Long-term you should be able to get from the server,
|
||||
but for now go to the internal repository and find the checked in SwaggerClient.yaml there.
|
||||
|
||||
2. Copy the content from there, and add into https://editor.swagger.io/
|
||||
3. Choose Generate Client, and choose Typescript-Node as the client to generate
|
||||
4. This will download a zip file. Open it and copy contents of api.ts
|
||||
5. Copy this content to apiGenerated.ts
|
||||
- keep the copyright header and everything above the let defaultBasePath = xyz line,
|
||||
- Override the rest of the file
|
||||
6. Format the apiGenerated.ts file so it passes gulp hygiene
|
||||
|
||||
## TokenRouter and other APIs:
|
||||
1. Get the API spec. This is available from a cluster at the address https://<ip>:30080/docs/swagger.json, where <ip> is the controller IP address.
|
||||
2. Copy the content from there, and add convert from OpenApi 3.0 to Swagger 2.0 so we can use the Typescript-Node client generated by https://editor.swagger.io/.
|
||||
Various converter tools are online. Alternatively, we might be able to use a different generator that has this client type (e.g. npm package @openapitools/openapi-generator-cli) but some require Java install.
|
||||
3. Copy the converted Swagger 2.0 spec into https://editor.swagger.io/
|
||||
4. Choose Generate Client, and choose Typescript-Node as the client to generate
|
||||
5. This will download a zip file. Open it and copy contents of api.ts
|
||||
6. Copy this content to tokenApiGenerated.ts
|
||||
- keep the copyright header and everything above the let defaultBasePath = xyz line,
|
||||
- Override the rest of the file
|
||||
7. Format the tokenApiGenerated.ts file so it passes gulp hygiene
|
||||
@@ -1,430 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
" \n",
|
||||
"## Create Azure Kubernetes Service cluster and deploy SQL Server 2019 Big Data Cluster\n",
|
||||
" \n",
|
||||
"This notebook walks through the process of creating a new Azure Kubernetes Service cluster first, and then deploys a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on the newly created AKS cluster.\n",
|
||||
" \n",
|
||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||
"* The **Required information** will check and prompt you for password if it is not set in the environment variable. The password will be used to access the cluster controller, SQL Server, and Knox.\n",
|
||||
"\n",
|
||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4f6bc3bc-3592-420a-b534-384011189005"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Prerequisites**\n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
"\n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|Azure CLI |Command-line tool for managing Azure services. Used to create AKS cluster | [Installation](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) |\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d949980e-ad3f-4d02-ae84-7e4fbb19a087"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Setup**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a56d3413-a730-4997-b5c2-c8abd972757e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import pandas,sys,os,json,html,getpass,time\n",
|
||||
"pandas_version = pandas.__version__.split('.')\n",
|
||||
"pandas_major = int(pandas_version[0])\n",
|
||||
"pandas_minor = int(pandas_version[1])\n",
|
||||
"pandas_patch = int(pandas_version[2])\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
|
||||
" sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
" if _exit_code != 0:\n",
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "326645cf-022a-47f2-8aff-37de71da8955",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set variables**\n",
|
||||
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "8716915b-1439-431b-ab0a-0221ef94cb7f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Check dependencies**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "db8b1e21-eb2c-4c35-b973-bc4ef38bb1d0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command('kubectl version --client=true')\n",
|
||||
"run_command('azdata --version')\n",
|
||||
"run_command('az --version')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9361deaf-28b1-4d02-912d-2011cae97e8a",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Required information**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "720c200c-322a-49dd-9aa3-8bf7946aa251"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
|
||||
"if invoked_by_wizard:\n",
|
||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
|
||||
"else:\n",
|
||||
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
|
||||
" if mssql_password == \"\":\n",
|
||||
" sys.exit(f'Password is required.')\n",
|
||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||
" if mssql_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
"print('You can also use the controller password to access Knox and SQL Server.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "17e5d087-7128-4d02-8c16-fe1ddee675e5",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to Azure**\n",
|
||||
"\n",
|
||||
"This will open a web browser window to enable credentials to be entered. If this cells is hanging forever, it might be because your Web browser windows is waiting for you to enter your Azure credentials!\n",
|
||||
""
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "baddf2d9-93ee-4c42-aaf1-b42116bb1912"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'az login')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "8f1404a6-216d-49fb-b6ad-81beeea50083",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
"### **Set active Azure subscription**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "230dc0f1-bf6e-474a-bfaa-aae6f8aad12e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"if azure_subscription_id != \"\":\n",
|
||||
" run_command(f'az account set --subscription {azure_subscription_id}')\n",
|
||||
"else:\n",
|
||||
" print('Using the default Azure subscription', {azure_subscription_id})\n",
|
||||
"run_command(f'az account show')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "ab230931-2e99-483b-a229-3847684a8c1c",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create Azure resource group**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d51db914-f484-489f-990d-72edb3065068"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'az group create --name {azure_resource_group} --location {azure_region}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7c53eb23-c327-41bf-8936-bd34a02ebdd5",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create AKS cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "818eb705-71e2-4013-8420-44886a5468b2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'az aks create --name {aks_cluster_name} --resource-group {azure_resource_group} --generate-ssh-keys --node-vm-size {azure_vm_size} --node-count {azure_vm_count}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3cea1da0-0c18-4030-a5aa-79bc98a5a14d",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set the new AKS cluster as current context**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "5ade8453-5e71-478f-b6b6-83c55626243d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'az aks get-credentials --resource-group {azure_resource_group} --name {aks_cluster_name} --admin --overwrite-existing')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9ccb9adf-1cf6-4dcb-8bd9-7ae9a85c2437",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create deployment configuration files**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "57eb69fb-c68f-4ba8-818d-ffbaa0bc7aec"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
|
||||
"if not os.path.exists(mssql_target_profile):\n",
|
||||
" os.mkdir(mssql_target_profile)\n",
|
||||
"bdcJsonObj = json.loads(bdc_json)\n",
|
||||
"controlJsonObj = json.loads(control_json)\n",
|
||||
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
|
||||
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
|
||||
"bdcJsonFile.close()\n",
|
||||
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
|
||||
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
|
||||
"controlJsonFile.close()\n",
|
||||
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3fd73c04-8a79-4d08-9049-1dad30265558",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6e82fad8-0fd0-4952-87ce-3fea1edd98cb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
|
||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
||||
"if os.name == 'nt':\n",
|
||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t{os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {mssql_cluster_name} ')\n",
|
||||
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c43ea026-ca5e-4e2a-8602-fcc786354168",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9c5428f4-08b9-4799-a35d-867c91dc29fb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'azdata login --namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "5120c387-1088-435b-856e-e59f147c45a2",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Show SQL Server 2019 Big Data Cluster endpoints**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "97974eda-e108-4c21-a58e-c6bb58f14ef1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from IPython.display import *\n",
|
||||
"pandas.set_option('display.max_colwidth', -1)\n",
|
||||
"cmd = f'azdata bdc endpoint list'\n",
|
||||
"cmdOutput = !{cmd}\n",
|
||||
"endpoints = json.loads(''.join(cmdOutput))\n",
|
||||
"endpointsDataFrame = pandas.DataFrame(endpoints)\n",
|
||||
"endpointsDataFrame.columns = [' '.join(word[0].upper() + word[1:] for word in columnName.split()) for columnName in endpoints[0].keys()]\n",
|
||||
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9a5d0aef-a8da-4845-b470-d714435f0304",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Connect to SQL Server Master instance in Azure Data Studio**\n",
|
||||
"Click the link below to connect to the SQL Server Master instance of the SQL Server 2019 Big Data Cluster."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4a49b629-bd7a-43ba-bf18-6cdc0737b0f9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
|
||||
"if sqlEndpoints and len(sqlEndpoints) == 1:\n",
|
||||
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
|
||||
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
|
||||
" display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))\n",
|
||||
"else:\n",
|
||||
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1c9d1f2c-62ba-4070-920a-d30b67bdcc7c",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
" \n",
|
||||
"## Deploy SQL Server 2019 Big Data Cluster on an existing Azure Kubernetes Service (AKS) cluster\n",
|
||||
" \n",
|
||||
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing AKS cluster.\n",
|
||||
" \n",
|
||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||
"* The **Required information** will check and prompt you for password if it is not set in the environment variable. The password can be used to access the cluster controller, SQL Server, and Knox.\n",
|
||||
"\n",
|
||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "82e60c1a-7acf-47ee-877f-9e85e92e11da"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Prerequisites** \n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
" \n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "714582b9-10ee-409e-ab12-15a4825c9471"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Setup**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e3dd8e75-e15f-44b4-81fc-1f54d6f0b1e2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import pandas,sys,os,json,html,getpass,time\n",
|
||||
"pandas_version = pandas.__version__.split('.')\n",
|
||||
"pandas_major = int(pandas_version[0])\n",
|
||||
"pandas_minor = int(pandas_version[1])\n",
|
||||
"pandas_patch = int(pandas_version[2])\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
|
||||
" sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
" if _exit_code != 0:\n",
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set variables**\n",
|
||||
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Check dependencies**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "2544648b-59c9-4ce5-a3b6-87086e214d4c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command('kubectl version --client=true')\n",
|
||||
"run_command('azdata --version')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "691671d7-3f05-406c-a183-4cff7d17f83d",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Required information**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0bb02e76-fee8-4dbc-a75b-d5b9d1b187d0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
|
||||
"if invoked_by_wizard:\n",
|
||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
|
||||
"else:\n",
|
||||
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
|
||||
" if mssql_password == \"\":\n",
|
||||
" sys.exit(f'Password is required.')\n",
|
||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||
" if mssql_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
"print('You can also use the controller password to access Knox and SQL Server.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set and show current context**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "127c8042-181f-4862-a390-96e59c181d09"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
|
||||
"run_command('kubectl config current-context')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create deployment configuration files**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "138536c3-1db6-428f-9e5c-8269a02fb52e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
|
||||
"if not os.path.exists(mssql_target_profile):\n",
|
||||
" os.mkdir(mssql_target_profile)\n",
|
||||
"bdcJsonObj = json.loads(bdc_json)\n",
|
||||
"controlJsonObj = json.loads(control_json)\n",
|
||||
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
|
||||
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
|
||||
"bdcJsonFile.close()\n",
|
||||
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
|
||||
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
|
||||
"controlJsonFile.close()\n",
|
||||
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "2ff82c8a-4bce-449c-9d91-3ac7dd272021",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "efe78cd3-ed73-4c9b-b586-fdd6c07dd37f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
|
||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
||||
"if os.name == 'nt':\n",
|
||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t{os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {mssql_cluster_name} ')\n",
|
||||
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4e026d39-12d4-4c80-8e30-de2b782f2110"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'azdata login --namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "79adda27-371d-4dcb-b867-db025f8162a5",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Show SQL Server 2019 Big Data Cluster endpoints**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c1921288-ad11-40d8-9aea-127a722b3df8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from IPython.display import *\n",
|
||||
"pandas.set_option('display.max_colwidth', -1)\n",
|
||||
"cmd = f'azdata bdc endpoint list'\n",
|
||||
"cmdOutput = !{cmd}\n",
|
||||
"endpoints = json.loads(''.join(cmdOutput))\n",
|
||||
"endpointsDataFrame = pandas.DataFrame(endpoints)\n",
|
||||
"endpointsDataFrame.columns = [' '.join(word[0].upper() + word[1:] for word in columnName.split()) for columnName in endpoints[0].keys()]\n",
|
||||
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a2202494-fd6c-4534-987d-15c403a5096f",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Connect to SQL Server Master instance in Azure Data Studio**\n",
|
||||
"Click the link below to connect to the SQL Server Master instance of the SQL Server 2019 Big Data Cluster."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "621863a2-aa61-46f4-a9d0-717f41c009ee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
|
||||
"if sqlEndpoints and len(sqlEndpoints) == 1:\n",
|
||||
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
|
||||
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
|
||||
" display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))\n",
|
||||
"else:\n",
|
||||
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "48342355-9d2b-4fa6-b1aa-3bc77d434dfa",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
" \n",
|
||||
"## Deploy SQL Server 2019 Big Data Cluster on an existing Azure Red Hat OpenShift cluster\n",
|
||||
" \n",
|
||||
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing Azure Red Hat OpenShift cluster.\n",
|
||||
" \n",
|
||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||
"* The **Required information** will check and prompt you for password if it is not set in the environment variable. The password can be used to access the cluster controller, SQL Server, and Knox.\n",
|
||||
"\n",
|
||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "23954d96-3932-4a8e-ab73-da605f99b1a4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Prerequisites** \n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
" \n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1d7f4c6a-0cb8-4ecc-81c8-544712253a3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Setup**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a31f9894-903f-4e19-a5a8-6fd888ff013b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import pandas,sys,os,json,html,getpass,time\n",
|
||||
"pandas_version = pandas.__version__.split('.')\n",
|
||||
"pandas_major = int(pandas_version[0])\n",
|
||||
"pandas_minor = int(pandas_version[1])\n",
|
||||
"pandas_patch = int(pandas_version[2])\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
|
||||
" sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
" if _exit_code != 0:\n",
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set variables**\n",
|
||||
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e70640d0-6059-4cab-939e-e985a978c0da"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Check dependencies**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "869d0397-a280-4dc4-be76-d652189b5131"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command('kubectl version --client=true')\n",
|
||||
"run_command('azdata --version')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c38afb67-1132-495e-9af1-35bf067acbeb",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Required information**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7b383b0d-5687-45b3-a16f-ba3b170c796e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
|
||||
"if invoked_by_wizard:\n",
|
||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = os.environ[\"AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD\"]\n",
|
||||
"else:\n",
|
||||
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
|
||||
" if mssql_password == \"\":\n",
|
||||
" sys.exit(f'Password is required.')\n",
|
||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||
" if mssql_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = getpass.getpass(prompt = 'Domain service account password')\n",
|
||||
" if mssql_domain_service_account_password == \"\":\n",
|
||||
" sys.exit(f'Domain service account password is required.')\n",
|
||||
"print('You can also use the controller password to access Knox and SQL Server.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set and show current context**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6456bd0c-5b64-4d76-be59-e3a5b32697f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
|
||||
"run_command('kubectl config current-context')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a38f8b3a-f93a-484c-b9e2-4eba3ed99cc2"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Make sure the target namespace already exists**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3bf1d902-2217-4c99-b2d6-38e45de8e308"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl get namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6ca9bf71-049a-458e-8000-311d4c15b1ca"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create deployment configuration files**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6d78da36-6af5-4309-baad-bc81bb2cdb7f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
|
||||
"if not os.path.exists(mssql_target_profile):\n",
|
||||
" os.mkdir(mssql_target_profile)\n",
|
||||
"bdcJsonObj = json.loads(bdc_json)\n",
|
||||
"controlJsonObj = json.loads(control_json)\n",
|
||||
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
|
||||
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
|
||||
"bdcJsonFile.close()\n",
|
||||
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
|
||||
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
|
||||
"controlJsonFile.close()\n",
|
||||
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7d56d262-8cd5-49e4-b745-332c6e7a3cb2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
|
||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
||||
"if mssql_auth_mode == \"ad\":\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_USERNAME\"] = mssql_domain_service_account_username\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_PASSWORD\"] = mssql_domain_service_account_password\n",
|
||||
"if os.name == 'nt':\n",
|
||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t{os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {mssql_cluster_name} ')\n",
|
||||
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7929fd90-324d-482a-a101-ae29cb183691"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'azdata login --namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Show SQL Server 2019 Big Data Cluster endpoints**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "038e801a-a393-4f8d-8e2d-97bc3b740b0c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from IPython.display import *\n",
|
||||
"pandas.set_option('display.max_colwidth', -1)\n",
|
||||
"cmd = f'azdata bdc endpoint list'\n",
|
||||
"cmdOutput = !{cmd}\n",
|
||||
"endpoints = json.loads(''.join(cmdOutput))\n",
|
||||
"endpointsDataFrame = pandas.DataFrame(endpoints)\n",
|
||||
"endpointsDataFrame.columns = [' '.join(word[0].upper() + word[1:] for word in columnName.split()) for columnName in endpoints[0].keys()]\n",
|
||||
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Connect to SQL Server Master instance in Azure Data Studio**\n",
|
||||
"Click the link below to connect to the SQL Server Master instance of the SQL Server 2019 Big Data Cluster."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0bd809fa-8225-4954-a50c-da57ea167896"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
|
||||
"if sqlEndpoints and len(sqlEndpoints) == 1:\n",
|
||||
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
|
||||
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
|
||||
" display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))\n",
|
||||
"else:\n",
|
||||
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
" \n",
|
||||
"## Deploy SQL Server 2019 Big Data Cluster on an existing cluster deployed using kubeadm\n",
|
||||
" \n",
|
||||
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing kubeadm cluster.\n",
|
||||
" \n",
|
||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||
"* The **Required information** will check and prompt you for password if it is not set in the environment variable. The password can be used to access the cluster controller, SQL Server, and Knox.\n",
|
||||
"\n",
|
||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "23954d96-3932-4a8e-ab73-da605f99b1a4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Prerequisites** \n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
" \n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1d7f4c6a-0cb8-4ecc-81c8-544712253a3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Setup**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a31f9894-903f-4e19-a5a8-6fd888ff013b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import pandas,sys,os,json,html,getpass,time\n",
|
||||
"pandas_version = pandas.__version__.split('.')\n",
|
||||
"pandas_major = int(pandas_version[0])\n",
|
||||
"pandas_minor = int(pandas_version[1])\n",
|
||||
"pandas_patch = int(pandas_version[2])\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
|
||||
" sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
" if _exit_code != 0:\n",
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set variables**\n",
|
||||
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e70640d0-6059-4cab-939e-e985a978c0da"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Check dependencies**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "869d0397-a280-4dc4-be76-d652189b5131"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command('kubectl version --client=true')\n",
|
||||
"run_command('azdata --version')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c38afb67-1132-495e-9af1-35bf067acbeb",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Required information**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7b383b0d-5687-45b3-a16f-ba3b170c796e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
|
||||
"if invoked_by_wizard:\n",
|
||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = os.environ[\"AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD\"]\n",
|
||||
"else:\n",
|
||||
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
|
||||
" if mssql_password == \"\":\n",
|
||||
" sys.exit(f'Password is required.')\n",
|
||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||
" if mssql_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = getpass.getpass(prompt = 'Domain service account password')\n",
|
||||
" if mssql_domain_service_account_password == \"\":\n",
|
||||
" sys.exit(f'Domain service account password is required.')\n",
|
||||
"print('You can also use the controller password to access Knox and SQL Server.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set and show current context**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6456bd0c-5b64-4d76-be59-e3a5b32697f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
|
||||
"run_command('kubectl config current-context')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a38f8b3a-f93a-484c-b9e2-4eba3ed99cc2"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create deployment configuration files**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6d78da36-6af5-4309-baad-bc81bb2cdb7f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
|
||||
"if not os.path.exists(mssql_target_profile):\n",
|
||||
" os.mkdir(mssql_target_profile)\n",
|
||||
"bdcJsonObj = json.loads(bdc_json)\n",
|
||||
"controlJsonObj = json.loads(control_json)\n",
|
||||
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
|
||||
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
|
||||
"bdcJsonFile.close()\n",
|
||||
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
|
||||
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
|
||||
"controlJsonFile.close()\n",
|
||||
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7d56d262-8cd5-49e4-b745-332c6e7a3cb2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
|
||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
||||
"if mssql_auth_mode == \"ad\":\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_USERNAME\"] = mssql_domain_service_account_username\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_PASSWORD\"] = mssql_domain_service_account_password\n",
|
||||
"if os.name == 'nt':\n",
|
||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t{os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {mssql_cluster_name} ')\n",
|
||||
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7929fd90-324d-482a-a101-ae29cb183691"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'azdata login --namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Show SQL Server 2019 Big Data Cluster endpoints**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "038e801a-a393-4f8d-8e2d-97bc3b740b0c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from IPython.display import *\n",
|
||||
"pandas.set_option('display.max_colwidth', -1)\n",
|
||||
"cmd = f'azdata bdc endpoint list'\n",
|
||||
"cmdOutput = !{cmd}\n",
|
||||
"endpoints = json.loads(''.join(cmdOutput))\n",
|
||||
"endpointsDataFrame = pandas.DataFrame(endpoints)\n",
|
||||
"endpointsDataFrame.columns = [' '.join(word[0].upper() + word[1:] for word in columnName.split()) for columnName in endpoints[0].keys()]\n",
|
||||
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Connect to SQL Server Master instance in Azure Data Studio**\n",
|
||||
"Click the link below to connect to the SQL Server Master instance of the SQL Server 2019 Big Data Cluster."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0bd809fa-8225-4954-a50c-da57ea167896"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
|
||||
"if sqlEndpoints and len(sqlEndpoints) == 1:\n",
|
||||
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
|
||||
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
|
||||
" display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))\n",
|
||||
"else:\n",
|
||||
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
" \n",
|
||||
"## Deploy SQL Server 2019 Big Data Cluster on an existing OpenShift cluster\n",
|
||||
" \n",
|
||||
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview?view=sqlallproducts-allversions\">SQL Server 2019 Big Data Cluster</a> on an existing OpenShift cluster.\n",
|
||||
" \n",
|
||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||
"* The **Required information** will check and prompt you for password if it is not set in the environment variable. The password can be used to access the cluster controller, SQL Server, and Knox.\n",
|
||||
"\n",
|
||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "23954d96-3932-4a8e-ab73-da605f99b1a4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Prerequisites** \n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
" \n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1d7f4c6a-0cb8-4ecc-81c8-544712253a3f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Setup**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a31f9894-903f-4e19-a5a8-6fd888ff013b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import pandas,sys,os,json,html,getpass,time\n",
|
||||
"pandas_version = pandas.__version__.split('.')\n",
|
||||
"pandas_major = int(pandas_version[0])\n",
|
||||
"pandas_minor = int(pandas_version[1])\n",
|
||||
"pandas_patch = int(pandas_version[2])\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
|
||||
" sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
" if _exit_code != 0:\n",
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "26fa8bc4-4b8e-4c31-ae11-50484821cea8",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set variables**\n",
|
||||
"Generated by Azure Data Studio using the values collected in the Deploy Big Data Cluster wizard"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e70640d0-6059-4cab-939e-e985a978c0da"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Check dependencies**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "869d0397-a280-4dc4-be76-d652189b5131"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command('kubectl version --client=true')\n",
|
||||
"run_command('azdata --version')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c38afb67-1132-495e-9af1-35bf067acbeb",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Required information**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7b383b0d-5687-45b3-a16f-ba3b170c796e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"invoked_by_wizard = \"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\" in os.environ\n",
|
||||
"if invoked_by_wizard:\n",
|
||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_BDC_ADMIN_PASSWORD\"]\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = os.environ[\"AZDATA_NB_VAR_BDC_AD_DOMAIN_SVC_PASSWORD\"]\n",
|
||||
"else:\n",
|
||||
" mssql_password = getpass.getpass(prompt = 'SQL Server 2019 Big Data Cluster controller password')\n",
|
||||
" if mssql_password == \"\":\n",
|
||||
" sys.exit(f'Password is required.')\n",
|
||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||
" if mssql_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
" if mssql_auth_mode == \"ad\":\n",
|
||||
" mssql_domain_service_account_password = getpass.getpass(prompt = 'Domain service account password')\n",
|
||||
" if mssql_domain_service_account_password == \"\":\n",
|
||||
" sys.exit(f'Domain service account password is required.')\n",
|
||||
"print('You can also use the controller password to access Knox and SQL Server.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "b5970f2b-cf13-41af-b0a2-5133d840325e",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Set and show current context**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6456bd0c-5b64-4d76-be59-e3a5b32697f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl config use-context {mssql_cluster_context}')\n",
|
||||
"run_command('kubectl config current-context')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "a38f8b3a-f93a-484c-b9e2-4eba3ed99cc2"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Make sure the target namespace already exists**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "b903f09b-0eeb-45c0-8173-1741cce3790c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'kubectl get namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "174c02ea-8876-43be-bd93-3a39223e25ec"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create deployment configuration files**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6d78da36-6af5-4309-baad-bc81bb2cdb7f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"mssql_target_profile = 'ads-bdc-custom-profile'\n",
|
||||
"if not os.path.exists(mssql_target_profile):\n",
|
||||
" os.mkdir(mssql_target_profile)\n",
|
||||
"bdcJsonObj = json.loads(bdc_json)\n",
|
||||
"controlJsonObj = json.loads(control_json)\n",
|
||||
"bdcJsonFile = open(f'{mssql_target_profile}/bdc.json', 'w')\n",
|
||||
"bdcJsonFile.write(json.dumps(bdcJsonObj, indent = 4))\n",
|
||||
"bdcJsonFile.close()\n",
|
||||
"controlJsonFile = open(f'{mssql_target_profile}/control.json', 'w')\n",
|
||||
"controlJsonFile.write(json.dumps(controlJsonObj, indent = 4))\n",
|
||||
"controlJsonFile.close()\n",
|
||||
"print(f'Created deployment configuration folder: {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3110ab23-ecfc-4e36-a1c5-28536b7edebf",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Create SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7d56d262-8cd5-49e4-b745-332c6e7a3cb2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"print (f'Creating SQL Server 2019 Big Data Cluster: {mssql_cluster_name} using configuration {mssql_target_profile}')\n",
|
||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||
"os.environ[\"AZDATA_USERNAME\"] = mssql_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
||||
"if mssql_auth_mode == \"ad\":\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_USERNAME\"] = mssql_domain_service_account_username\n",
|
||||
" os.environ[\"DOMAIN_SERVICE_ACCOUNT_PASSWORD\"] = mssql_domain_service_account_password\n",
|
||||
"if os.name == 'nt':\n",
|
||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t{os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {mssql_cluster_name} ')\n",
|
||||
"run_command(f'azdata bdc create -c {mssql_target_profile}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0a743e88-e7d0-4b41-b8a3-e43985d15f2b",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to SQL Server 2019 Big Data Cluster**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7929fd90-324d-482a-a101-ae29cb183691"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"run_command(f'azdata login --namespace {mssql_cluster_name}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3a49909b-e09e-4e62-a825-c39de2cffc94",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Show SQL Server 2019 Big Data Cluster endpoints**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "038e801a-a393-4f8d-8e2d-97bc3b740b0c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from IPython.display import *\n",
|
||||
"pandas.set_option('display.max_colwidth', -1)\n",
|
||||
"cmd = f'azdata bdc endpoint list'\n",
|
||||
"cmdOutput = !{cmd}\n",
|
||||
"endpoints = json.loads(''.join(cmdOutput))\n",
|
||||
"endpointsDataFrame = pandas.DataFrame(endpoints)\n",
|
||||
"endpointsDataFrame.columns = [' '.join(word[0].upper() + word[1:] for word in columnName.split()) for columnName in endpoints[0].keys()]\n",
|
||||
"display(HTML(endpointsDataFrame.to_html(index=False, render_links=True)))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "2a8c8d5d-862c-4672-9309-38aa03afc4e6",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Connect to SQL Server Master instance in Azure Data Studio**\n",
|
||||
"Click the link below to connect to the SQL Server Master instance of the SQL Server 2019 Big Data Cluster."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "0bd809fa-8225-4954-a50c-da57ea167896"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"sqlEndpoints = [x for x in endpoints if x['name'] == 'sql-server-master']\n",
|
||||
"if sqlEndpoints and len(sqlEndpoints) == 1:\n",
|
||||
" connectionParameter = '{\"serverName\":\"' + sqlEndpoints[0]['endpoint'] + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":' + json.dumps(mssql_username) + ',\"password\":' + json.dumps(mssql_password) + '}'\n",
|
||||
" display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server Master instance</font></a><br/>'))\n",
|
||||
" display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))\n",
|
||||
"else:\n",
|
||||
" sys.exit('Could not find the SQL Server Master instance endpoint.')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d591785d-71aa-4c5d-9cbb-a7da79bca503",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,369 +0,0 @@
|
||||
{
|
||||
"name": "big-data-cluster",
|
||||
"displayName": "%text.sqlServerBigDataClusters%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||
"icon": "images/extension.png",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": "*"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onCommand:azdata.resource.deploy",
|
||||
"onCommand:bigDataClusters.command.mount",
|
||||
"onCommand:bigDataClusters.command.refreshmount",
|
||||
"onCommand:bigDataClusters.command.deletemount",
|
||||
"onCommand:bigDataClusters.command.createController",
|
||||
"onCommand:bigDataClusters.command.connectController",
|
||||
"onCommand:bigDataClusters.command.removeController",
|
||||
"onCommand:bigDataClusters.command.manageController",
|
||||
"onCommand:bigDataClusters.command.refreshController",
|
||||
"onView:sqlBigDataCluster"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": false,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"main": "./out/extension",
|
||||
"contributes": {
|
||||
"dataExplorer": {
|
||||
"sqlBigDataCluster": [
|
||||
{
|
||||
"id": "sqlBigDataCluster",
|
||||
"name": "%text.sqlServerBigDataClusters%"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "bigDataClusters.command.createController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.connectController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.removeController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.manageController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.mount",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshmount",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.deletemount",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "bigDataClusters.command.createController",
|
||||
"when": "view == sqlBigDataCluster",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.connectController",
|
||||
"when": "view == sqlBigDataCluster",
|
||||
"group": "navigation@2"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "bigDataClusters.command.manageController",
|
||||
"when": "view == sqlBigDataCluster && viewItem == bigDataClusters.itemType.controllerNode",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"when": "view == sqlBigDataCluster && viewItem == bigDataClusters.itemType.controllerNode",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.removeController",
|
||||
"when": "view == sqlBigDataCluster && viewItem == bigDataClusters.itemType.controllerNode",
|
||||
"group": "navigation@3"
|
||||
}
|
||||
],
|
||||
"objectExplorer/item/context": [
|
||||
{
|
||||
"command": "bigDataClusters.command.mount",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType!=mssqlCluster:message && nodeSubType=~/^(?!:mount).*$/",
|
||||
"group": "1mssqlCluster@10"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshmount",
|
||||
"when": "nodeType == mssqlCluster:folder && nodeSubType==:mount:",
|
||||
"group": "1mssqlCluster@11"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.deletemount",
|
||||
"when": "nodeType == mssqlCluster:folder && nodeSubType==:mount:",
|
||||
"group": "1mssqlCluster@12"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "bigDataClusters.command.createController",
|
||||
"title": "%command.createController.title%",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.connectController",
|
||||
"title": "%command.connectController.title%",
|
||||
"icon": "$(disconnect)"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.removeController",
|
||||
"title": "%command.removeController.title%",
|
||||
"when": "viewItem == bigDataClusters.itemType.controllerNode"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"title": "%command.refreshController.title%",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.manageController",
|
||||
"title": "%command.manageController.title%"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.mount",
|
||||
"title": "%command.mount.title%"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshmount",
|
||||
"title": "%command.refreshmount.title%"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.deletemount",
|
||||
"title": "%command.deletemount.title%"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "%bdc.configuration.title%",
|
||||
"properties": {
|
||||
"bigDataCluster.ignoreSslVerification": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%bdc.ignoreSslVerification.desc%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "sqlBigDataCluster",
|
||||
"contents": "%bdc.view.welcome.connect%",
|
||||
"when": "bdc.loaded"
|
||||
},
|
||||
{
|
||||
"view": "sqlBigDataCluster",
|
||||
"contents": "%bdc.view.welcome.loading%",
|
||||
"when": "!bdc.loaded"
|
||||
}
|
||||
],
|
||||
"resourceDeploymentTypes": [
|
||||
{
|
||||
"name": "sql-bdc",
|
||||
"displayIndex": 3,
|
||||
"displayName": "%resource-type-sql-bdc-display-name%",
|
||||
"description": "%resource-type-sql-bdc-description%",
|
||||
"platforms": "*",
|
||||
"icon": "./images/sql_bdc.svg",
|
||||
"tags": [
|
||||
"On-premises",
|
||||
"SQL Server",
|
||||
"Cloud"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"name": "version",
|
||||
"displayName": "%version-display-name%",
|
||||
"values": [
|
||||
{
|
||||
"name": "bdc2019",
|
||||
"displayName": "%bdc-2019-display-name%"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "target",
|
||||
"displayName": "%bdc-deployment-target%",
|
||||
"values": [
|
||||
{
|
||||
"name": "new-aks",
|
||||
"displayName": "%bdc-deployment-target-new-aks%"
|
||||
},
|
||||
{
|
||||
"name": "existing-aks",
|
||||
"displayName": "%bdc-deployment-target-existing-aks%"
|
||||
},
|
||||
{
|
||||
"name": "existing-kubeadm",
|
||||
"displayName": "%bdc-deployment-target-existing-kubeadm%"
|
||||
},
|
||||
{
|
||||
"name": "existing-aro",
|
||||
"displayName": "%bdc-deployment-target-existing-aro%"
|
||||
},
|
||||
{
|
||||
"name": "existing-openshift",
|
||||
"displayName": "%bdc-deployment-target-existing-openshift%"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "sql-bdc_new-aks_bdc2019",
|
||||
"bdcWizard": {
|
||||
"type": "new-aks",
|
||||
"notebook": "./notebooks/deployment/2019/deploy-bdc-aks.ipynb"
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl",
|
||||
"version": "1.13.0"
|
||||
},
|
||||
{
|
||||
"name": "azure-cli"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.9"
|
||||
}
|
||||
],
|
||||
"when": "target=new-aks&&version=bdc2019"
|
||||
},
|
||||
{
|
||||
"name": "sql-bdc_existing-aks_bdc2019",
|
||||
"bdcWizard": {
|
||||
"type": "existing-aks",
|
||||
"notebook": "./notebooks/deployment/2019/deploy-bdc-existing-aks.ipynb"
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl",
|
||||
"version": "1.13.0"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.9"
|
||||
}
|
||||
],
|
||||
"when": "target=existing-aks&&version=bdc2019"
|
||||
},
|
||||
{
|
||||
"name": "sql-bdc_existing-kubeadm_bdc2019",
|
||||
"bdcWizard": {
|
||||
"type": "existing-kubeadm",
|
||||
"notebook": "./notebooks/deployment/2019/deploy-bdc-existing-kubeadm.ipynb"
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl",
|
||||
"version": "1.13.0"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.9"
|
||||
}
|
||||
],
|
||||
"when": "target=existing-kubeadm&&version=bdc2019"
|
||||
},
|
||||
{
|
||||
"name": "sql-bdc_existing-aro_bdc2019",
|
||||
"bdcWizard": {
|
||||
"type": "existing-aro",
|
||||
"notebook": "./notebooks/deployment/2019/deploy-bdc-existing-aro.ipynb"
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl",
|
||||
"version": "1.13.0"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.9"
|
||||
}
|
||||
],
|
||||
"when": "target=existing-aro&&version=bdc2019"
|
||||
},
|
||||
{
|
||||
"name": "sql-bdc_existing-openshift_bdc2019",
|
||||
"bdcWizard": {
|
||||
"type": "existing-openshift",
|
||||
"notebook": "./notebooks/deployment/2019/deploy-bdc-existing-openshift.ipynb"
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl",
|
||||
"version": "1.13.0"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.9"
|
||||
}
|
||||
],
|
||||
"when": "target=existing-openshift&&version=bdc2019"
|
||||
}
|
||||
],
|
||||
"agreements": [
|
||||
{
|
||||
"template": "%bdc-agreement%",
|
||||
"links": [
|
||||
{
|
||||
"text": "%microsoft-privacy-statement%",
|
||||
"url": "https://go.microsoft.com/fwlink/?LinkId=853010"
|
||||
},
|
||||
{
|
||||
"text": "%bdc-agreement-bdc-eula%",
|
||||
"url": "https://go.microsoft.com/fwlink/?LinkId=2002534"
|
||||
},
|
||||
{
|
||||
"text": "%bdc-agreement-azdata-eula%",
|
||||
"url": "https://aka.ms/eula-azdata-en"
|
||||
}
|
||||
],
|
||||
"when": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/ads-kerberos": "^1.1.3",
|
||||
"request": "^2.88.0",
|
||||
"vscode-nls": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/request": "^2.48.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"json-schema": "0.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"description": "Support for managing SQL Server Big Data Clusters",
|
||||
"text.sqlServerBigDataClusters": "SQL Server Big Data Clusters",
|
||||
"command.connectController.title": "Connect to Existing Controller",
|
||||
"command.createController.title": "Create New Controller",
|
||||
"command.removeController.title": "Remove Controller",
|
||||
"command.refreshController.title": "Refresh",
|
||||
"command.manageController.title": "Manage",
|
||||
"command.mount.title": "Mount HDFS",
|
||||
"command.refreshmount.title": "Refresh Mount",
|
||||
"command.deletemount.title": "Delete Mount",
|
||||
"bdc.configuration.title": "Big Data Cluster",
|
||||
"bdc.view.welcome.connect": "No SQL Big Data Cluster controllers registered. [Learn More](https://docs.microsoft.com/sql/big-data-cluster/big-data-cluster-overview)\n[Connect Controller](command:bigDataClusters.command.connectController)",
|
||||
"bdc.view.welcome.loading": "Loading controllers...",
|
||||
"bdc.ignoreSslVerification.desc": "Ignore SSL verification errors against SQL Server Big Data Cluster endpoints such as HDFS, Spark, and Controller if true",
|
||||
"resource-type-sql-bdc-display-name": "SQL Server Big Data Cluster",
|
||||
"resource-type-sql-bdc-description": "SQL Server Big Data Cluster allows you to deploy scalable clusters of SQL Server, Spark, and HDFS containers running on Kubernetes",
|
||||
"version-display-name": "Version",
|
||||
"bdc-2019-display-name": "SQL Server 2019",
|
||||
"bdc-deployment-target": "Deployment target",
|
||||
"bdc-deployment-target-new-aks": "New Azure Kubernetes Service Cluster",
|
||||
"bdc-deployment-target-existing-aks": "Existing Azure Kubernetes Service Cluster",
|
||||
"bdc-deployment-target-existing-kubeadm": "Existing Kubernetes Cluster (kubeadm)",
|
||||
"bdc-deployment-target-existing-aro": "Existing Azure Red Hat OpenShift cluster",
|
||||
"bdc-deployment-target-existing-openshift": "Existing OpenShift cluster",
|
||||
"bdc-cluster-settings-section-title": "SQL Server Big Data Cluster settings",
|
||||
"bdc-cluster-name-field": "Cluster name",
|
||||
"bdc-controller-username-field": "Controller username",
|
||||
"bdc-password-field": "Password",
|
||||
"bdc-confirm-password-field": "Confirm password",
|
||||
"bdc-azure-settings-section-title": "Azure settings",
|
||||
"bdc-azure-subscription-id-field": "Subscription id",
|
||||
"bdc-azure-subscription-id-placeholder": "Use my default Azure subscription",
|
||||
"bdc-azure-resource-group-field": "Resource group name",
|
||||
"bdc-azure-region-field": "Region",
|
||||
"bdc-azure-aks-name-field": "AKS cluster name",
|
||||
"bdc-azure-vm-size-field": "VM size",
|
||||
"bdc-azure-vm-count-field": "VM count",
|
||||
"bdc-storage-class-field": "Storage class name",
|
||||
"bdc-data-size-field": "Capacity for data (GB)",
|
||||
"bdc-log-size-field": "Capacity for logs (GB)",
|
||||
"bdc-agreement": "I accept {0}, {1} and {2}.",
|
||||
"microsoft-privacy-statement": "Microsoft Privacy Statement",
|
||||
"bdc-agreement-azdata-eula": "azdata License Terms",
|
||||
"bdc-agreement-bdc-eula": "SQL Server License Terms"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 869 B |
@@ -1 +0,0 @@
|
||||
<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:#fff;}</style></defs><title>copy_inverse</title><path class="cls-1" d="M3,0V3.36H0V16.1H13V12.73h3V0Zm9,15.19H1.08V4.27H12Zm3-3.36H13V3.36H4V.9H15Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 274 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 6H11V3H5V6ZM6 4H10V5H6V4Z" fill="white"/>
|
||||
<path d="M1 0H14V16H1V13H0V12H1V10.023H0V9.023H1V7.039H0V6.039H1V4H0V3H1V0ZM13 15V1H2V3H3V4H2V6.039H3V7.039H2V9.023H3V10.023H2V12H3V13H2V15H13Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 317 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 986 B |
@@ -1 +0,0 @@
|
||||
<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:#3bb44a;}</style></defs><title>success_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#FFCC00"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 367 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 869 B |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>copy</title><path d="M3-.15V3.21H0V15.95H13V12.58h3V-.15ZM12,15H1.08V4.12H12Zm3-3.36H13V3.21h-9V.75H15Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 212 B |
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Artboard 20</title>
|
||||
<g>
|
||||
<path d="M5,6h6V3H5ZM6,4h4V5H6Z"/>
|
||||
<path d="M1,0H14V16H1V13H0V12H1V10.023H0v-1H1V7.039H0v-1H1V4H0V3H1ZM13,15V1H2V3H3V4H2V6.039H3v1H2V9.023H3v1H2V12H3v1H2v2Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 301 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 986 B |
@@ -1 +0,0 @@
|
||||
<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:#3bb44a;}</style></defs><title>success_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 255 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#FFCC00"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#DDB100"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 631 B |
@@ -1,2 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="32" height="32">
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 94 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="32" height="32">
|
||||
<path fill="#FF0000" d="M960 256q115 0 221 30t198 84 169 130 130 168 84 199 30 221q0 115-30 221t-84 198-130 169-168 130-199 84-221 30q-115 0-221-30t-198-84-169-130-130-168-84-199-30-221q0-115 30-221t84-198 130-169 168-130 199-84 221-30z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 337 B |
44
extensions/big-data-cluster/src/bdc.d.ts
vendored
@@ -1,44 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'bdc' {
|
||||
|
||||
export const enum constants {
|
||||
extensionName = 'Microsoft.big-data-cluster'
|
||||
}
|
||||
|
||||
export interface IExtension {
|
||||
getClusterController(url: string, authType: AuthType, username?: string, password?: string): IClusterController;
|
||||
}
|
||||
|
||||
export interface IEndpointModel {
|
||||
name?: string;
|
||||
description?: string;
|
||||
endpoint?: string;
|
||||
protocol?: string;
|
||||
}
|
||||
|
||||
export interface IHttpResponse {
|
||||
method?: string;
|
||||
url?: string;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
}
|
||||
|
||||
export interface IEndPointsResponse {
|
||||
response: IHttpResponse;
|
||||
endPoints: IEndpointModel[];
|
||||
}
|
||||
|
||||
export type AuthType = 'integrated' | 'basic';
|
||||
|
||||
export interface IClusterController {
|
||||
getClusterConfig(): Promise<any>;
|
||||
getKnoxUsername(defaultUsername: string): Promise<string>;
|
||||
getEndPoints(promptConnect?: boolean): Promise<IEndPointsResponse>
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +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 kerberos from '@microsoft/ads-kerberos';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function authenticateKerberos(hostname: string): Promise<string> {
|
||||
const service = 'HTTP' + (process.platform === 'win32' ? '/' : '@') + hostname;
|
||||
const mechOID = kerberos.GSS_MECH_OID_KRB5;
|
||||
let client = await kerberos.initializeClient(service, { mechOID });
|
||||
let response = await client.step('');
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
type HostAndIp = { host: string, port: string };
|
||||
|
||||
export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
||||
let authority = vscode.Uri.parse(endpoint).authority;
|
||||
let hostAndPortRegex = /^(.*)([,:](\d+))/g;
|
||||
let match = hostAndPortRegex.exec(authority);
|
||||
if (match) {
|
||||
return {
|
||||
host: match[1],
|
||||
port: match[3]
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: authority,
|
||||
port: undefined
|
||||
};
|
||||
}
|
||||
@@ -1,77 +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 enum BdcItemType {
|
||||
controllerRoot = 'bigDataClusters.itemType.controllerRootNode',
|
||||
controller = 'bigDataClusters.itemType.controllerNode',
|
||||
loadingController = 'bigDataClusters.itemType.loadingControllerNode'
|
||||
}
|
||||
|
||||
export interface IconPath {
|
||||
dark: string;
|
||||
light: string;
|
||||
}
|
||||
|
||||
export class IconPathHelper {
|
||||
private static extensionContext: vscode.ExtensionContext;
|
||||
|
||||
public static controllerNode: IconPath;
|
||||
public static copy: IconPath;
|
||||
public static refresh: IconPath;
|
||||
public static status_ok: IconPath;
|
||||
public static status_warning: IconPath;
|
||||
public static notebook: IconPath;
|
||||
public static status_circle_red: IconPath;
|
||||
public static status_circle_blank: IconPath;
|
||||
|
||||
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
||||
IconPathHelper.extensionContext = extensionContext;
|
||||
IconPathHelper.controllerNode = {
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
|
||||
};
|
||||
IconPathHelper.copy = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/copy.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/copy_inverse.svg')
|
||||
};
|
||||
IconPathHelper.refresh = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/refresh.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/refresh_inverse.svg')
|
||||
};
|
||||
IconPathHelper.status_ok = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/status_ok_light.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/status_ok_dark.svg')
|
||||
};
|
||||
IconPathHelper.status_warning = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/status_warning_light.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/status_warning_dark.svg')
|
||||
};
|
||||
IconPathHelper.notebook = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/notebook.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/notebook_inverse.svg')
|
||||
};
|
||||
IconPathHelper.status_circle_red = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_red.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_red.svg')
|
||||
};
|
||||
IconPathHelper.status_circle_blank = {
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_blank.svg'),
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/status_circle_blank.svg')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace cssStyles {
|
||||
export const title = { 'font-size': '14px', 'font-weight': '600' };
|
||||
export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' };
|
||||
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
|
||||
export const lastUpdatedText = { ...text, 'color': '#595959' };
|
||||
export const errorText = { ...text, 'color': 'red' };
|
||||
}
|
||||
|
||||
export const clusterEndpointsProperty = 'clusterEndpoints';
|
||||
export const controllerEndpointName = 'controller';
|
||||
@@ -1,455 +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 request from 'request';
|
||||
import { authenticateKerberos, getHostAndPortFromEndpoint } from '../auth';
|
||||
import { BdcRouterApi, Authentication, EndpointModel, BdcStatusModel, DefaultApi } from './apiGenerated';
|
||||
import { TokenRouterApi } from './clusterApiGenerated2';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ConnectControllerDialog, ConnectControllerModel } from '../dialog/connectControllerDialog';
|
||||
import { getIgnoreSslVerificationConfigSetting } from '../utils';
|
||||
import { IClusterController, AuthType, IEndPointsResponse, IHttpResponse } from 'bdc';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const DEFAULT_KNOX_USERNAME = 'root';
|
||||
|
||||
class SslAuth implements Authentication {
|
||||
constructor() { }
|
||||
|
||||
applyToRequest(requestOptions: request.Options): void {
|
||||
requestOptions.rejectUnauthorized = !getIgnoreSslVerificationConfigSetting();
|
||||
}
|
||||
}
|
||||
|
||||
export class KerberosAuth extends SslAuth implements Authentication {
|
||||
|
||||
constructor(public kerberosToken: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
override applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
if (requestOptions && requestOptions.headers) {
|
||||
requestOptions.headers['Authorization'] = `Negotiate ${this.kerberosToken}`;
|
||||
}
|
||||
requestOptions.auth = undefined;
|
||||
}
|
||||
}
|
||||
export class BasicAuth extends SslAuth implements Authentication {
|
||||
constructor(public username: string, public password: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
override applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
requestOptions.auth = {
|
||||
username: this.username, password: this.password
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class OAuthWithSsl extends SslAuth implements Authentication {
|
||||
public accessToken: string = '';
|
||||
|
||||
override applyToRequest(requestOptions: request.Options): void {
|
||||
super.applyToRequest(requestOptions);
|
||||
if (requestOptions && requestOptions.headers) {
|
||||
requestOptions.headers['Authorization'] = `Bearer ${this.accessToken}`;
|
||||
}
|
||||
requestOptions.auth = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class BdcApiWrapper extends BdcRouterApi {
|
||||
constructor(basePathOrUsername: string, password: string, basePath: string, auth: Authentication) {
|
||||
if (password) {
|
||||
super(basePathOrUsername, password, basePath);
|
||||
} else {
|
||||
super(basePath, undefined, undefined);
|
||||
}
|
||||
this.authentications.default = auth;
|
||||
}
|
||||
}
|
||||
class DefaultApiWrapper extends DefaultApi {
|
||||
constructor(basePathOrUsername: string, password: string, basePath: string, auth: Authentication) {
|
||||
if (password) {
|
||||
super(basePathOrUsername, password, basePath);
|
||||
} else {
|
||||
super(basePath, undefined, undefined);
|
||||
}
|
||||
this.authentications.default = auth;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClusterController implements IClusterController {
|
||||
|
||||
private _authPromise: Promise<Authentication>;
|
||||
private _url: string;
|
||||
private readonly _dialog: ConnectControllerDialog;
|
||||
private _connectionPromise: Promise<ClusterController>;
|
||||
|
||||
constructor(url: string,
|
||||
private _authType: AuthType,
|
||||
private _username?: string,
|
||||
private _password?: string
|
||||
) {
|
||||
if (!url || (_authType === 'basic' && (!_username || !_password))) {
|
||||
throw new Error('Missing required inputs for Cluster controller API (URL, username, password)');
|
||||
}
|
||||
this._url = adjustUrl(url);
|
||||
if (this._authType === 'basic') {
|
||||
this._authPromise = Promise.resolve(new BasicAuth(_username, _password));
|
||||
} else {
|
||||
this._authPromise = this.requestTokenUsingKerberos();
|
||||
}
|
||||
this._dialog = new ConnectControllerDialog(new ConnectControllerModel(
|
||||
{
|
||||
url: this._url,
|
||||
auth: this._authType,
|
||||
username: this._username,
|
||||
password: this._password
|
||||
}));
|
||||
}
|
||||
|
||||
public get url(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public get authType(): AuthType {
|
||||
return this._authType;
|
||||
}
|
||||
|
||||
public get username(): string | undefined {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
public get password(): string | undefined {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
private async requestTokenUsingKerberos(): Promise<Authentication> {
|
||||
let supportsKerberos = await this.verifyKerberosSupported();
|
||||
if (!supportsKerberos) {
|
||||
throw new Error(localize('error.no.activedirectory', "This cluster does not support Windows authentication"));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// AD auth is available, login to keberos and convert to token auth for all future calls
|
||||
let host = getHostAndPortFromEndpoint(this._url).host;
|
||||
let kerberosToken = await authenticateKerberos(host);
|
||||
let tokenApi = new TokenRouterApi(this._url);
|
||||
tokenApi.setDefaultAuthentication(new KerberosAuth(kerberosToken));
|
||||
let result = await tokenApi.apiV1TokenPost();
|
||||
let auth = new OAuthWithSsl();
|
||||
auth.accessToken = result.body.accessToken;
|
||||
return auth;
|
||||
} catch (error) {
|
||||
let controllerErr = new ControllerError(error, localize('bdc.error.tokenPost', "Error during authentication"));
|
||||
if (controllerErr.code === 401) {
|
||||
throw new Error(localize('bdc.error.unauthorized', "You do not have permission to log into this cluster using Windows Authentication"));
|
||||
}
|
||||
// Else throw the error as-is
|
||||
throw controllerErr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that this cluster supports Kerberos authentication. It does this by sending a request to the Token API route
|
||||
* without any credentials and verifying that it gets a 401 response back with a Negotiate www-authenticate header.
|
||||
*/
|
||||
private async verifyKerberosSupported(): Promise<boolean> {
|
||||
let tokenApi = new TokenRouterApi(this._url);
|
||||
tokenApi.setDefaultAuthentication(new SslAuth());
|
||||
try {
|
||||
await tokenApi.apiV1TokenPost();
|
||||
console.warn(`Token API returned success without any auth while verifying Kerberos support for BDC Cluster ${this._url}`);
|
||||
// If we get to here, the route for tokens doesn't require auth which is an unexpected error state
|
||||
return false;
|
||||
}
|
||||
catch (error) {
|
||||
if (!error.response) {
|
||||
console.warn(`No response when verifying Kerberos support for BDC Cluster ${this._url} - ${error}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error.response.statusCode !== 401) {
|
||||
console.warn(`Got unexpected status code ${error.response.statusCode} when verifying Kerberos support for BDC Cluster ${this._url}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auths = error.response.headers['www-authenticate'] as string[] ?? [];
|
||||
if (auths.includes('Negotiate')) {
|
||||
return true;
|
||||
}
|
||||
console.warn(`Didn't get expected Negotiate auth type when verifying Kerberos support for BDC Cluster ${this.url}. Supported types : ${auths.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async getKnoxUsername(defaultUsername: string): Promise<string> {
|
||||
// This all is necessary because prior to CU5 BDC deployments all had the same default username for
|
||||
// accessing the Knox gateway. But in the allowRunAsRoot setting was added and defaulted to false - so
|
||||
// if that exists and is false then we use the username instead.
|
||||
// Note that the SQL username may not necessarily be correct here either - but currently this is what
|
||||
// we're requiring to run Notebooks in a BDC
|
||||
const config = await this.getClusterConfig();
|
||||
return config.spec?.spec?.security?.allowRunAsRoot === false ? defaultUsername : DEFAULT_KNOX_USERNAME;
|
||||
}
|
||||
|
||||
public async getClusterConfig(promptConnect: boolean = false): Promise<any> {
|
||||
return await this.withConnectRetry<any>(
|
||||
this.getClusterConfigImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.getClusterConfig', "Error retrieving cluster config from {0}", this._url));
|
||||
}
|
||||
|
||||
private async getClusterConfigImpl(self: ClusterController): Promise<any> {
|
||||
let auth = await self._authPromise;
|
||||
let endPointApi = new BdcApiWrapper(self._username, self._password, self._url, auth);
|
||||
let options: any = {};
|
||||
|
||||
let result = await endPointApi.getCluster(options);
|
||||
return {
|
||||
response: result.response as IHttpResponse,
|
||||
spec: JSON.parse(result.body.spec)
|
||||
};
|
||||
}
|
||||
|
||||
public async getEndPoints(promptConnect: boolean = false): Promise<IEndPointsResponse> {
|
||||
return await this.withConnectRetry<IEndPointsResponse>(
|
||||
this.getEndpointsImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.getEndPoints', "Error retrieving endpoints from {0}", this._url));
|
||||
}
|
||||
|
||||
private async getEndpointsImpl(self: ClusterController): Promise<IEndPointsResponse> {
|
||||
let auth = await self._authPromise;
|
||||
let endPointApi = new BdcApiWrapper(self._username, self._password, self._url, auth);
|
||||
let options: any = {};
|
||||
|
||||
let result = await endPointApi.endpointsGet(options);
|
||||
return {
|
||||
response: result.response as IHttpResponse,
|
||||
endPoints: result.body as EndpointModel[]
|
||||
};
|
||||
}
|
||||
|
||||
public async getBdcStatus(promptConnect: boolean = false): Promise<IBdcStatusResponse> {
|
||||
return await this.withConnectRetry<IBdcStatusResponse>(
|
||||
this.getBdcStatusImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.getBdcStatus', "Error retrieving BDC status from {0}", this._url));
|
||||
}
|
||||
|
||||
private async getBdcStatusImpl(self: ClusterController): Promise<IBdcStatusResponse> {
|
||||
let auth = await self._authPromise;
|
||||
const bdcApi = new BdcApiWrapper(self._username, self._password, self._url, auth);
|
||||
|
||||
const bdcStatus = await bdcApi.getBdcStatus('', '', /*all*/ true);
|
||||
return {
|
||||
response: bdcStatus.response,
|
||||
bdcStatus: bdcStatus.body
|
||||
};
|
||||
}
|
||||
|
||||
public async mountHdfs(mountPath: string, remoteUri: string, credentials: {}, promptConnection: boolean = false): Promise<MountResponse> {
|
||||
return await this.withConnectRetry<MountResponse>(
|
||||
this.mountHdfsImpl,
|
||||
promptConnection,
|
||||
localize('bdc.error.mountHdfs', "Error creating mount"),
|
||||
mountPath,
|
||||
remoteUri,
|
||||
credentials);
|
||||
}
|
||||
|
||||
private async mountHdfsImpl(self: ClusterController, mountPath: string, remoteUri: string, credentials: {}): Promise<MountResponse> {
|
||||
let auth = await self._authPromise;
|
||||
const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
|
||||
|
||||
const mountStatus = await api.createMount('', '', remoteUri, mountPath, credentials);
|
||||
return {
|
||||
response: mountStatus.response,
|
||||
status: mountStatus.body
|
||||
};
|
||||
}
|
||||
|
||||
public async getMountStatus(mountPath?: string, promptConnect: boolean = false): Promise<MountStatusResponse> {
|
||||
return await this.withConnectRetry<MountStatusResponse>(
|
||||
this.getMountStatusImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.statusHdfs', "Error getting mount status"),
|
||||
mountPath);
|
||||
}
|
||||
|
||||
private async getMountStatusImpl(self: ClusterController, mountPath?: string): Promise<MountStatusResponse> {
|
||||
const auth = await self._authPromise;
|
||||
const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
|
||||
|
||||
const mountStatus = await api.listMounts('', '', mountPath);
|
||||
return {
|
||||
response: mountStatus.response,
|
||||
mount: mountStatus.body ? JSON.parse(mountStatus.body) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
public async refreshMount(mountPath: string, promptConnect: boolean = false): Promise<MountResponse> {
|
||||
return await this.withConnectRetry<MountResponse>(
|
||||
this.refreshMountImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.refreshHdfs', "Error refreshing mount"),
|
||||
mountPath);
|
||||
}
|
||||
|
||||
private async refreshMountImpl(self: ClusterController, mountPath: string): Promise<MountResponse> {
|
||||
const auth = await self._authPromise;
|
||||
const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
|
||||
|
||||
const mountStatus = await api.refreshMount('', '', mountPath);
|
||||
return {
|
||||
response: mountStatus.response,
|
||||
status: mountStatus.body
|
||||
};
|
||||
}
|
||||
|
||||
public async deleteMount(mountPath: string, promptConnect: boolean = false): Promise<MountResponse> {
|
||||
return await this.withConnectRetry<MountResponse>(
|
||||
this.deleteMountImpl,
|
||||
promptConnect,
|
||||
localize('bdc.error.deleteHdfs', "Error deleting mount"),
|
||||
mountPath);
|
||||
}
|
||||
|
||||
private async deleteMountImpl(self: ClusterController, mountPath: string): Promise<MountResponse> {
|
||||
let auth = await self._authPromise;
|
||||
const api = new DefaultApiWrapper(self._username, self._password, self._url, auth);
|
||||
|
||||
const mountStatus = await api.deleteMount('', '', mountPath);
|
||||
return {
|
||||
response: mountStatus.response,
|
||||
status: mountStatus.body
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that wraps a function call in a try/catch and if promptConnect is true
|
||||
* will prompt the user to re-enter connection information and if that succeeds updates
|
||||
* this with the new information.
|
||||
* @param f The API function we're wrapping
|
||||
* @param promptConnect Whether to actually prompt for connection on failure
|
||||
* @param errorMessage The message to include in the wrapped error thrown
|
||||
* @param args The args to pass to the function
|
||||
*/
|
||||
private async withConnectRetry<T>(f: (...args: any[]) => Promise<T>, promptConnect: boolean, errorMessage: string, ...args: any[]): Promise<T> {
|
||||
try {
|
||||
try {
|
||||
return await f(this, ...args);
|
||||
} catch (error) {
|
||||
if (promptConnect) {
|
||||
// We don't want to open multiple dialogs here if multiple calls come in the same time so check
|
||||
// and see if we have are actively waiting on an open dialog to return and if so then just wait
|
||||
// on that promise.
|
||||
if (!this._connectionPromise) {
|
||||
this._connectionPromise = this._dialog.showDialog();
|
||||
}
|
||||
const controller = await this._connectionPromise;
|
||||
if (controller) {
|
||||
this._username = controller._username;
|
||||
this._password = controller._password;
|
||||
this._url = controller._url;
|
||||
this._authType = controller._authType;
|
||||
this._authPromise = controller._authPromise;
|
||||
}
|
||||
return await f(this, args);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
throw new ControllerError(error, errorMessage);
|
||||
} finally {
|
||||
this._connectionPromise = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 IClusterRequest {
|
||||
url: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
method?: string;
|
||||
}
|
||||
|
||||
export interface IBdcStatusResponse {
|
||||
response: IHttpResponse;
|
||||
bdcStatus: BdcStatusModel;
|
||||
}
|
||||
|
||||
export enum MountState {
|
||||
Creating = 'Creating',
|
||||
Ready = 'Ready',
|
||||
Error = 'Error'
|
||||
}
|
||||
|
||||
export interface MountInfo {
|
||||
mount: string;
|
||||
remote: string;
|
||||
state: MountState;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface MountResponse {
|
||||
response: IHttpResponse;
|
||||
status: any;
|
||||
}
|
||||
export interface MountStatusResponse {
|
||||
response: IHttpResponse;
|
||||
mount: MountInfo[];
|
||||
}
|
||||
|
||||
export class ControllerError extends Error {
|
||||
public code?: number;
|
||||
public reason?: string;
|
||||
public address?: string;
|
||||
public statusMessage?: string;
|
||||
/**
|
||||
*
|
||||
* @param error The original error to wrap
|
||||
* @param messagePrefix Optional text to prefix the error message with
|
||||
*/
|
||||
constructor(error: any, messagePrefix?: string) {
|
||||
super(messagePrefix);
|
||||
// Pull out the response information containing details about the failure
|
||||
if (error.response) {
|
||||
this.code = error.response.statusCode;
|
||||
this.message += `${error.response.statusMessage ? ` - ${error.response.statusMessage}` : ''}` || '';
|
||||
this.address = error.response.url || '';
|
||||
this.statusMessage = error.response.statusMessage;
|
||||
}
|
||||
else if (error.message) {
|
||||
this.message += ` - ${error.message}`;
|
||||
}
|
||||
|
||||
// The body message contains more specific information about the failure
|
||||
if (error.body && error.body.reason) {
|
||||
this.message += ` - ${error.body.reason}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +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 * as vscode from 'vscode';
|
||||
import { ClusterController, ControllerError } from '../controller/clusterControllerApi';
|
||||
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
|
||||
import { BdcDashboardOptions } from './bdcDashboardModel';
|
||||
import { ControllerNode } from '../tree/controllerTreeNode';
|
||||
import { ManageControllerCommand } from '../../commands';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { AuthType } from 'bdc';
|
||||
|
||||
function getAuthCategory(name: AuthType): azdata.CategoryValue {
|
||||
if (name === 'basic') {
|
||||
return { name: name, displayName: loc.basic };
|
||||
}
|
||||
return { name: name, displayName: loc.windowsAuth };
|
||||
}
|
||||
|
||||
export class AddControllerDialogModel {
|
||||
|
||||
private _canceled = false;
|
||||
private _authTypes: azdata.CategoryValue[];
|
||||
constructor(
|
||||
public treeDataProvider: ControllerTreeDataProvider,
|
||||
public node?: ControllerNode,
|
||||
public prefilledUrl?: string,
|
||||
public prefilledAuth?: azdata.CategoryValue,
|
||||
public prefilledUsername?: string,
|
||||
public prefilledPassword?: string,
|
||||
public prefilledRememberPassword?: boolean
|
||||
) {
|
||||
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
||||
this.prefilledAuth = prefilledAuth;
|
||||
if (!prefilledAuth) {
|
||||
let auth = (node && node['auth']) || 'basic';
|
||||
this.prefilledAuth = getAuthCategory(auth);
|
||||
}
|
||||
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
||||
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
||||
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
||||
}
|
||||
|
||||
public get authCategories(): azdata.CategoryValue[] {
|
||||
if (!this._authTypes) {
|
||||
this._authTypes = [getAuthCategory('basic'), getAuthCategory('integrated')];
|
||||
}
|
||||
return this._authTypes;
|
||||
}
|
||||
|
||||
public async onComplete(url: string, auth: AuthType, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
try {
|
||||
|
||||
if (auth === 'basic') {
|
||||
// Verify username and password as we can't make them required in the UI
|
||||
if (!username) {
|
||||
throw new Error(loc.usernameRequired);
|
||||
} else if (!password) {
|
||||
throw new Error(loc.passwordRequired);
|
||||
}
|
||||
}
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let controller = new ClusterController(url, auth, username, password);
|
||||
let response = await controller.getEndPoints();
|
||||
if (response && response.endPoints) {
|
||||
if (this._canceled) {
|
||||
return;
|
||||
}
|
||||
this.treeDataProvider.addOrUpdateController(url, auth, username, password, rememberPassword);
|
||||
vscode.commands.executeCommand(ManageControllerCommand, <BdcDashboardOptions>{ url: url, auth: auth, username: username, password: password });
|
||||
await this.treeDataProvider.saveControllers();
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore the error if we cancelled the request since we can't stop the actual request from completing
|
||||
if (!this._canceled) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async onError(error: ControllerError): Promise<void> {
|
||||
// implement
|
||||
}
|
||||
|
||||
public async onCancel(): Promise<void> {
|
||||
this._canceled = true;
|
||||
if (this.node) {
|
||||
this.node.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AddControllerDialog {
|
||||
|
||||
private dialog: azdata.window.Dialog;
|
||||
private uiModelBuilder: azdata.ModelBuilder;
|
||||
|
||||
private urlInputBox: azdata.InputBoxComponent;
|
||||
private authDropdown: azdata.DropDownComponent;
|
||||
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(loc.addNewController);
|
||||
this.dialog.registerContent(async view => {
|
||||
this.uiModelBuilder = view.modelBuilder;
|
||||
|
||||
this.urlInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.url.toLocaleLowerCase(),
|
||||
value: this.model.prefilledUrl
|
||||
}).component();
|
||||
this.authDropdown = this.uiModelBuilder.dropDown().withProps({
|
||||
values: this.model.authCategories,
|
||||
value: this.model.prefilledAuth,
|
||||
editable: false,
|
||||
}).component();
|
||||
this.authDropdown.onValueChanged(e => this.onAuthChanged());
|
||||
this.usernameInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.usernameRequired.toLocaleLowerCase(),
|
||||
value: this.model.prefilledUsername
|
||||
}).component();
|
||||
this.passwordInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.password,
|
||||
inputType: 'password',
|
||||
value: this.model.prefilledPassword
|
||||
})
|
||||
.component();
|
||||
this.rememberPwCheckBox = this.uiModelBuilder.checkBox()
|
||||
.withProps({
|
||||
label: loc.rememberPassword,
|
||||
checked: this.model.prefilledRememberPassword
|
||||
}).component();
|
||||
|
||||
let formModel = this.uiModelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [
|
||||
{
|
||||
component: this.urlInputBox,
|
||||
title: loc.clusterUrl,
|
||||
required: true
|
||||
}, {
|
||||
component: this.authDropdown,
|
||||
title: loc.authType,
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: loc.username,
|
||||
required: false
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: loc.password,
|
||||
required: false
|
||||
}, {
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
this.onAuthChanged();
|
||||
await view.initializeModel(formModel);
|
||||
this.urlInputBox.focus();
|
||||
});
|
||||
|
||||
this.dialog.registerCloseValidator(async () => await this.validate());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = loc.add;
|
||||
this.dialog.cancelButton.label = loc.cancel;
|
||||
}
|
||||
|
||||
private get authValue(): AuthType {
|
||||
return (<azdata.CategoryValue>this.authDropdown.value).name as AuthType;
|
||||
}
|
||||
|
||||
private onAuthChanged(): void {
|
||||
let isBasic = this.authValue === 'basic';
|
||||
this.usernameInputBox.enabled = isBasic;
|
||||
this.passwordInputBox.enabled = isBasic;
|
||||
this.rememberPwCheckBox.enabled = isBasic;
|
||||
if (!isBasic) {
|
||||
this.usernameInputBox.value = '';
|
||||
this.passwordInputBox.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
private async validate(): Promise<boolean> {
|
||||
let url = this.urlInputBox && this.urlInputBox.value;
|
||||
let auth = this.authValue;
|
||||
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, auth, username, password, rememberPassword);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.dialog.message = {
|
||||
text: (typeof error === 'string') ? error : error.message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
if (this.model && this.model.onError) {
|
||||
await this.model.onError(error as ControllerError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async cancel(): Promise<void> {
|
||||
if (this.model && this.model.onCancel) {
|
||||
await this.model.onCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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 { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
||||
import { BdcServiceStatusPage } from './bdcServiceStatusPage';
|
||||
import { BdcDashboardOverviewPage } from './bdcDashboardOverviewPage';
|
||||
import { BdcStatusModel, ServiceStatusModel } from '../controller/apiGenerated';
|
||||
import { getServiceNameDisplayText, showErrorMessage, getHealthStatusDotIcon } from '../utils';
|
||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||
import { InitializingComponent } from './intializingComponent';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
export class BdcDashboard extends InitializingComponent {
|
||||
|
||||
private dashboard: azdata.window.ModelViewDashboard;
|
||||
|
||||
private modelView: azdata.ModelView;
|
||||
|
||||
private createdServicePages: Map<string, azdata.DashboardTab> = new Map<string, azdata.DashboardTab>();
|
||||
private overviewTab: azdata.DashboardTab;
|
||||
|
||||
constructor(private title: string, private model: BdcDashboardModel) {
|
||||
super();
|
||||
model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||
model.onBdcError(errorEvent => this.eventuallyRunOnInitialized(() => this.handleError(errorEvent)));
|
||||
}
|
||||
|
||||
public async showDashboard(): Promise<void> {
|
||||
await this.createDashboard();
|
||||
await this.dashboard.open();
|
||||
}
|
||||
|
||||
private async createDashboard(): Promise<void> {
|
||||
this.dashboard = azdata.window.createModelViewDashboard(this.title, 'BdcDashboard', { alwaysShowTabs: true });
|
||||
this.dashboard.registerTabs(async (modelView: azdata.ModelView) => {
|
||||
this.modelView = modelView;
|
||||
|
||||
const overviewPage = new BdcDashboardOverviewPage(this.model, modelView, this.dashboard);
|
||||
this.overviewTab = {
|
||||
title: loc.bdcOverview,
|
||||
id: 'overview-tab',
|
||||
content: overviewPage.container,
|
||||
toolbar: overviewPage.toolbarContainer
|
||||
};
|
||||
return [
|
||||
this.overviewTab
|
||||
];
|
||||
});
|
||||
this.initialized = true;
|
||||
|
||||
// Now that we've created the UI load data from the model in case it already had data
|
||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
||||
}
|
||||
|
||||
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
||||
if (!bdcStatus) {
|
||||
return;
|
||||
}
|
||||
this.updateServicePages(bdcStatus.services);
|
||||
}
|
||||
|
||||
private handleError(errorEvent: BdcErrorEvent): void {
|
||||
if (errorEvent.errorType !== 'general') {
|
||||
return;
|
||||
}
|
||||
// We don't want to show an error for the connection dialog being
|
||||
// canceled since that's a normal case.
|
||||
if (!(errorEvent.error instanceof HdfsDialogCancelledError)) {
|
||||
showErrorMessage(errorEvent.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the service tab pages, creating any new ones as necessary
|
||||
*/
|
||||
private updateServicePages(services?: ServiceStatusModel[]): void {
|
||||
if (services) {
|
||||
// Create a service page for each new service. We currently don't support services being removed.
|
||||
services.forEach(s => {
|
||||
const existingPage = this.createdServicePages.get(s.serviceName);
|
||||
if (existingPage) {
|
||||
existingPage.icon = getHealthStatusDotIcon(s.healthStatus);
|
||||
} else {
|
||||
const serviceStatusPage = new BdcServiceStatusPage(s.serviceName, this.model, this.modelView);
|
||||
const newTab = <azdata.Tab>{
|
||||
title: getServiceNameDisplayText(s.serviceName),
|
||||
id: s.serviceName,
|
||||
icon: getHealthStatusDotIcon(s.healthStatus),
|
||||
content: serviceStatusPage.container,
|
||||
toolbar: serviceStatusPage.toolbarContainer
|
||||
};
|
||||
this.createdServicePages.set(s.serviceName, newTab);
|
||||
}
|
||||
});
|
||||
this.dashboard.updateTabs([
|
||||
this.overviewTab,
|
||||
{
|
||||
title: loc.clusterDetails,
|
||||
tabs: Array.from(this.createdServicePages.values())
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +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 * as vscode from 'vscode';
|
||||
import { ClusterController } from '../controller/clusterControllerApi';
|
||||
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||
import { Endpoint, Service } from '../utils';
|
||||
import { ConnectControllerDialog, ConnectControllerModel } from './connectControllerDialog';
|
||||
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
|
||||
import { AuthType } from 'bdc';
|
||||
|
||||
export type BdcDashboardOptions = { url: string, auth: AuthType, username: string, password: string, rememberPassword: boolean };
|
||||
|
||||
type BdcErrorType = 'bdcStatus' | 'bdcEndpoints' | 'general';
|
||||
export type BdcErrorEvent = { error: Error, errorType: BdcErrorType };
|
||||
|
||||
export class BdcDashboardModel {
|
||||
|
||||
private _clusterController: ClusterController;
|
||||
private _bdcStatus: BdcStatusModel | undefined;
|
||||
private _endpoints: EndpointModel[] | undefined;
|
||||
private _bdcStatusLastUpdated: Date | undefined;
|
||||
private _endpointsLastUpdated: Date | undefined;
|
||||
private readonly _onDidUpdateEndpoints = new vscode.EventEmitter<EndpointModel[]>();
|
||||
private readonly _onDidUpdateBdcStatus = new vscode.EventEmitter<BdcStatusModel>();
|
||||
private readonly _onBdcError = new vscode.EventEmitter<BdcErrorEvent>();
|
||||
public onDidUpdateEndpoints = this._onDidUpdateEndpoints.event;
|
||||
public onDidUpdateBdcStatus = this._onDidUpdateBdcStatus.event;
|
||||
public onBdcError = this._onBdcError.event;
|
||||
|
||||
constructor(private _options: BdcDashboardOptions, private _treeDataProvider: ControllerTreeDataProvider) {
|
||||
try {
|
||||
this._clusterController = new ClusterController(_options.url, _options.auth, _options.username, _options.password);
|
||||
this.refresh().catch(e => console.log(`Unexpected error refreshing BdcModel ${e instanceof Error ? e.message : e}`));
|
||||
} catch {
|
||||
this.promptReconnect().then(async () => {
|
||||
await this.refresh();
|
||||
}).catch(error => {
|
||||
this._onBdcError.fire({ error: error, errorType: 'general' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get bdcStatus(): BdcStatusModel | undefined {
|
||||
return this._bdcStatus;
|
||||
}
|
||||
|
||||
public get serviceEndpoints(): EndpointModel[] | undefined {
|
||||
return this._endpoints;
|
||||
}
|
||||
|
||||
public get bdcStatusLastUpdated(): Date | undefined {
|
||||
return this._bdcStatusLastUpdated;
|
||||
}
|
||||
|
||||
public get endpointsLastUpdated(): Date | undefined {
|
||||
return this._endpointsLastUpdated;
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
try {
|
||||
if (!this._clusterController) {
|
||||
// If this succeeds without error we know we have a clusterController at this point
|
||||
await this.promptReconnect();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this._clusterController.getBdcStatus(true).then(response => {
|
||||
this._bdcStatus = response.bdcStatus;
|
||||
this._bdcStatusLastUpdated = new Date();
|
||||
this._onDidUpdateBdcStatus.fire(this.bdcStatus);
|
||||
}).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcStatus' })),
|
||||
this._clusterController.getEndPoints(true).then(response => {
|
||||
this._endpoints = response.endPoints;
|
||||
fixEndpoints(this._endpoints);
|
||||
this._endpointsLastUpdated = new Date();
|
||||
this._onDidUpdateEndpoints.fire(this.serviceEndpoints);
|
||||
}).catch(error => this._onBdcError.fire({ error: error, errorType: 'bdcEndpoints' }))
|
||||
]);
|
||||
} catch (error) {
|
||||
this._onBdcError.fire({ error: error, errorType: 'general' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a partially filled connection profile for the SQL Server Master Instance endpoint
|
||||
* associated with this cluster.
|
||||
* @returns The IConnectionProfile - or undefined if the endpoints haven't been loaded yet
|
||||
*/
|
||||
public getSqlServerMasterConnectionProfile(): azdata.IConnectionProfile | undefined {
|
||||
const sqlServerMasterEndpoint = this.serviceEndpoints && this.serviceEndpoints.find(e => e.name === Endpoint.sqlServerMaster);
|
||||
if (!sqlServerMasterEndpoint) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We default to sa - if that doesn't work then callers of this should open up a connection
|
||||
// dialog so the user can enter in the correct connection information
|
||||
return {
|
||||
connectionName: undefined,
|
||||
serverName: sqlServerMasterEndpoint.endpoint,
|
||||
databaseName: undefined,
|
||||
userName: 'sa',
|
||||
password: this._options.password,
|
||||
authenticationType: '',
|
||||
savePassword: true,
|
||||
groupFullName: undefined,
|
||||
groupId: undefined,
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: true,
|
||||
id: undefined,
|
||||
options: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens up a dialog prompting the user to re-enter credentials for the controller
|
||||
*/
|
||||
private async promptReconnect(): Promise<void> {
|
||||
this._clusterController = await new ConnectControllerDialog(new ConnectControllerModel(this._options)).showDialog();
|
||||
await this.updateController();
|
||||
}
|
||||
|
||||
private async updateController(): Promise<void> {
|
||||
if (!this._clusterController) {
|
||||
return;
|
||||
}
|
||||
this._treeDataProvider.addOrUpdateController(
|
||||
this._clusterController.url,
|
||||
this._clusterController.authType,
|
||||
this._clusterController.username,
|
||||
this._clusterController.password,
|
||||
this._options.rememberPassword);
|
||||
await this._treeDataProvider.saveControllers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the troubleshoot book URL for the specified service, defaulting to the BDC
|
||||
* troubleshoot notebook if the service name is unknown.
|
||||
* @param service The service name to get the troubleshoot notebook URL for
|
||||
*/
|
||||
export function getTroubleshootNotebookUrl(service?: string): string {
|
||||
service = service || '';
|
||||
switch (service.toLowerCase()) {
|
||||
case Service.sql:
|
||||
return 'troubleshooters/tsg101-troubleshoot-sql-server';
|
||||
case Service.hdfs:
|
||||
return 'troubleshooters/tsg102-troubleshoot-hdfs';
|
||||
case Service.spark:
|
||||
return 'troubleshooters/tsg103-troubleshoot-spark';
|
||||
case Service.control:
|
||||
return 'troubleshooters/tsg104-troubleshoot-control';
|
||||
case Service.gateway:
|
||||
return 'troubleshooters/tsg105-troubleshoot-gateway';
|
||||
case Service.app:
|
||||
return 'troubleshooters/tsg106-troubleshoot-app';
|
||||
}
|
||||
return 'troubleshooters/tsg100-troubleshoot-bdc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies fixes to the endpoints received so they are displayed correctly
|
||||
* @param endpoints The endpoints received to modify
|
||||
*/
|
||||
function fixEndpoints(endpoints?: EndpointModel[]): void {
|
||||
if (!endpoints) {
|
||||
return;
|
||||
}
|
||||
endpoints.forEach(e => {
|
||||
if (e.name === Endpoint.metricsui && e.endpoint && e.endpoint.indexOf('/d/wZx3OUdmz') === -1) {
|
||||
// Update to have correct URL
|
||||
e.endpoint += '/d/wZx3OUdmz';
|
||||
}
|
||||
if (e.name === Endpoint.logsui && e.endpoint && e.endpoint.indexOf('/app/kibana#/discover') === -1) {
|
||||
// Update to have correct URL
|
||||
e.endpoint += '/app/kibana#/discover';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,468 +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 * as vscode from 'vscode';
|
||||
import { BdcDashboardModel, BdcErrorEvent } from './bdcDashboardModel';
|
||||
import { IconPathHelper, cssStyles } from '../constants';
|
||||
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint, getBdcStatusErrorMessage } from '../utils';
|
||||
import { EndpointModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||
import { createViewDetailsButton } from './commonControls';
|
||||
import { HdfsDialogCancelledError } from './hdfsDialogBase';
|
||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
const hyperlinkedEndpoints = [Endpoint.metricsui, Endpoint.logsui, Endpoint.sparkHistory, Endpoint.yarnUi];
|
||||
|
||||
export class BdcDashboardOverviewPage extends BdcDashboardPage {
|
||||
private rootContainer: azdata.FlexContainer;
|
||||
private lastUpdatedLabel: azdata.TextComponent;
|
||||
private propertiesContainerLoadingComponent: azdata.LoadingComponent;
|
||||
|
||||
private serviceStatusTable: azdata.DeclarativeTableComponent;
|
||||
private endpointsTable: azdata.DeclarativeTableComponent;
|
||||
private endpointsLoadingComponent: azdata.LoadingComponent;
|
||||
private endpointsDisplayContainer: azdata.FlexContainer;
|
||||
private serviceStatusLoadingComponent: azdata.LoadingComponent;
|
||||
private serviceStatusDisplayContainer: azdata.FlexContainer;
|
||||
private propertiesErrorMessage: azdata.TextComponent;
|
||||
private endpointsErrorMessage: azdata.TextComponent;
|
||||
private serviceStatusErrorMessage: azdata.TextComponent;
|
||||
|
||||
constructor(model: BdcDashboardModel, modelView: azdata.ModelView, private dashboard: azdata.window.ModelViewDashboard) {
|
||||
super(model, modelView);
|
||||
this.model.onDidUpdateEndpoints(endpoints => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdate(endpoints)));
|
||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||
this.model.onBdcError(error => this.eventuallyRunOnInitialized(() => this.handleBdcError(error)));
|
||||
}
|
||||
|
||||
public get container(): azdata.FlexContainer {
|
||||
// Lazily create the container only when needed
|
||||
if (!this.rootContainer) {
|
||||
this.rootContainer = this.createContainer();
|
||||
}
|
||||
return this.rootContainer;
|
||||
}
|
||||
|
||||
public createContainer(): azdata.FlexContainer {
|
||||
const rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
||||
{
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
|
||||
// ##############
|
||||
// # PROPERTIES #
|
||||
// ##############
|
||||
|
||||
const propertiesLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({ value: loc.clusterProperties, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } })
|
||||
.component();
|
||||
rootContainer.addItem(propertiesLabel, { CSSStyles: { 'margin-top': '15px', 'padding-left': '10px', ...cssStyles.title } });
|
||||
|
||||
const propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
|
||||
|
||||
this.propertiesContainerLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(propertiesContainer).component();
|
||||
rootContainer.addItem(this.propertiesContainerLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '10px' } });
|
||||
|
||||
// ############
|
||||
// # OVERVIEW #
|
||||
// ############
|
||||
|
||||
const overviewHeaderContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
||||
rootContainer.addItem(overviewHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
||||
|
||||
const overviewLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.clusterOverview,
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
})
|
||||
.component();
|
||||
|
||||
overviewHeaderContainer.addItem(overviewLabel, { CSSStyles: { ...cssStyles.title } });
|
||||
|
||||
this.lastUpdatedLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.lastUpdated(),
|
||||
CSSStyles: { ...cssStyles.lastUpdatedText }
|
||||
}).component();
|
||||
|
||||
overviewHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
||||
|
||||
const overviewContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||
|
||||
this.serviceStatusTable = this.modelView.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: [
|
||||
{ // status icon
|
||||
displayName: '',
|
||||
ariaLabel: loc.statusIcon,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 25,
|
||||
headerCssStyles: {
|
||||
'border': 'none'
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // service
|
||||
displayName: loc.serviceName,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 175,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // state
|
||||
displayName: loc.state,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // health status
|
||||
displayName: loc.healthStatus,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
},
|
||||
{ // view details button
|
||||
displayName: '',
|
||||
ariaLabel: loc.viewErrorDetails,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: loc.clusterOverview
|
||||
})
|
||||
.component();
|
||||
|
||||
this.serviceStatusDisplayContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusTable);
|
||||
|
||||
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||
// component once it's finished loading the content
|
||||
this.serviceStatusLoadingComponent = this.modelView.modelBuilder.loadingComponent()
|
||||
.withProps({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||
.component();
|
||||
|
||||
this.serviceStatusDisplayContainer.addItem(this.serviceStatusLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||
|
||||
this.serviceStatusErrorMessage = this.modelView.modelBuilder.text().withProps({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||
overviewContainer.addItem(this.serviceStatusErrorMessage);
|
||||
|
||||
overviewContainer.addItem(this.serviceStatusDisplayContainer);
|
||||
|
||||
rootContainer.addItem(overviewContainer, { flex: '0 0 auto' });
|
||||
|
||||
// #####################
|
||||
// # SERVICE ENDPOINTS #
|
||||
// #####################
|
||||
|
||||
const endpointsLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({ value: loc.serviceEndpoints, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
||||
.component();
|
||||
rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
||||
|
||||
this.endpointsErrorMessage = this.modelView.modelBuilder.text().withProps({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
|
||||
|
||||
const endpointsContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||
|
||||
this.endpointsTable = this.modelView.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: [
|
||||
{ // service
|
||||
displayName: loc.service,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 200,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // endpoint
|
||||
displayName: loc.endpoint,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 350,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
'overflow': 'hidden',
|
||||
'text-overflow': 'ellipsis'
|
||||
},
|
||||
},
|
||||
{ // copy
|
||||
displayName: '',
|
||||
ariaLabel: loc.copy,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: loc.serviceEndpoints
|
||||
}).component();
|
||||
|
||||
this.endpointsDisplayContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this.endpointsDisplayContainer.addItem(this.endpointsTable);
|
||||
|
||||
// Note we don't make the table a child of the loading component since making the loading component align correctly
|
||||
// messes up the layout for the table that we display after loading is finished. Instead we'll just remove the loading
|
||||
// component once it's finished loading the content
|
||||
this.endpointsLoadingComponent = this.modelView.modelBuilder.loadingComponent()
|
||||
.withProps({ CSSStyles: { 'padding-top': '0px', 'padding-bottom': '0px' } })
|
||||
.component();
|
||||
this.endpointsDisplayContainer.addItem(this.endpointsLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'padding-left': '150px', width: '30px' } });
|
||||
|
||||
endpointsContainer.addItem(this.endpointsErrorMessage);
|
||||
endpointsContainer.addItem(this.endpointsDisplayContainer);
|
||||
rootContainer.addItem(endpointsContainer, { flex: '0 0 auto' });
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Now that we've created the UI load data from the model in case it already had data
|
||||
this.handleEndpointsUpdate(this.model.serviceEndpoints);
|
||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
||||
|
||||
return rootContainer;
|
||||
}
|
||||
|
||||
public onRefreshStarted(): void {
|
||||
this.propertiesErrorMessage.display = 'none';
|
||||
this.serviceStatusErrorMessage.display = 'none';
|
||||
this.endpointsErrorMessage.display = 'none';
|
||||
|
||||
this.serviceStatusDisplayContainer.display = undefined;
|
||||
this.propertiesContainerLoadingComponent.display = undefined;
|
||||
this.endpointsDisplayContainer.display = undefined;
|
||||
}
|
||||
|
||||
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
||||
if (!bdcStatus) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdatedLabel.value = loc.lastUpdated(this.model.bdcStatusLastUpdated);
|
||||
|
||||
this.propertiesContainerLoadingComponent.loading = false;
|
||||
|
||||
(<azdata.PropertiesContainerComponentProperties>this.propertiesContainerLoadingComponent.component).propertyItems = [
|
||||
{ displayName: loc.clusterState, value: getStateDisplayText(bdcStatus.state) },
|
||||
{ displayName: loc.healthStatus, value: getHealthStatusDisplayText(bdcStatus.healthStatus) }
|
||||
];
|
||||
|
||||
if (bdcStatus.services) {
|
||||
this.serviceStatusTable.data = bdcStatus.services.map(serviceStatus => {
|
||||
const statusIconCell = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: getHealthStatusIcon(serviceStatus.healthStatus),
|
||||
ariaRole: 'img',
|
||||
title: getHealthStatusDisplayText(serviceStatus.healthStatus),
|
||||
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
||||
}).component();
|
||||
const nameCell = this.modelView.modelBuilder.hyperlink()
|
||||
.withProps({
|
||||
label: getServiceNameDisplayText(serviceStatus.serviceName),
|
||||
url: '',
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
}).component();
|
||||
nameCell.onDidClick(() => {
|
||||
this.dashboard.selectTab(serviceStatus.serviceName);
|
||||
});
|
||||
|
||||
const viewDetailsButton = serviceStatus.healthStatus !== 'healthy' && serviceStatus.details && serviceStatus.details.length > 0 ? createViewDetailsButton(this.modelView.modelBuilder, serviceStatus.details) : undefined;
|
||||
return [
|
||||
statusIconCell,
|
||||
nameCell,
|
||||
getStateDisplayText(serviceStatus.state),
|
||||
getHealthStatusDisplayText(serviceStatus.healthStatus),
|
||||
viewDetailsButton];
|
||||
});
|
||||
this.serviceStatusDisplayContainer.removeItem(this.serviceStatusLoadingComponent);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEndpointsUpdate(endpoints?: EndpointModel[]): void {
|
||||
if (!endpoints) {
|
||||
return;
|
||||
}
|
||||
// Sort the endpoints. The sort method is that SQL Server Master is first - followed by all
|
||||
// others in alphabetical order by endpoint
|
||||
const sqlServerMasterEndpoints = endpoints.filter(e => e.name === Endpoint.sqlServerMaster);
|
||||
endpoints = endpoints.filter(e => e.name !== Endpoint.sqlServerMaster)
|
||||
.sort((e1, e2) => {
|
||||
if (e1.endpoint < e2.endpoint) { return -1; }
|
||||
if (e1.endpoint > e2.endpoint) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
endpoints.unshift(...sqlServerMasterEndpoints);
|
||||
|
||||
this.endpointsTable.dataValues = endpoints.map(e => {
|
||||
const copyValueCell = this.modelView.modelBuilder.button().withProps({ title: loc.copy }).component();
|
||||
copyValueCell.iconPath = IconPathHelper.copy;
|
||||
copyValueCell.onDidClick(() => {
|
||||
vscode.env.clipboard.writeText(e.endpoint);
|
||||
vscode.window.showInformationMessage(loc.copiedEndpoint(getEndpointDisplayText(e.name, e.description)));
|
||||
});
|
||||
return [{ value: getEndpointDisplayText(e.name, e.description) },
|
||||
{ value: createEndpointComponent(this.modelView.modelBuilder, e, this.model, hyperlinkedEndpoints.some(he => he === e.name)) },
|
||||
{ value: copyValueCell }];
|
||||
});
|
||||
|
||||
this.endpointsDisplayContainer.removeItem(this.endpointsLoadingComponent);
|
||||
}
|
||||
|
||||
private handleBdcError(errorEvent: BdcErrorEvent): void {
|
||||
if (errorEvent.errorType === 'bdcEndpoints') {
|
||||
const errorMessage = loc.endpointsError(errorEvent.error.message);
|
||||
this.showEndpointsError(errorMessage);
|
||||
} else if (errorEvent.errorType === 'bdcStatus') {
|
||||
this.showBdcStatusError(getBdcStatusErrorMessage(errorEvent.error));
|
||||
} else {
|
||||
this.handleGeneralError(errorEvent.error);
|
||||
}
|
||||
}
|
||||
|
||||
private showBdcStatusError(errorMessage: string): void {
|
||||
this.serviceStatusDisplayContainer.display = 'none';
|
||||
this.propertiesContainerLoadingComponent.display = 'none';
|
||||
this.serviceStatusErrorMessage.value = errorMessage;
|
||||
this.serviceStatusErrorMessage.display = undefined;
|
||||
this.propertiesErrorMessage.value = errorMessage;
|
||||
this.propertiesErrorMessage.display = undefined;
|
||||
}
|
||||
|
||||
private showEndpointsError(errorMessage: string): void {
|
||||
this.endpointsDisplayContainer.display = 'none';
|
||||
this.endpointsErrorMessage.display = undefined;
|
||||
this.endpointsErrorMessage.value = errorMessage;
|
||||
}
|
||||
|
||||
private handleGeneralError(error: Error): void {
|
||||
if (error instanceof HdfsDialogCancelledError) {
|
||||
const errorMessage = loc.noConnectionError;
|
||||
this.showBdcStatusError(errorMessage);
|
||||
this.showEndpointsError(errorMessage);
|
||||
} else {
|
||||
const errorMessage = loc.unexpectedError(error);
|
||||
this.showBdcStatusError(errorMessage);
|
||||
this.showEndpointsError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createEndpointComponent(modelBuilder: azdata.ModelBuilder, endpoint: EndpointModel, bdcModel: BdcDashboardModel, isHyperlink: boolean): azdata.HyperlinkComponent | azdata.TextComponent {
|
||||
if (isHyperlink) {
|
||||
return modelBuilder.hyperlink()
|
||||
.withProps({
|
||||
label: endpoint.endpoint,
|
||||
title: endpoint.endpoint,
|
||||
url: endpoint.endpoint
|
||||
})
|
||||
.component();
|
||||
}
|
||||
else if (endpoint.name === Endpoint.sqlServerMaster) {
|
||||
const endpointCell = modelBuilder.hyperlink()
|
||||
.withProps({
|
||||
title: endpoint.endpoint,
|
||||
label: endpoint.endpoint,
|
||||
url: '',
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
}).component();
|
||||
endpointCell.onDidClick(async () => {
|
||||
const connProfile = bdcModel.getSqlServerMasterConnectionProfile();
|
||||
const result = await azdata.connection.connect(connProfile, true, true);
|
||||
if (!result.connected) {
|
||||
if (result.errorMessage && result.errorMessage.length > 0) {
|
||||
vscode.window.showErrorMessage(result.errorMessage);
|
||||
}
|
||||
// Clear out the password and username before connecting since those being wrong are likely the issue
|
||||
connProfile.userName = undefined;
|
||||
connProfile.password = undefined;
|
||||
azdata.connection.openConnectionDialog(undefined, connProfile);
|
||||
}
|
||||
});
|
||||
return endpointCell;
|
||||
}
|
||||
else {
|
||||
return modelBuilder.text()
|
||||
.withProps({
|
||||
value: endpoint.endpoint,
|
||||
title: endpoint.endpoint,
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
})
|
||||
.component();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +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 { IconPathHelper } from '../constants';
|
||||
import { BdcDashboardModel, getTroubleshootNotebookUrl } from './bdcDashboardModel';
|
||||
import * as loc from '../localizedConstants';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { InitializingComponent } from './intializingComponent';
|
||||
|
||||
export abstract class BdcDashboardPage extends InitializingComponent {
|
||||
|
||||
private _toolbarContainer: azdata.ToolbarContainer;
|
||||
private _refreshButton: azdata.ButtonComponent;
|
||||
|
||||
constructor(protected model: BdcDashboardModel, protected modelView: azdata.ModelView, protected serviceName?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Lazily create the container only when needed
|
||||
if (!this._toolbarContainer) {
|
||||
this._toolbarContainer = this.createToolbarContainer();
|
||||
}
|
||||
return this._toolbarContainer;
|
||||
}
|
||||
|
||||
protected createToolbarContainer(): azdata.ToolbarContainer {
|
||||
// Refresh button
|
||||
this._refreshButton = this.modelView.modelBuilder.button()
|
||||
.withProps({
|
||||
label: loc.refresh,
|
||||
iconPath: IconPathHelper.refresh
|
||||
}).component();
|
||||
|
||||
this._refreshButton.onDidClick(async () => {
|
||||
await this.doRefresh();
|
||||
});
|
||||
|
||||
const openTroubleshootNotebookButton = this.modelView.modelBuilder.button()
|
||||
.withProps({
|
||||
label: loc.troubleshoot,
|
||||
iconPath: IconPathHelper.notebook
|
||||
}).component();
|
||||
|
||||
openTroubleshootNotebookButton.onDidClick(() => {
|
||||
vscode.commands.executeCommand('books.sqlserver2019', getTroubleshootNotebookUrl(this.serviceName));
|
||||
});
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems(
|
||||
[
|
||||
{ component: this._refreshButton },
|
||||
{ component: openTroubleshootNotebookButton }
|
||||
]
|
||||
).component();
|
||||
}
|
||||
|
||||
private async doRefresh(): Promise<void> {
|
||||
try {
|
||||
this._refreshButton.enabled = false;
|
||||
await this.model.refresh();
|
||||
} finally {
|
||||
this._refreshButton.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,358 +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 { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { BdcStatusModel, InstanceStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
||||
import { getHealthStatusDisplayText, getHealthStatusIcon, getStateDisplayText, Service } from '../utils';
|
||||
import { cssStyles } from '../constants';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { createViewDetailsButton } from './commonControls';
|
||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
|
||||
|
||||
private resourceStatusModel: ResourceStatusModel;
|
||||
private rootContainer: azdata.FlexContainer;
|
||||
private instanceHealthStatusTable: azdata.DeclarativeTableComponent;
|
||||
private metricsAndLogsRowsTable: azdata.DeclarativeTableComponent;
|
||||
private lastUpdatedLabel: azdata.TextComponent;
|
||||
|
||||
constructor(model: BdcDashboardModel, modelView: azdata.ModelView, serviceName: string, private resourceName: string) {
|
||||
super(model, modelView, serviceName);
|
||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||
}
|
||||
|
||||
public get container(): azdata.FlexContainer {
|
||||
// Lazily create the container only when needed
|
||||
if (!this.rootContainer) {
|
||||
// We do this here so that we can have the resource model to use for populating the data
|
||||
// in the tables. This is to get around a timing issue with ModelView tables
|
||||
this.updateResourceStatusModel(this.model.bdcStatus);
|
||||
this.createContainer();
|
||||
}
|
||||
return this.rootContainer;
|
||||
}
|
||||
|
||||
private createContainer(): void {
|
||||
this.rootContainer = this.modelView.modelBuilder.flexContainer().withLayout(
|
||||
{
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
|
||||
// ##############################
|
||||
// # INSTANCE HEALTH AND STATUS #
|
||||
// ##############################
|
||||
|
||||
const healthStatusHeaderContainer = this.modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '20px' }).component();
|
||||
this.rootContainer.addItem(healthStatusHeaderContainer, { CSSStyles: { 'padding-left': '10px', 'padding-top': '15px' } });
|
||||
|
||||
// Header label
|
||||
const healthStatusHeaderLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.healthStatusDetails,
|
||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' }
|
||||
})
|
||||
.component();
|
||||
|
||||
healthStatusHeaderContainer.addItem(healthStatusHeaderLabel, { CSSStyles: { ...cssStyles.title } });
|
||||
|
||||
// Last updated label
|
||||
this.lastUpdatedLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.lastUpdated(this.model.bdcStatusLastUpdated),
|
||||
CSSStyles: { ...cssStyles.lastUpdatedText }
|
||||
}).component();
|
||||
|
||||
healthStatusHeaderContainer.addItem(this.lastUpdatedLabel, { CSSStyles: { 'margin-left': '45px' } });
|
||||
|
||||
this.instanceHealthStatusTable = this.modelView.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: [
|
||||
{ // status icon
|
||||
displayName: '',
|
||||
ariaLabel: loc.statusIcon,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 25,
|
||||
headerCssStyles: {
|
||||
'border': 'none'
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // instance
|
||||
displayName: loc.instance,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // state
|
||||
displayName: loc.state,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
{ // health status
|
||||
displayName: loc.healthStatus,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
},
|
||||
{ // view details button
|
||||
displayName: '',
|
||||
ariaLabel: loc.viewErrorDetails,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
'border': 'none'
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
},
|
||||
},
|
||||
],
|
||||
data: this.createHealthStatusRows(),
|
||||
ariaLabel: loc.healthStatusDetails
|
||||
}).component();
|
||||
this.rootContainer.addItem(this.instanceHealthStatusTable, { flex: '0 0 auto' });
|
||||
|
||||
// ####################
|
||||
// # METRICS AND LOGS #
|
||||
// ####################
|
||||
|
||||
// Title label
|
||||
const endpointsLabel = this.modelView.modelBuilder.text()
|
||||
.withProps({ value: loc.metricsAndLogs, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
|
||||
.component();
|
||||
this.rootContainer.addItem(endpointsLabel, { CSSStyles: { 'padding-left': '10px', ...cssStyles.title } });
|
||||
|
||||
let metricsAndLogsColumns: azdata.DeclarativeTableColumn[] =
|
||||
[
|
||||
{ // instance
|
||||
displayName: loc.instance,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 125,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
},
|
||||
{ // node metrics
|
||||
displayName: loc.nodeMetrics,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Only show SQL metrics column for SQL resource instances
|
||||
if (this.serviceName.toLowerCase() === Service.sql) {
|
||||
metricsAndLogsColumns.push(
|
||||
{ // sql metrics
|
||||
displayName: loc.sqlMetrics,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
metricsAndLogsColumns.push(
|
||||
{ // logs
|
||||
displayName: loc.logs,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
...cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none'
|
||||
}
|
||||
});
|
||||
|
||||
this.metricsAndLogsRowsTable = this.modelView.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: metricsAndLogsColumns,
|
||||
data: this.createMetricsAndLogsRows(),
|
||||
ariaLabel: loc.metricsAndLogs
|
||||
}).component();
|
||||
this.rootContainer.addItem(this.metricsAndLogsRowsTable, { flex: '0 0 auto' });
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private updateResourceStatusModel(bdcStatus?: BdcStatusModel): void {
|
||||
// If we can't find the resource model for this resource then just
|
||||
// default to keeping what we had originally
|
||||
if (!bdcStatus) {
|
||||
return;
|
||||
}
|
||||
const service = bdcStatus.services ? bdcStatus.services.find(s => s.serviceName === this.serviceName) : undefined;
|
||||
this.resourceStatusModel = service ? service.resources.find(r => r.resourceName === this.resourceName) : this.resourceStatusModel;
|
||||
}
|
||||
|
||||
private handleBdcStatusUpdate(bdcStatus?: BdcStatusModel): void {
|
||||
this.updateResourceStatusModel(bdcStatus);
|
||||
|
||||
if (!this.resourceStatusModel || isNullOrUndefined(this.resourceStatusModel.instances)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastUpdatedLabel.value = loc.lastUpdated(this.model.bdcStatusLastUpdated);
|
||||
|
||||
this.instanceHealthStatusTable.data = this.createHealthStatusRows();
|
||||
|
||||
this.metricsAndLogsRowsTable.data = this.createMetricsAndLogsRows();
|
||||
}
|
||||
|
||||
private createMetricsAndLogsRows(): any[][] {
|
||||
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createMetricsAndLogsRow(instanceStatus)) : [];
|
||||
}
|
||||
|
||||
private createHealthStatusRows(): any[][] {
|
||||
return this.resourceStatusModel ? this.resourceStatusModel.instances.map(instanceStatus => this.createHealthStatusRow(instanceStatus)) : [];
|
||||
}
|
||||
|
||||
private createMetricsAndLogsRow(instanceStatus: InstanceStatusModel): any[] {
|
||||
const row: any[] = [instanceStatus.instanceName];
|
||||
|
||||
// Not all instances have all logs available - in that case just display N/A instead of a link
|
||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.nodeMetricsUrl)) {
|
||||
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
||||
} else {
|
||||
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.view,
|
||||
url: instanceStatus.dashboards.nodeMetricsUrl,
|
||||
title: instanceStatus.dashboards.nodeMetricsUrl,
|
||||
ariaLabel: loc.viewNodeMetrics(instanceStatus.dashboards.nodeMetricsUrl),
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
}).component());
|
||||
}
|
||||
|
||||
// Only show SQL metrics column for SQL resource instances
|
||||
if (this.serviceName === Service.sql) {
|
||||
// Not all instances have all logs available - in that case just display N/A instead of a link
|
||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.sqlMetricsUrl)) {
|
||||
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
||||
} else {
|
||||
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.view,
|
||||
url: instanceStatus.dashboards.sqlMetricsUrl,
|
||||
title: instanceStatus.dashboards.sqlMetricsUrl,
|
||||
ariaLabel: loc.viewSqlMetrics(instanceStatus.dashboards.sqlMetricsUrl),
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
}).component());
|
||||
}
|
||||
}
|
||||
|
||||
if (isNullOrUndefined(instanceStatus.dashboards) || isNullOrUndefined(instanceStatus.dashboards.logsUrl)) {
|
||||
row.push(this.modelView.modelBuilder.text().withProps({ value: loc.notAvailable, CSSStyles: { ...cssStyles.text } }).component());
|
||||
} else {
|
||||
row.push(this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.view,
|
||||
url: instanceStatus.dashboards.logsUrl,
|
||||
title: instanceStatus.dashboards.logsUrl,
|
||||
ariaLabel: loc.viewLogs(instanceStatus.dashboards.logsUrl),
|
||||
CSSStyles: { ...cssStyles.text }
|
||||
}).component());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private createHealthStatusRow(instanceStatus: InstanceStatusModel): any[] {
|
||||
const statusIconCell = this.modelView.modelBuilder.text()
|
||||
.withProps({
|
||||
value: getHealthStatusIcon(instanceStatus.healthStatus),
|
||||
ariaRole: 'img',
|
||||
title: getHealthStatusDisplayText(instanceStatus.healthStatus),
|
||||
CSSStyles: { 'user-select': 'none', ...cssStyles.text }
|
||||
}).component();
|
||||
|
||||
const viewDetailsButton = instanceStatus.healthStatus !== 'healthy' && instanceStatus.details && instanceStatus.details.length > 0 ? createViewDetailsButton(this.modelView.modelBuilder, instanceStatus.details) : undefined;
|
||||
return [
|
||||
statusIconCell,
|
||||
instanceStatus.instanceName,
|
||||
getStateDisplayText(instanceStatus.state),
|
||||
getHealthStatusDisplayText(instanceStatus.healthStatus),
|
||||
viewDetailsButton];
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { BdcStatusModel, ResourceStatusModel } from '../controller/apiGenerated';
|
||||
import { BdcDashboardResourceStatusPage } from './bdcDashboardResourceStatusPage';
|
||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { BdcDashboardPage } from './bdcDashboardPage';
|
||||
import { getHealthStatusDotIcon } from '../utils';
|
||||
|
||||
export class BdcServiceStatusPage extends BdcDashboardPage {
|
||||
|
||||
private createdResourceTabs: Map<string, azdata.Tab> = new Map<string, azdata.Tab>();
|
||||
private tabbedPanel: azdata.TabbedPanelComponent;
|
||||
|
||||
constructor(serviceName: string, model: BdcDashboardModel, modelView: azdata.ModelView) {
|
||||
super(model, modelView, serviceName);
|
||||
this.model.onDidUpdateBdcStatus(bdcStatus => this.eventuallyRunOnInitialized(() => this.handleBdcStatusUpdate(bdcStatus)));
|
||||
}
|
||||
|
||||
public get container(): azdata.TabbedPanelComponent {
|
||||
// Lazily create the container only when needed
|
||||
if (!this.tabbedPanel) {
|
||||
this.createPage();
|
||||
}
|
||||
return this.tabbedPanel;
|
||||
}
|
||||
|
||||
private createPage(): void {
|
||||
this.tabbedPanel = this.modelView.modelBuilder.tabbedPanel()
|
||||
.withLayout({ showIcon: true, alwaysShowTabs: true }).component();
|
||||
|
||||
// Initialize our set of tab pages
|
||||
this.handleBdcStatusUpdate(this.model.bdcStatus);
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private handleBdcStatusUpdate(bdcStatus: BdcStatusModel): void {
|
||||
if (!bdcStatus) {
|
||||
return;
|
||||
}
|
||||
const service = bdcStatus.services.find(s => s.serviceName === this.serviceName);
|
||||
if (service && service.resources) {
|
||||
this.updateResourcePages(service.resources);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the resource tab pages, creating any new ones as necessary
|
||||
*/
|
||||
private updateResourcePages(resources: ResourceStatusModel[]): void {
|
||||
resources.forEach(resource => {
|
||||
const existingTab = this.createdResourceTabs.get(resource.resourceName);
|
||||
if (existingTab) {
|
||||
existingTab.icon = getHealthStatusDotIcon(resource.healthStatus);
|
||||
} else {
|
||||
const resourceStatusPage = new BdcDashboardResourceStatusPage(this.model, this.modelView, this.serviceName, resource.resourceName);
|
||||
const newTab: azdata.Tab = {
|
||||
title: resource.resourceName,
|
||||
id: resource.resourceName,
|
||||
content: resourceStatusPage.container,
|
||||
icon: getHealthStatusDotIcon(resource.healthStatus)
|
||||
};
|
||||
this.createdResourceTabs.set(resource.resourceName, newTab);
|
||||
}
|
||||
});
|
||||
this.tabbedPanel.updateTabs(Array.from(this.createdResourceTabs.values()));
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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 * as vscode from 'vscode';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
export function createViewDetailsButton(modelBuilder: azdata.ModelBuilder, text: string): azdata.ButtonComponent {
|
||||
const viewDetailsButton = modelBuilder.button().withProps({
|
||||
label: loc.viewDetails,
|
||||
ariaLabel: loc.viewErrorDetails,
|
||||
secondary: true
|
||||
}).component();
|
||||
viewDetailsButton.onDidClick(() => {
|
||||
vscode.window.showErrorMessage(text, { modal: true });
|
||||
});
|
||||
return viewDetailsButton;
|
||||
}
|
||||
@@ -1,49 +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 { HdfsDialogBase, HdfsDialogModelBase, HdfsDialogProperties } from './hdfsDialogBase';
|
||||
import { ClusterController } from '../controller/clusterControllerApi';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
|
||||
export class ConnectControllerDialog extends HdfsDialogBase<HdfsDialogProperties, ClusterController> {
|
||||
constructor(model: ConnectControllerModel) {
|
||||
super(loc.connectToController, model);
|
||||
}
|
||||
|
||||
protected getMainSectionComponents(): (azdata.FormComponentGroup | azdata.FormComponent)[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected async validate(): Promise<{ validated: boolean, value?: ClusterController }> {
|
||||
try {
|
||||
const controller = await this.model.onComplete({
|
||||
url: this.urlInputBox && this.urlInputBox.value,
|
||||
auth: this.authValue,
|
||||
username: this.usernameInputBox && this.usernameInputBox.value,
|
||||
password: this.passwordInputBox && this.passwordInputBox.value
|
||||
});
|
||||
return { validated: true, value: controller };
|
||||
} catch (error) {
|
||||
await this.reportError(error);
|
||||
return { validated: false, value: undefined };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectControllerModel extends HdfsDialogModelBase<HdfsDialogProperties, ClusterController> {
|
||||
|
||||
constructor(props: HdfsDialogProperties) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected async handleCompleted(): Promise<ClusterController> {
|
||||
this.throwIfMissingUsernamePassword();
|
||||
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
return await this.createAndVerifyControllerConnection();
|
||||
}
|
||||
}
|
||||
@@ -1,240 +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 { ClusterController, ControllerError } from '../controller/clusterControllerApi';
|
||||
import { Deferred } from '../../common/promise';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { AuthType, IEndPointsResponse } from 'bdc';
|
||||
|
||||
function getAuthCategory(name: AuthType): azdata.CategoryValue {
|
||||
if (name === 'basic') {
|
||||
return { name: name, displayName: loc.basic };
|
||||
}
|
||||
return { name: name, displayName: loc.windowsAuth };
|
||||
}
|
||||
|
||||
export interface HdfsDialogProperties {
|
||||
url?: string;
|
||||
auth?: AuthType;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class HdfsDialogCancelledError extends Error {
|
||||
constructor(message: string = 'Dialog cancelled') {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class HdfsDialogModelBase<T extends HdfsDialogProperties, R> {
|
||||
protected _canceled = false;
|
||||
private _authTypes: azdata.CategoryValue[];
|
||||
constructor(
|
||||
public props: T
|
||||
) {
|
||||
if (!props.auth) {
|
||||
this.props.auth = 'basic';
|
||||
}
|
||||
}
|
||||
|
||||
public get authCategories(): azdata.CategoryValue[] {
|
||||
if (!this._authTypes) {
|
||||
this._authTypes = [getAuthCategory('basic'), getAuthCategory('integrated')];
|
||||
}
|
||||
return this._authTypes;
|
||||
}
|
||||
|
||||
public get authCategory(): azdata.CategoryValue {
|
||||
return getAuthCategory(this.props.auth);
|
||||
}
|
||||
|
||||
public async onComplete(props: T): Promise<R | undefined> {
|
||||
try {
|
||||
this.props = props;
|
||||
return await this.handleCompleted();
|
||||
} catch (error) {
|
||||
// Ignore the error if we cancelled the request since we can't stop the actual request from completing
|
||||
if (!this._canceled) {
|
||||
throw error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract handleCompleted(): Promise<R>;
|
||||
|
||||
public async onError(error: ControllerError): Promise<void> {
|
||||
// implement
|
||||
}
|
||||
|
||||
public async onCancel(): Promise<void> {
|
||||
this._canceled = true;
|
||||
}
|
||||
|
||||
protected createController(): ClusterController {
|
||||
return new ClusterController(this.props.url, this.props.auth, this.props.username, this.props.password);
|
||||
}
|
||||
|
||||
protected async createAndVerifyControllerConnection(): Promise<ClusterController> {
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let controller = this.createController();
|
||||
let response: IEndPointsResponse;
|
||||
try {
|
||||
response = await controller.getEndPoints();
|
||||
if (!response || !response.endPoints) {
|
||||
throw new Error(loc.loginFailed);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(loc.loginFailedWithError(err));
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
|
||||
protected throwIfMissingUsernamePassword(): void {
|
||||
if (this.props.auth === 'basic') {
|
||||
// Verify username and password as we can't make them required in the UI
|
||||
if (!this.props.username) {
|
||||
throw new Error(loc.usernameRequired);
|
||||
} else if (!this.props.password) {
|
||||
throw new Error(loc.passwordRequired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class HdfsDialogBase<T extends HdfsDialogProperties, R> {
|
||||
|
||||
protected dialog: azdata.window.Dialog;
|
||||
protected uiModelBuilder!: azdata.ModelBuilder;
|
||||
|
||||
protected urlInputBox!: azdata.InputBoxComponent;
|
||||
protected authDropdown!: azdata.DropDownComponent;
|
||||
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||
|
||||
private returnPromise: Deferred<R>;
|
||||
|
||||
constructor(private title: string, protected model: HdfsDialogModelBase<T, R>) {
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<R> {
|
||||
this.returnPromise = new Deferred<R>();
|
||||
this.createDialog();
|
||||
azdata.window.openDialog(this.dialog);
|
||||
return this.returnPromise.promise;
|
||||
}
|
||||
|
||||
private createDialog(): void {
|
||||
this.dialog = azdata.window.createModelViewDialog(this.title);
|
||||
this.dialog.registerContent(async view => {
|
||||
this.uiModelBuilder = view.modelBuilder;
|
||||
|
||||
this.urlInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.url.toLocaleLowerCase(),
|
||||
value: this.model.props.url,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.authDropdown = this.uiModelBuilder.dropDown().withProps({
|
||||
values: this.model.authCategories,
|
||||
value: this.model.authCategory,
|
||||
editable: false,
|
||||
}).component();
|
||||
this.authDropdown.onValueChanged(e => this.onAuthChanged());
|
||||
this.usernameInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.username.toLocaleLowerCase(),
|
||||
value: this.model.props.username
|
||||
}).component();
|
||||
this.passwordInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
placeHolder: loc.password.toLocaleLowerCase(),
|
||||
inputType: 'password',
|
||||
value: this.model.props.password
|
||||
})
|
||||
.component();
|
||||
|
||||
let connectionSection: azdata.FormComponentGroup = {
|
||||
components: [
|
||||
{
|
||||
component: this.urlInputBox,
|
||||
title: loc.clusterUrl,
|
||||
required: true
|
||||
}, {
|
||||
component: this.authDropdown,
|
||||
title: loc.authType,
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: loc.username,
|
||||
required: false
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: loc.password,
|
||||
required: false
|
||||
}
|
||||
],
|
||||
title: loc.clusterConnection
|
||||
};
|
||||
let formModel = this.uiModelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
this.getMainSectionComponents().concat(
|
||||
connectionSection)
|
||||
).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
this.onAuthChanged();
|
||||
});
|
||||
|
||||
this.dialog.registerCloseValidator(async () => {
|
||||
const result = await this.validate();
|
||||
if (result.validated) {
|
||||
this.returnPromise.resolve(result.value);
|
||||
this.returnPromise = undefined;
|
||||
}
|
||||
return result.validated;
|
||||
});
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = loc.ok;
|
||||
this.dialog.cancelButton.label = loc.cancel;
|
||||
}
|
||||
|
||||
protected abstract getMainSectionComponents(): (azdata.FormComponentGroup | azdata.FormComponent)[];
|
||||
|
||||
protected get authValue(): AuthType {
|
||||
return (<azdata.CategoryValue>this.authDropdown.value).name as AuthType;
|
||||
}
|
||||
|
||||
private onAuthChanged(): void {
|
||||
let isBasic = this.authValue === 'basic';
|
||||
this.usernameInputBox.enabled = isBasic;
|
||||
this.passwordInputBox.enabled = isBasic;
|
||||
if (!isBasic) {
|
||||
this.usernameInputBox.value = '';
|
||||
this.passwordInputBox.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract validate(): Promise<{ validated: boolean, value?: R }>;
|
||||
|
||||
private async cancel(): Promise<void> {
|
||||
if (this.model && this.model.onCancel) {
|
||||
await this.model.onCancel();
|
||||
}
|
||||
this.returnPromise.reject(new HdfsDialogCancelledError());
|
||||
}
|
||||
|
||||
protected async reportError(error: any): Promise<void> {
|
||||
this.dialog.message = {
|
||||
text: (typeof error === 'string') ? error : error.message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
if (this.model && this.model.onError) {
|
||||
await this.model.onError(error as ControllerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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 { Deferred } from '../../common/promise';
|
||||
|
||||
export abstract class InitializingComponent {
|
||||
|
||||
private _initialized: boolean = false;
|
||||
|
||||
private _onInitializedPromise: Deferred<void> = new Deferred();
|
||||
|
||||
constructor() { }
|
||||
|
||||
protected get initialized(): boolean {
|
||||
return this._initialized;
|
||||
}
|
||||
|
||||
protected set initialized(value: boolean) {
|
||||
if (!this._initialized && value) {
|
||||
this._initialized = true;
|
||||
this._onInitializedPromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action when the component is initialized. If already initialized just runs
|
||||
* the action immediately.
|
||||
* @param action The action to be ran when the page is initialized
|
||||
*/
|
||||
protected eventuallyRunOnInitialized(action: () => void): void {
|
||||
if (!this._initialized) {
|
||||
this._onInitializedPromise.promise.then(() => action()).catch(error => console.error(`Unexpected error running onInitialized action for BDC Page : ${error}`));
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,378 +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 azdata from 'azdata';
|
||||
import { ClusterController, MountInfo, MountState } from '../controller/clusterControllerApi';
|
||||
import { HdfsDialogBase, HdfsDialogModelBase, HdfsDialogProperties } from './hdfsDialogBase';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
/**
|
||||
* Converts a comma-delimited set of key value pair credentials to a JSON object.
|
||||
* This code is taken from the azdata implementation written in Python
|
||||
*/
|
||||
function convertCredsToJson(creds: string): { credentials: {} } {
|
||||
if (!creds) {
|
||||
return undefined;
|
||||
}
|
||||
let credObj: { 'credentials': { [key: string]: any } } = { 'credentials': {} };
|
||||
let pairs = creds.split(',');
|
||||
let validPairs: string[] = [];
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
// handle escaped commas in a browser-agnostic way using regex:
|
||||
// this matches a string ending in a single escape character \, but not \\.
|
||||
// In this case we split on ',` when we should've ignored it as it was a \, instead.
|
||||
// Restore the escaped comma by combining the 2 split strings
|
||||
if (i < (pairs.length - 1) && pairs[i].match(/(?!\\).*\\$/)) {
|
||||
pairs[i + 1] = `${pairs[i]},${pairs[i + 1]}`;
|
||||
} else {
|
||||
validPairs.push(pairs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
validPairs.forEach(pair => {
|
||||
const formattingErr = loc.badCredentialsFormatting(pair);
|
||||
try {
|
||||
// # remove escaped characters for ,
|
||||
pair = pair.replace('\\,', ',').trim();
|
||||
let firstEquals = pair.indexOf('=');
|
||||
if (firstEquals <= 0 || firstEquals >= pair.length) {
|
||||
throw new Error(formattingErr);
|
||||
}
|
||||
let key = pair.substring(0, firstEquals);
|
||||
let value = pair.substring(firstEquals + 1);
|
||||
credObj.credentials[key] = value;
|
||||
} catch (err) {
|
||||
throw new Error(formattingErr);
|
||||
}
|
||||
});
|
||||
return credObj;
|
||||
}
|
||||
|
||||
export interface MountHdfsProperties extends HdfsDialogProperties {
|
||||
hdfsPath?: string;
|
||||
remoteUri?: string;
|
||||
credentials?: string;
|
||||
}
|
||||
|
||||
export class MountHdfsDialogModel extends HdfsDialogModelBase<MountHdfsProperties, void> {
|
||||
private credentials: {};
|
||||
|
||||
constructor(props: MountHdfsProperties) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected async handleCompleted(): Promise<void> {
|
||||
this.throwIfMissingUsernamePassword();
|
||||
// Validate credentials
|
||||
this.credentials = convertCredsToJson(this.props.credentials);
|
||||
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let controller = await this.createAndVerifyControllerConnection();
|
||||
if (this._canceled) {
|
||||
return;
|
||||
}
|
||||
azdata.tasks.startBackgroundOperation(
|
||||
{
|
||||
connection: undefined,
|
||||
displayName: loc.mountTask(this.props.hdfsPath),
|
||||
description: '',
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
this.onSubmit(controller, op);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async onSubmit(controller: ClusterController, op: azdata.BackgroundOperation): Promise<void> {
|
||||
try {
|
||||
await controller.mountHdfs(this.props.hdfsPath, this.props.remoteUri, this.credentials);
|
||||
op.updateStatus(azdata.TaskStatus.InProgress, loc.mountTaskSubmitted);
|
||||
|
||||
// Wait until status has changed or some sensible time expired. If it goes over 2 minutes we assume it's "working"
|
||||
// as there's no other API that'll give us this for now
|
||||
let result = await this.waitOnMountStatusChange(controller);
|
||||
let msg = result.state === MountState.Ready ? loc.mountCompleted : loc.mountInProgress;
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded, msg);
|
||||
} catch (error) {
|
||||
const errMsg = loc.mountError(error);
|
||||
vscode.window.showErrorMessage(errMsg);
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private waitOnMountStatusChange(controller: ClusterController): Promise<MountInfo> {
|
||||
return new Promise<MountInfo>((resolve, reject) => {
|
||||
const waitTime = 5 * 1000; // 5 seconds
|
||||
const maxRetries = 30; // 5 x 30 = 150 seconds. After this time, can assume things are "working" as 2 min timeout passed
|
||||
let waitOnChange = async (retries: number) => {
|
||||
try {
|
||||
let mountInfo = await this.getMountStatus(controller, this.props.hdfsPath);
|
||||
if (mountInfo && mountInfo.error || mountInfo.state === MountState.Error) {
|
||||
reject(new Error(mountInfo.error ? mountInfo.error : loc.mountErrorUnknown));
|
||||
} else if (mountInfo.state === MountState.Ready || retries <= 0) {
|
||||
resolve(mountInfo);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
waitOnChange(retries - 1).catch(e => reject(e));
|
||||
}, waitTime);
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
waitOnChange(maxRetries);
|
||||
});
|
||||
}
|
||||
|
||||
private async getMountStatus(controller: ClusterController, path: string): Promise<MountInfo> {
|
||||
let statusResponse = await controller.getMountStatus(path);
|
||||
if (statusResponse.mount) {
|
||||
return Array.isArray(statusResponse.mount) ? statusResponse.mount[0] : statusResponse.mount;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class MountHdfsDialog extends HdfsDialogBase<MountHdfsProperties, void> {
|
||||
private pathInputBox: azdata.InputBoxComponent;
|
||||
private remoteUriInputBox: azdata.InputBoxComponent;
|
||||
private credentialsInputBox: azdata.InputBoxComponent;
|
||||
|
||||
constructor(model: MountHdfsDialogModel) {
|
||||
super(loc.mountFolder, model);
|
||||
}
|
||||
|
||||
protected getMainSectionComponents(): (azdata.FormComponentGroup | azdata.FormComponent)[] {
|
||||
const newMountName = '/mymount';
|
||||
let pathVal = this.model.props.hdfsPath;
|
||||
pathVal = (!pathVal || pathVal === '/') ? newMountName : (pathVal + newMountName);
|
||||
this.pathInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: pathVal
|
||||
}).component();
|
||||
this.remoteUriInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: this.model.props.remoteUri
|
||||
})
|
||||
.component();
|
||||
this.credentialsInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
inputType: 'password',
|
||||
value: this.model.props.credentials
|
||||
})
|
||||
.component();
|
||||
|
||||
return [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
component: this.pathInputBox,
|
||||
title: loc.hdfsPath,
|
||||
required: true,
|
||||
layout: {
|
||||
info: loc.hdfsPathInfo
|
||||
}
|
||||
}, {
|
||||
component: this.remoteUriInputBox,
|
||||
title: loc.remoteUri,
|
||||
required: true,
|
||||
layout: {
|
||||
info: loc.remoteUriInfo
|
||||
}
|
||||
}, {
|
||||
component: this.credentialsInputBox,
|
||||
title: loc.credentials,
|
||||
required: false,
|
||||
layout: {
|
||||
info: loc.credentialsInfo
|
||||
}
|
||||
}
|
||||
],
|
||||
title: loc.mountConfiguration
|
||||
}];
|
||||
}
|
||||
|
||||
protected async validate(): Promise<{ validated: boolean }> {
|
||||
try {
|
||||
await this.model.onComplete({
|
||||
url: this.urlInputBox && this.urlInputBox.value,
|
||||
auth: this.authValue,
|
||||
username: this.usernameInputBox && this.usernameInputBox.value,
|
||||
password: this.passwordInputBox && this.passwordInputBox.value,
|
||||
hdfsPath: this.pathInputBox && this.pathInputBox.value,
|
||||
remoteUri: this.remoteUriInputBox && this.remoteUriInputBox.value,
|
||||
credentials: this.credentialsInputBox && this.credentialsInputBox.value
|
||||
});
|
||||
return { validated: true };
|
||||
} catch (error) {
|
||||
await this.reportError(error);
|
||||
return { validated: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshMountDialog extends HdfsDialogBase<MountHdfsProperties, void> {
|
||||
private pathInputBox: azdata.InputBoxComponent;
|
||||
|
||||
constructor(model: RefreshMountModel) {
|
||||
super(loc.refreshMount, model);
|
||||
}
|
||||
|
||||
protected getMainSectionComponents(): (azdata.FormComponentGroup | azdata.FormComponent)[] {
|
||||
this.pathInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: this.model.props.hdfsPath
|
||||
}).component();
|
||||
return [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
component: this.pathInputBox,
|
||||
title: loc.hdfsPath,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
title: loc.mountConfiguration
|
||||
}];
|
||||
}
|
||||
|
||||
protected async validate(): Promise<{ validated: boolean }> {
|
||||
try {
|
||||
await this.model.onComplete({
|
||||
url: this.urlInputBox && this.urlInputBox.value,
|
||||
auth: this.authValue,
|
||||
username: this.usernameInputBox && this.usernameInputBox.value,
|
||||
password: this.passwordInputBox && this.passwordInputBox.value,
|
||||
hdfsPath: this.pathInputBox && this.pathInputBox.value
|
||||
});
|
||||
return { validated: true };
|
||||
} catch (error) {
|
||||
await this.reportError(error);
|
||||
return { validated: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshMountModel extends HdfsDialogModelBase<MountHdfsProperties, void> {
|
||||
|
||||
constructor(props: MountHdfsProperties) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected async handleCompleted(): Promise<void> {
|
||||
this.throwIfMissingUsernamePassword();
|
||||
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let controller = await this.createAndVerifyControllerConnection();
|
||||
if (this._canceled) {
|
||||
return;
|
||||
}
|
||||
azdata.tasks.startBackgroundOperation(
|
||||
{
|
||||
connection: undefined,
|
||||
displayName: loc.refreshMountTask(this.props.hdfsPath),
|
||||
description: '',
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
this.onSubmit(controller, op);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async onSubmit(controller: ClusterController, op: azdata.BackgroundOperation): Promise<void> {
|
||||
try {
|
||||
await controller.refreshMount(this.props.hdfsPath);
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded, loc.refreshMountTaskSubmitted);
|
||||
} catch (error) {
|
||||
const errMsg = (error instanceof Error) ? error.message : error;
|
||||
vscode.window.showErrorMessage(errMsg);
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteMountDialog extends HdfsDialogBase<MountHdfsProperties, void> {
|
||||
private pathInputBox: azdata.InputBoxComponent;
|
||||
|
||||
constructor(model: DeleteMountModel) {
|
||||
super(loc.deleteMount, model);
|
||||
}
|
||||
|
||||
protected getMainSectionComponents(): (azdata.FormComponentGroup | azdata.FormComponent)[] {
|
||||
this.pathInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: this.model.props.hdfsPath
|
||||
}).component();
|
||||
return [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
component: this.pathInputBox,
|
||||
title: loc.hdfsPath,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
title: loc.mountConfiguration
|
||||
}];
|
||||
}
|
||||
|
||||
protected async validate(): Promise<{ validated: boolean }> {
|
||||
try {
|
||||
await this.model.onComplete({
|
||||
url: this.urlInputBox && this.urlInputBox.value,
|
||||
auth: this.authValue,
|
||||
username: this.usernameInputBox && this.usernameInputBox.value,
|
||||
password: this.passwordInputBox && this.passwordInputBox.value,
|
||||
hdfsPath: this.pathInputBox && this.pathInputBox.value
|
||||
});
|
||||
return { validated: true };
|
||||
} catch (error) {
|
||||
await this.reportError(error);
|
||||
return { validated: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteMountModel extends HdfsDialogModelBase<MountHdfsProperties, void> {
|
||||
|
||||
constructor(props: MountHdfsProperties) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected async handleCompleted(): Promise<void> {
|
||||
this.throwIfMissingUsernamePassword();
|
||||
|
||||
// We pre-fetch the endpoints here to verify that the information entered is correct (the user is able to connect)
|
||||
let controller = await this.createAndVerifyControllerConnection();
|
||||
if (this._canceled) {
|
||||
return;
|
||||
}
|
||||
azdata.tasks.startBackgroundOperation(
|
||||
{
|
||||
connection: undefined,
|
||||
displayName: loc.deleteMountTask(this.props.hdfsPath),
|
||||
description: '',
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
this.onSubmit(controller, op);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async onSubmit(controller: ClusterController, op: azdata.BackgroundOperation): Promise<void> {
|
||||
try {
|
||||
await controller.deleteMount(this.props.hdfsPath);
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded, loc.deleteMountTaskSubmitted);
|
||||
} catch (error) {
|
||||
const errMsg = (error instanceof Error) ? error.message : error;
|
||||
vscode.window.showErrorMessage(errMsg);
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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';
|
||||
import { ControllerError } from './controller/clusterControllerApi';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
// Labels
|
||||
export const statusIcon = localize('bdc.dashboard.status', "Status Icon");
|
||||
export const instance = localize('bdc.dashboard.instance', "Instance");
|
||||
export const state = localize('bdc.dashboard.state', "State");
|
||||
export const view = localize('bdc.dashboard.view', "View");
|
||||
export const notAvailable = localize('bdc.dashboard.notAvailable', "N/A");
|
||||
export const healthStatusDetails = localize('bdc.dashboard.healthStatusDetails', "Health Status Details");
|
||||
export const metricsAndLogs = localize('bdc.dashboard.metricsAndLogs', "Metrics and Logs");
|
||||
export const healthStatus = localize('bdc.dashboard.healthStatus', "Health Status");
|
||||
export const nodeMetrics = localize('bdc.dashboard.nodeMetrics', "Node Metrics");
|
||||
export const sqlMetrics = localize('bdc.dashboard.sqlMetrics', "SQL Metrics");
|
||||
export const logs = localize('bdc.dashboard.logs', "Logs");
|
||||
export function viewNodeMetrics(uri: string): string { return localize('bdc.dashboard.viewNodeMetrics', "View Node Metrics {0}", uri); }
|
||||
export function viewSqlMetrics(uri: string): string { return localize('bdc.dashboard.viewSqlMetrics', "View SQL Metrics {0}", uri); }
|
||||
export function viewLogs(uri: string): string { return localize('bdc.dashboard.viewLogs', "View Kibana Logs {0}", uri); }
|
||||
export function lastUpdated(date?: Date): string {
|
||||
return localize('bdc.dashboard.lastUpdated', "Last Updated : {0}",
|
||||
date ?
|
||||
`${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
: '-');
|
||||
}
|
||||
export const basic = localize('basicAuthName', "Basic");
|
||||
export const windowsAuth = localize('integratedAuthName', "Windows Authentication");
|
||||
export const addNewController = localize('addNewController', "Add New Controller");
|
||||
export const url = localize('url', "URL");
|
||||
export const username = localize('username', "Username");
|
||||
export const password = localize('password', "Password");
|
||||
export const rememberPassword = localize('rememberPassword', "Remember Password");
|
||||
export const clusterUrl = localize('clusterManagementUrl', "Cluster Management URL");
|
||||
export const authType = localize('textAuthCapital', "Authentication type");
|
||||
export const clusterConnection = localize('hdsf.dialog.connection.section', "Cluster Connection");
|
||||
export const add = localize('add', "Add");
|
||||
export const cancel = localize('cancel', "Cancel");
|
||||
export const ok = localize('ok', "OK");
|
||||
export const refresh = localize('bdc.dashboard.refresh', "Refresh");
|
||||
export const troubleshoot = localize('bdc.dashboard.troubleshoot', "Troubleshoot");
|
||||
export const bdcOverview = localize('bdc.dashboard.bdcOverview', "Big Data Cluster overview");
|
||||
export const clusterDetails = localize('bdc.dashboard.clusterDetails', "Cluster Details");
|
||||
export const clusterOverview = localize('bdc.dashboard.clusterOverview', "Cluster Overview");
|
||||
export const serviceEndpoints = localize('bdc.dashboard.serviceEndpoints', "Service Endpoints");
|
||||
export const clusterProperties = localize('bdc.dashboard.clusterProperties', "Cluster Properties");
|
||||
export const clusterState = localize('bdc.dashboard.clusterState', "Cluster State");
|
||||
export const serviceName = localize('bdc.dashboard.serviceName', "Service Name");
|
||||
export const service = localize('bdc.dashboard.service', "Service");
|
||||
export const endpoint = localize('bdc.dashboard.endpoint', "Endpoint");
|
||||
export function copiedEndpoint(endpointName: string): string { return localize('copiedEndpoint', "Endpoint '{0}' copied to clipboard", endpointName); }
|
||||
export const copy = localize('bdc.dashboard.copy', "Copy");
|
||||
export const viewDetails = localize('bdc.dashboard.viewDetails', "View Details");
|
||||
export const viewErrorDetails = localize('bdc.dashboard.viewErrorDetails', "View Error Details");
|
||||
export const connectToController = localize('connectController.dialog.title', "Connect to Controller");
|
||||
export const mountConfiguration = localize('mount.main.section', "Mount Configuration");
|
||||
export function mountTask(path: string): string { return localize('mount.task.name', "Mounting HDFS folder on path {0}", path); }
|
||||
export function refreshMountTask(path: string): string { return localize('refreshmount.task.name', "Refreshing HDFS Mount on path {0}", path); }
|
||||
export function deleteMountTask(path: string): string { return localize('deletemount.task.name', "Deleting HDFS Mount on path {0}", path); }
|
||||
export const mountTaskSubmitted = localize('mount.task.submitted', "Mount creation has started");
|
||||
export const refreshMountTaskSubmitted = localize('refreshmount.task.submitted', "Refresh mount request submitted");
|
||||
export const deleteMountTaskSubmitted = localize('deletemount.task.submitted', "Delete mount request submitted");
|
||||
export const mountCompleted = localize('mount.task.complete', "Mounting HDFS folder is complete");
|
||||
export const mountInProgress = localize('mount.task.inprogress', "Mounting is likely to complete, check back later to verify");
|
||||
export const mountFolder = localize('mount.dialog.title', "Mount HDFS Folder");
|
||||
export const hdfsPath = localize('mount.hdfsPath.title', "HDFS Path");
|
||||
export const hdfsPathInfo = localize('mount.hdfsPath.info', "Path to a new (non-existing) directory which you want to associate with the mount");
|
||||
export const remoteUri = localize('mount.remoteUri.title', "Remote URI");
|
||||
export const remoteUriInfo = localize('mount.remoteUri.info', "The URI to the remote data source. Example for ADLS: abfs://fs@saccount.dfs.core.windows.net/");
|
||||
export const credentials = localize('mount.credentials.title', "Credentials");
|
||||
export const credentialsInfo = localize('mount.credentials.info', "Mount credentials for authentication to remote data source for reads");
|
||||
export const refreshMount = localize('refreshmount.dialog.title', "Refresh Mount");
|
||||
export const deleteMount = localize('deleteMount.dialog.title', "Delete Mount");
|
||||
export const loadingClusterStateCompleted = localize('bdc.dashboard.loadingClusterStateCompleted', "Loading cluster state completed");
|
||||
export const loadingHealthStatusCompleted = localize('bdc.dashboard.loadingHealthStatusCompleted', "Loading health status completed");
|
||||
|
||||
// Errors
|
||||
export const usernameRequired = localize('err.controller.username.required', "Username is required");
|
||||
export const passwordRequired = localize('err.controller.password.required', "Password is required");
|
||||
export function endpointsError(msg: string): string { return localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", msg); }
|
||||
export const noConnectionError = localize('bdc.dashboard.noConnection', "The dashboard requires a connection. Please click retry to enter your credentials.");
|
||||
export function unexpectedError(error: Error): string { return localize('bdc.dashboard.unexpectedError', "Unexpected error occurred: {0}", error.message); }
|
||||
export const loginFailed = localize('mount.hdfs.loginerror1', "Login to controller failed");
|
||||
export function loginFailedWithError(error: ControllerError): string { return localize('mount.hdfs.loginerror2', "Login to controller failed: {0}", error.statusMessage || error.message); }
|
||||
export function badCredentialsFormatting(pair: string): string { return localize('mount.err.formatting', "Bad formatting of credentials at {0}", pair); }
|
||||
export function mountError(error: any): string { return localize('mount.task.error', "Error mounting folder: {0}", (error instanceof Error ? error.message : error)); }
|
||||
export const mountErrorUnknown = localize('mount.error.unknown', "Unknown error occurred during the mount process");
|
||||
@@ -1,10 +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 { TreeNode } from './treeNode';
|
||||
|
||||
export interface IControllerTreeChangeHandler {
|
||||
notifyNodeChanged(node?: TreeNode): void;
|
||||
}
|
||||
@@ -1,209 +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 azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
|
||||
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
|
||||
import { showErrorMessage } from '../utils';
|
||||
import { AuthType } from 'bdc';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const CredentialNamespace = 'clusterControllerCredentials';
|
||||
|
||||
interface IControllerInfoSlim {
|
||||
url: string;
|
||||
auth: AuthType;
|
||||
username: string;
|
||||
password?: string;
|
||||
rememberPassword: boolean;
|
||||
}
|
||||
|
||||
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;
|
||||
private initialized: boolean = false;
|
||||
|
||||
constructor(private memento: vscode.Memento) {
|
||||
this.root = new ControllerRootNode(this);
|
||||
}
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
if (element) {
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
if (!this.initialized) {
|
||||
try {
|
||||
await this.loadSavedControllers();
|
||||
} catch (err) {
|
||||
void vscode.window.showErrorMessage(localize('bdc.controllerTreeDataProvider.error', "Unexpected error loading saved controllers: {0}", err));
|
||||
}
|
||||
}
|
||||
|
||||
return this.root.getChildren();
|
||||
}
|
||||
|
||||
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
||||
return element.getTreeItem();
|
||||
}
|
||||
|
||||
public notifyNodeChanged(node?: TreeNode): void {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates a node in the tree with the specified connection information
|
||||
* @param url The URL for the BDC management endpoint
|
||||
* @param auth The type of auth to use
|
||||
* @param username The username (if basic auth)
|
||||
* @param password The password (if basic auth)
|
||||
* @param rememberPassword Whether to store the password in the password store when saving
|
||||
*/
|
||||
public addOrUpdateController(
|
||||
url: string,
|
||||
auth: AuthType,
|
||||
username: string,
|
||||
password: string,
|
||||
rememberPassword: boolean
|
||||
): void {
|
||||
this.removeNonControllerNodes();
|
||||
this.root.addOrUpdateControllerNode(url, auth, username, password, rememberPassword);
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
|
||||
public removeController(url: string, auth: AuthType, username: string): ControllerNode[] {
|
||||
let removed = this.root.removeControllerNode(url, auth, username);
|
||||
if (removed) {
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
private removeNonControllerNodes(): void {
|
||||
this.removeDefectiveControllerNodes(this.root.children);
|
||||
}
|
||||
|
||||
private removeDefectiveControllerNodes(nodes: TreeNode[]): void {
|
||||
if (nodes.length > 0) {
|
||||
for (let i = 0; i < nodes.length; ++i) {
|
||||
if (nodes[i] instanceof ControllerNode) {
|
||||
let controller = nodes[i] as ControllerNode;
|
||||
if (!controller.url || !controller.id) {
|
||||
nodes.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSavedControllers(): Promise<void> {
|
||||
// Optimistically set to true so we don't double-load the tree
|
||||
this.initialized = true;
|
||||
try {
|
||||
let controllers: IControllerInfoSlim[] = this.memento.get('controllers');
|
||||
let treeNodes: TreeNode[] = [];
|
||||
if (controllers) {
|
||||
for (const c of controllers) {
|
||||
let password = undefined;
|
||||
if (c.rememberPassword) {
|
||||
password = await this.getPassword(c.url, c.username);
|
||||
}
|
||||
if (!c.auth) {
|
||||
// Added before we had added authentication
|
||||
c.auth = 'basic';
|
||||
}
|
||||
treeNodes.push(new ControllerNode(
|
||||
c.url, c.auth, c.username, password, c.rememberPassword,
|
||||
undefined, this.root, this, undefined
|
||||
));
|
||||
}
|
||||
this.removeDefectiveControllerNodes(treeNodes);
|
||||
}
|
||||
|
||||
this.root.clearChildren();
|
||||
treeNodes.forEach(node => this.root.addChild(node));
|
||||
await vscode.commands.executeCommand('setContext', 'bdc.loaded', true);
|
||||
} catch (err) {
|
||||
// Reset so we can try again if the tree refreshes
|
||||
this.initialized = false;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public async saveControllers(): Promise<void> {
|
||||
const controllers = this.root.children.map((e): IControllerInfoSlim => {
|
||||
const controller = e as ControllerNode;
|
||||
return {
|
||||
url: controller.url,
|
||||
auth: controller.auth,
|
||||
username: controller.username,
|
||||
password: controller.password,
|
||||
rememberPassword: controller.rememberPassword
|
||||
};
|
||||
});
|
||||
|
||||
const controllersWithoutPassword = controllers.map((e): IControllerInfoSlim => {
|
||||
return {
|
||||
url: e.url,
|
||||
auth: e.auth,
|
||||
username: e.username,
|
||||
rememberPassword: e.rememberPassword
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await this.memento.update('controllers', controllersWithoutPassword);
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
}
|
||||
|
||||
for (const 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 | undefined> {
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,247 +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 azdata from 'azdata';
|
||||
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { IconPathHelper, BdcItemType, IconPath } from '../constants';
|
||||
import { AuthType } from 'bdc';
|
||||
|
||||
abstract class ControllerTreeNode extends TreeNode {
|
||||
|
||||
constructor(
|
||||
label: string,
|
||||
parent: ControllerTreeNode,
|
||||
private _treeChangeHandler: IControllerTreeChangeHandler,
|
||||
private _description?: string,
|
||||
private _nodeType?: string,
|
||||
private _iconPath?: IconPath
|
||||
) {
|
||||
super(label, parent);
|
||||
this._description = this._description || this.label;
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<ControllerTreeNode[]> {
|
||||
return this.children as ControllerTreeNode[];
|
||||
}
|
||||
|
||||
public override 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 = vscode.TreeItemCollapsibleState.None;
|
||||
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: IconPath) {
|
||||
this._iconPath = iconPath;
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
return this._iconPath;
|
||||
}
|
||||
|
||||
public set treeChangeHandler(treeChangeHandler: IControllerTreeChangeHandler) {
|
||||
this._treeChangeHandler = treeChangeHandler;
|
||||
}
|
||||
|
||||
public get treeChangeHandler(): IControllerTreeChangeHandler {
|
||||
return this._treeChangeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export class ControllerRootNode extends ControllerTreeNode {
|
||||
|
||||
constructor(treeChangeHandler: IControllerTreeChangeHandler) {
|
||||
super('root', undefined, treeChangeHandler, undefined, BdcItemType.controllerRoot);
|
||||
}
|
||||
|
||||
public override async getChildren(): Promise<ControllerNode[]> {
|
||||
return this.children as ControllerNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates a node in the tree with the specified connection information
|
||||
* @param url The URL for the BDC management endpoint
|
||||
* @param auth The type of auth to use
|
||||
* @param username The username (if basic auth)
|
||||
* @param password The password (if basic auth)
|
||||
* @param rememberPassword Whether to store the password in the password store when saving
|
||||
*/
|
||||
public addOrUpdateControllerNode(
|
||||
url: string,
|
||||
auth: AuthType,
|
||||
username: string,
|
||||
password: string,
|
||||
rememberPassword: boolean
|
||||
): void {
|
||||
let controllerNode = this.getExistingControllerNode(url, auth, username);
|
||||
if (controllerNode) {
|
||||
controllerNode.password = password;
|
||||
controllerNode.rememberPassword = rememberPassword;
|
||||
controllerNode.clearChildren();
|
||||
} else {
|
||||
controllerNode = new ControllerNode(url, auth, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
||||
this.addChild(controllerNode);
|
||||
}
|
||||
}
|
||||
|
||||
public removeControllerNode(url: string, auth: AuthType, username: string): ControllerNode[] | undefined {
|
||||
if (!url || (auth === 'basic' && !username)) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
let index = nodes.findIndex(e => isControllerMatch(e, url, auth, username));
|
||||
let deleted: ControllerNode[] | undefined;
|
||||
if (index >= 0) {
|
||||
deleted = nodes.splice(index, 1);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
private getExistingControllerNode(url: string, auth: AuthType, username: string): ControllerNode | undefined {
|
||||
if (!url || !username) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
return nodes.find(e => isControllerMatch(e, url, auth, username));
|
||||
}
|
||||
}
|
||||
|
||||
export class ControllerNode extends ControllerTreeNode {
|
||||
|
||||
constructor(
|
||||
private _url: string,
|
||||
private _auth: AuthType,
|
||||
private _username: string,
|
||||
private _password: string,
|
||||
private _rememberPassword: boolean,
|
||||
label: string,
|
||||
parent: ControllerTreeNode,
|
||||
treeChangeHandler: IControllerTreeChangeHandler,
|
||||
description?: string,
|
||||
) {
|
||||
super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPathHelper.controllerNode);
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public override async getChildren(): Promise<ControllerTreeNode[] | undefined> {
|
||||
if (this.children && this.children.length > 0) {
|
||||
this.clearChildren();
|
||||
}
|
||||
|
||||
if (!this._password) {
|
||||
vscode.commands.executeCommand('bigDataClusters.command.connectController', this);
|
||||
return this.children as ControllerTreeNode[];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static toIpAndPort(url: string): string | undefined {
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '');
|
||||
}
|
||||
|
||||
public get url(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
|
||||
public get auth(): AuthType {
|
||||
return this._auth;
|
||||
}
|
||||
|
||||
|
||||
public get username(): string {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
public get password(): string {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
public set password(pw: string) {
|
||||
this._password = pw;
|
||||
}
|
||||
|
||||
public override set label(label: string) {
|
||||
super.label = label || this.generateLabel();
|
||||
}
|
||||
|
||||
public get rememberPassword() {
|
||||
return this._rememberPassword;
|
||||
}
|
||||
|
||||
public set rememberPassword(rememberPassword: boolean) {
|
||||
this._rememberPassword = rememberPassword;
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = `controller: ${ControllerNode.toIpAndPort(this._url)}`;
|
||||
if (this._auth === 'basic') {
|
||||
label += ` (${this._username})`;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public override get label(): string {
|
||||
return super.label;
|
||||
}
|
||||
|
||||
public override set description(description: string) {
|
||||
super.description = description || super.label;
|
||||
}
|
||||
|
||||
public override get description(): string {
|
||||
return super.description;
|
||||
}
|
||||
}
|
||||
|
||||
function isControllerMatch(node: ControllerNode, url: string, auth: string, username: string): unknown {
|
||||
return node.url === url && node.auth === auth && node.username === username;
|
||||
}
|
||||
@@ -1,193 +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 * 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 getChildren(): Promise<TreeNode[]>;
|
||||
public abstract getTreeItem(): vscode.TreeItem;
|
||||
public abstract getNodeInfo(): azdata.NodeInfo;
|
||||
}
|
||||
@@ -1,289 +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 azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as constants from './constants';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export enum Endpoint {
|
||||
gateway = 'gateway',
|
||||
sparkHistory = 'spark-history',
|
||||
yarnUi = 'yarn-ui',
|
||||
appProxy = 'app-proxy',
|
||||
mgmtproxy = 'mgmtproxy',
|
||||
managementProxy = 'management-proxy',
|
||||
logsui = 'logsui',
|
||||
metricsui = 'metricsui',
|
||||
controller = 'controller',
|
||||
sqlServerMaster = 'sql-server-master',
|
||||
webhdfs = 'webhdfs',
|
||||
livy = 'livy'
|
||||
}
|
||||
|
||||
export enum Service {
|
||||
sql = 'sql',
|
||||
hdfs = 'hdfs',
|
||||
spark = 'spark',
|
||||
control = 'control',
|
||||
gateway = 'gateway',
|
||||
app = 'app'
|
||||
}
|
||||
|
||||
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, prefixText?: string): void {
|
||||
if (error) {
|
||||
let text: string = prefixText || '';
|
||||
if (typeof error === 'string') {
|
||||
text += error as string;
|
||||
} else if (typeof error === 'object' && error !== null) {
|
||||
text += error.message;
|
||||
if (error.code && error.code > 0) {
|
||||
text += ` (${error.code})`;
|
||||
}
|
||||
} else {
|
||||
text += `${error}`;
|
||||
}
|
||||
vscode.window.showErrorMessage(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappings of the different expected state values to their localized friendly names.
|
||||
* These are defined in aris/projects/controller/src/Microsoft.SqlServer.Controller/StateMachines
|
||||
*/
|
||||
const stateToDisplayTextMap: { [key: string]: string } = {
|
||||
// K8sScaledSetStateMachine
|
||||
'creating': localize('state.creating', "Creating"),
|
||||
'waiting': localize('state.waiting', "Waiting"),
|
||||
'ready': localize('state.ready', "Ready"),
|
||||
'deleting': localize('state.deleting', "Deleting"),
|
||||
'deleted': localize('state.deleted', "Deleted"),
|
||||
'applyingupgrade': localize('state.applyingUpgrade', "Applying Upgrade"),
|
||||
'upgrading': localize('state.upgrading', "Upgrading"),
|
||||
'applyingmanagedupgrade': localize('state.applyingmanagedupgrade', "Applying Managed Upgrade"),
|
||||
'managedupgrading': localize('state.managedUpgrading', "Managed Upgrading"),
|
||||
'rollback': localize('state.rollback', "Rollback"),
|
||||
'rollbackinprogress': localize('state.rollbackInProgress', "Rollback In Progress"),
|
||||
'rollbackcomplete': localize('state.rollbackComplete', "Rollback Complete"),
|
||||
'error': localize('state.error', "Error"),
|
||||
|
||||
// BigDataClusterStateMachine
|
||||
'creatingsecrets': localize('state.creatingSecrets', "Creating Secrets"),
|
||||
'waitingforsecrets': localize('state.waitingForSecrets', "Waiting For Secrets"),
|
||||
'creatinggroups': localize('state.creatingGroups', "Creating Groups"),
|
||||
'waitingforgroups': localize('state.waitingForGroups', "Waiting For Groups"),
|
||||
'creatingresources': localize('state.creatingResources', "Creating Resources"),
|
||||
'waitingforresources': localize('state.waitingForResources', "Waiting For Resources"),
|
||||
'creatingkerberosdelegationsetup': localize('state.creatingKerberosDelegationSetup', "Creating Kerberos Delegation Setup"),
|
||||
'waitingforkerberosdelegationsetup': localize('state.waitingForKerberosDelegationSetup', "Waiting For Kerberos Delegation Setup"),
|
||||
'waitingfordeletion': localize('state.waitingForDeletion', "Waiting For Deletion"),
|
||||
'waitingforupgrade': localize('state.waitingForUpgrade', "Waiting For Upgrade"),
|
||||
'upgradePaused': localize('state.upgradePaused', "Upgrade Paused"),
|
||||
|
||||
// Other
|
||||
'running': localize('state.running', "Running"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the localized text to display for a corresponding state
|
||||
* @param state The state to get the display text for
|
||||
*/
|
||||
export function getStateDisplayText(state?: string): string {
|
||||
state = state || '';
|
||||
return stateToDisplayTextMap[state.toLowerCase()] || state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localized text to display for a corresponding endpoint
|
||||
* @param endpointName The endpoint name to get the display text for
|
||||
* @param description The backup description to use if we don't have our own
|
||||
*/
|
||||
export function getEndpointDisplayText(endpointName?: string, description?: string): string {
|
||||
endpointName = endpointName || '';
|
||||
switch (endpointName.toLowerCase()) {
|
||||
case Endpoint.appProxy:
|
||||
return localize('endpoint.appproxy', "Application Proxy");
|
||||
case Endpoint.controller:
|
||||
return localize('endpoint.controller', "Cluster Management Service");
|
||||
case Endpoint.gateway:
|
||||
return localize('endpoint.gateway', "Gateway to access HDFS files, Spark");
|
||||
case Endpoint.managementProxy:
|
||||
return localize('endpoint.managementproxy', "Management Proxy");
|
||||
case Endpoint.mgmtproxy:
|
||||
return localize('endpoint.mgmtproxy', "Management Proxy");
|
||||
case Endpoint.sqlServerMaster:
|
||||
return localize('endpoint.sqlServerEndpoint', "SQL Server Master Instance Front-End");
|
||||
case Endpoint.metricsui:
|
||||
return localize('endpoint.grafana', "Metrics Dashboard");
|
||||
case Endpoint.logsui:
|
||||
return localize('endpoint.kibana', "Log Search Dashboard");
|
||||
case Endpoint.yarnUi:
|
||||
return localize('endpoint.yarnHistory', "Spark Diagnostics and Monitoring Dashboard");
|
||||
case Endpoint.sparkHistory:
|
||||
return localize('endpoint.sparkHistory', "Spark Jobs Management and Monitoring Dashboard");
|
||||
case Endpoint.webhdfs:
|
||||
return localize('endpoint.webhdfs', "HDFS File System Proxy");
|
||||
case Endpoint.livy:
|
||||
return localize('endpoint.livy', "Proxy for running Spark statements, jobs, applications");
|
||||
default:
|
||||
// Default is to use the description if one was given, otherwise worst case just fall back to using the
|
||||
// original endpoint name
|
||||
return description && description.length > 0 ? description : endpointName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localized text to display for a corresponding service
|
||||
* @param serviceName The service name to get the display text for
|
||||
*/
|
||||
export function getServiceNameDisplayText(serviceName?: string): string {
|
||||
serviceName = serviceName || '';
|
||||
switch (serviceName.toLowerCase()) {
|
||||
case Service.sql:
|
||||
return localize('service.sql', "SQL Server");
|
||||
case Service.hdfs:
|
||||
return localize('service.hdfs', "HDFS");
|
||||
case Service.spark:
|
||||
return localize('service.spark', "Spark");
|
||||
case Service.control:
|
||||
return localize('service.control', "Control");
|
||||
case Service.gateway:
|
||||
return localize('service.gateway', "Gateway");
|
||||
case Service.app:
|
||||
return localize('service.app', "App");
|
||||
default:
|
||||
return serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localized text to display for a corresponding health status
|
||||
* @param healthStatus The health status to get the display text for
|
||||
*/
|
||||
export function getHealthStatusDisplayText(healthStatus?: string) {
|
||||
healthStatus = healthStatus || '';
|
||||
switch (healthStatus.toLowerCase()) {
|
||||
case 'healthy':
|
||||
return localize('bdc.healthy', "Healthy");
|
||||
case 'unhealthy':
|
||||
return localize('bdc.unhealthy', "Unhealthy");
|
||||
default:
|
||||
return healthStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status icon for the corresponding health status
|
||||
* @param healthStatus The status to check
|
||||
*/
|
||||
export function getHealthStatusIcon(healthStatus?: string): string {
|
||||
healthStatus = healthStatus || '';
|
||||
switch (healthStatus.toLowerCase()) {
|
||||
case 'healthy':
|
||||
return '✔️';
|
||||
default:
|
||||
// Consider all non-healthy status' as errors
|
||||
return '⚠️';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status dot icon which will be a • for all non-healthy states
|
||||
* @param healthStatus The status to check
|
||||
*/
|
||||
export function getHealthStatusDotIcon(healthStatus?: string): constants.IconPath {
|
||||
healthStatus = healthStatus || '';
|
||||
switch (healthStatus.toLowerCase()) {
|
||||
case 'healthy':
|
||||
return constants.IconPathHelper.status_circle_blank;
|
||||
default:
|
||||
// Display status dot for all non-healthy status'
|
||||
return constants.IconPathHelper.status_circle_red;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface RawEndpoint {
|
||||
serviceName: string;
|
||||
description?: string;
|
||||
endpoint?: string;
|
||||
protocol?: string;
|
||||
ipAddress?: string;
|
||||
port?: number;
|
||||
}
|
||||
|
||||
interface IEndpoint {
|
||||
serviceName: string;
|
||||
description: string;
|
||||
endpoint: string;
|
||||
protocol: string;
|
||||
}
|
||||
|
||||
function getClusterEndpoints(serverInfo: azdata.ServerInfo): IEndpoint[] {
|
||||
let endpoints: RawEndpoint[] = serverInfo.options[constants.clusterEndpointsProperty];
|
||||
if (!endpoints || endpoints.length === 0) { return []; }
|
||||
|
||||
return endpoints.map(e => {
|
||||
// If endpoint is missing, we're on CTP bits. All endpoints from the CTP serverInfo should be treated as HTTPS
|
||||
let endpoint = e.endpoint ? e.endpoint : `https://${e.ipAddress}:${e.port}`;
|
||||
let updatedEndpoint: IEndpoint = {
|
||||
serviceName: e.serviceName,
|
||||
description: e.description,
|
||||
endpoint: endpoint,
|
||||
protocol: e.protocol
|
||||
};
|
||||
return updatedEndpoint;
|
||||
});
|
||||
}
|
||||
|
||||
export function getControllerEndpoint(serverInfo: azdata.ServerInfo): string | undefined {
|
||||
let endpoints = getClusterEndpoints(serverInfo);
|
||||
if (endpoints) {
|
||||
let index = endpoints.findIndex(ep => ep.serviceName.toLowerCase() === constants.controllerEndpointName.toLowerCase());
|
||||
if (index < 0) { return undefined; }
|
||||
return endpoints[index].endpoint;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getBdcStatusErrorMessage(error: Error): string {
|
||||
return localize('endpointsError', "Unexpected error retrieving BDC Endpoints: {0}", error.message);
|
||||
}
|
||||
|
||||
const bdcConfigSectionName = 'bigDataCluster';
|
||||
const ignoreSslConfigName = 'ignoreSslVerification';
|
||||
|
||||
/**
|
||||
* Retrieves the current setting for whether to ignore SSL verification errors
|
||||
*/
|
||||
export function getIgnoreSslVerificationConfigSetting(): boolean {
|
||||
try {
|
||||
const config = vscode.workspace.getConfiguration(bdcConfigSectionName);
|
||||
return config.get<boolean>(ignoreSslConfigName, true);
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error retrieving ${bdcConfigSectionName}.${ignoreSslConfigName} setting : ${error}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const ManageControllerCommand = 'bigDataClusters.command.manageController';
|
||||
export const CreateControllerCommand = 'bigDataClusters.command.createController';
|
||||
export const ConnectControllerCommand = 'bigDataClusters.command.connectController';
|
||||
export const RemoveControllerCommand = 'bigDataClusters.command.removeController';
|
||||
export const RefreshControllerCommand = 'bigDataClusters.command.refreshController';
|
||||
export const MountHdfsCommand = 'bigDataClusters.command.mount';
|
||||
export const RefreshMountCommand = 'bigDataClusters.command.refreshmount';
|
||||
export const DeleteMountCommand = 'bigDataClusters.command.deletemount';
|
||||
@@ -1,25 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Deferred promise
|
||||
*/
|
||||
export class Deferred<T> {
|
||||
promise: Promise<T>;
|
||||
resolve: (value?: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult> {
|
||||
return this.promise.then(onfulfilled, onrejected);
|
||||
}
|
||||
}
|
||||
@@ -1,225 +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 azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ControllerTreeDataProvider } from './bigDataCluster/tree/controllerTreeDataProvider';
|
||||
import { IconPathHelper } from './bigDataCluster/constants';
|
||||
import { TreeNode } from './bigDataCluster/tree/treeNode';
|
||||
import { AddControllerDialogModel, AddControllerDialog } from './bigDataCluster/dialog/addControllerDialog';
|
||||
import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
|
||||
import { BdcDashboard } from './bigDataCluster/dialog/bdcDashboard';
|
||||
import { BdcDashboardModel, BdcDashboardOptions } from './bigDataCluster/dialog/bdcDashboardModel';
|
||||
import { MountHdfsDialogModel as MountHdfsModel, MountHdfsProperties, MountHdfsDialog, DeleteMountDialog, DeleteMountModel, RefreshMountDialog, RefreshMountModel } from './bigDataCluster/dialog/mountHdfsDialog';
|
||||
import { getControllerEndpoint } from './bigDataCluster/utils';
|
||||
import * as commands from './commands';
|
||||
import { HdfsDialogCancelledError } from './bigDataCluster/dialog/hdfsDialogBase';
|
||||
import { IExtension, AuthType, IClusterController } from 'bdc';
|
||||
import { ClusterController } from './bigDataCluster/controller/clusterControllerApi';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const endpointNotFoundError = localize('mount.error.endpointNotFound', "Controller endpoint information was not found");
|
||||
|
||||
let throttleTimers: { [key: string]: any } = {};
|
||||
|
||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtension> {
|
||||
IconPathHelper.setExtensionContext(extensionContext);
|
||||
await vscode.commands.executeCommand('setContext', 'bdc.loaded', false);
|
||||
const treeDataProvider = new ControllerTreeDataProvider(extensionContext.globalState);
|
||||
let controllers: any[] = extensionContext.globalState.get('controllers', []);
|
||||
if (controllers.length > 0) {
|
||||
const deprecationNoticeKey = 'bdc.deprecationNoticeShown';
|
||||
const deprecationNoticeShown = extensionContext.globalState.get(deprecationNoticeKey, false);
|
||||
if (!deprecationNoticeShown) {
|
||||
void vscode.window.showWarningMessage(localize('bdc.deprecationWarning', 'The Big Data Cluster add-on is being retired and Azure Data Studio functionality for it will be removed in an upcoming release. Read more about this and support going forward [here](https://go.microsoft.com/fwlink/?linkid=2207340).'));
|
||||
void extensionContext.globalState.update(deprecationNoticeKey, true);
|
||||
}
|
||||
}
|
||||
vscode.window.registerTreeDataProvider('sqlBigDataCluster', treeDataProvider);
|
||||
registerCommands(extensionContext, treeDataProvider);
|
||||
return {
|
||||
getClusterController(url: string, authType: AuthType, username?: string, password?: string): IClusterController {
|
||||
return new ClusterController(url, authType, username, password);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
}
|
||||
|
||||
function registerCommands(context: vscode.ExtensionContext, treeDataProvider: ControllerTreeDataProvider): void {
|
||||
vscode.commands.registerCommand(commands.ConnectControllerCommand, (node?: TreeNode) => {
|
||||
runThrottledAction(commands.ConnectControllerCommand, () => addBdcController(treeDataProvider, node));
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand(commands.CreateControllerCommand, () => {
|
||||
runThrottledAction(commands.CreateControllerCommand, () => vscode.commands.executeCommand('azdata.resource.deploy', 'sql-bdc', ['sql-bdc']));
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand(commands.RemoveControllerCommand, async (node: TreeNode) => {
|
||||
await deleteBdcController(treeDataProvider, node);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand(commands.RefreshControllerCommand, (node: TreeNode) => {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
treeDataProvider.notifyNodeChanged(node);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand(commands.ManageControllerCommand, async (info: ControllerNode | BdcDashboardOptions, addOrUpdateController: boolean = false) => {
|
||||
const title: string = `${localize('bdc.dashboard.title', "Big Data Cluster Dashboard -")} ${ControllerNode.toIpAndPort(info.url)}`;
|
||||
if (addOrUpdateController) {
|
||||
// The info may be wrong, but if it is then we'll prompt to reconnect when the dashboard is opened
|
||||
// and update with the correct info then
|
||||
treeDataProvider.addOrUpdateController(
|
||||
info.url,
|
||||
info.auth,
|
||||
info.username,
|
||||
info.password,
|
||||
info.rememberPassword);
|
||||
await treeDataProvider.saveControllers();
|
||||
}
|
||||
const dashboard: BdcDashboard = new BdcDashboard(title, new BdcDashboardModel(info, treeDataProvider));
|
||||
await dashboard.showDashboard();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand(commands.MountHdfsCommand, e => mountHdfs(e).catch(error => {
|
||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||
}));
|
||||
vscode.commands.registerCommand(commands.RefreshMountCommand, e => refreshMount(e).catch(error => {
|
||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||
}));
|
||||
vscode.commands.registerCommand(commands.DeleteMountCommand, e => deleteMount(e).catch(error => {
|
||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||
}));
|
||||
}
|
||||
|
||||
async function mountHdfs(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||
const mountProps = await getMountProps(explorerContext);
|
||||
if (mountProps) {
|
||||
const dialog = new MountHdfsDialog(new MountHdfsModel(mountProps));
|
||||
try {
|
||||
await dialog.showDialog();
|
||||
} catch (error) {
|
||||
if (!(error instanceof HdfsDialogCancelledError)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||
const mountProps = await getMountProps(explorerContext);
|
||||
if (mountProps) {
|
||||
const dialog = new RefreshMountDialog(new RefreshMountModel(mountProps));
|
||||
await dialog.showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMount(explorerContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||
const mountProps = await getMountProps(explorerContext);
|
||||
if (mountProps) {
|
||||
const dialog = new DeleteMountDialog(new DeleteMountModel(mountProps));
|
||||
await dialog.showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
async function getMountProps(explorerContext?: azdata.ObjectExplorerContext): Promise<MountHdfsProperties | undefined> {
|
||||
let endpoint = await lookupController(explorerContext);
|
||||
if (!endpoint) {
|
||||
vscode.window.showErrorMessage(endpointNotFoundError);
|
||||
return undefined;
|
||||
}
|
||||
let profile = explorerContext.connectionProfile;
|
||||
let mountProps: MountHdfsProperties = {
|
||||
url: endpoint,
|
||||
auth: profile.authenticationType === azdata.connection.AuthenticationType.SqlLogin ? 'basic' : 'integrated',
|
||||
username: profile.userName,
|
||||
password: profile.password,
|
||||
hdfsPath: getHdsfPath(explorerContext.nodeInfo.nodePath)
|
||||
};
|
||||
return mountProps;
|
||||
}
|
||||
|
||||
function getHdsfPath(nodePath: string): string {
|
||||
const hdfsNodeLabel = '/HDFS';
|
||||
let index = nodePath.indexOf(hdfsNodeLabel);
|
||||
if (index >= 0) {
|
||||
let subPath = nodePath.substring(index + hdfsNodeLabel.length);
|
||||
return subPath.length > 0 ? subPath : '/';
|
||||
}
|
||||
// Use the root
|
||||
return '/';
|
||||
}
|
||||
|
||||
async function lookupController(explorerContext?: azdata.ObjectExplorerContext): Promise<string | undefined> {
|
||||
if (!explorerContext) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let serverInfo = await azdata.connection.getServerInfo(explorerContext.connectionProfile.id);
|
||||
if (!serverInfo || !serverInfo.options) {
|
||||
vscode.window.showErrorMessage(endpointNotFoundError);
|
||||
return undefined;
|
||||
}
|
||||
return getControllerEndpoint(serverInfo);
|
||||
}
|
||||
|
||||
function addBdcController(treeDataProvider: ControllerTreeDataProvider, node?: TreeNode): void {
|
||||
let model = new AddControllerDialogModel(treeDataProvider, node as ControllerNode);
|
||||
let dialog = new AddControllerDialog(model);
|
||||
dialog.showDialog();
|
||||
}
|
||||
|
||||
async function deleteBdcController(treeDataProvider: ControllerTreeDataProvider, node: TreeNode): Promise<boolean | undefined> {
|
||||
if (!node && !(node instanceof ControllerNode)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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('textConfirmRemoveController', "Are you sure you want to remove \'{0}\'?", controllerNode.label)
|
||||
};
|
||||
|
||||
let result = await vscode.window.showQuickPick(Object.keys(choices), options);
|
||||
let remove: boolean = !!(result && choices[result]);
|
||||
if (remove) {
|
||||
await removeControllerInternal(treeDataProvider, controllerNode);
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
|
||||
async function removeControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): Promise<void> {
|
||||
const removed = treeDataProvider.removeController(controllerNode.url, controllerNode.auth, controllerNode.username);
|
||||
if (removed) {
|
||||
await treeDataProvider.saveControllers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttles actions to avoid bug where on clicking in tree, action gets called twice
|
||||
* instead of once. Any right-click action is safe, just the default on-click action in a tree
|
||||
*/
|
||||
function runThrottledAction(id: string, action: () => void) {
|
||||
let timer = throttleTimers[id];
|
||||
if (!timer) {
|
||||
throttleTimers[id] = timer = setTimeout(() => {
|
||||
action();
|
||||
clearTimeout(timer);
|
||||
throttleTimers[id] = undefined;
|
||||
}, 150);
|
||||
}
|
||||
// else ignore this as we got an identical action in the last 150ms
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference path='../../../../src/vscode-dts/vscode.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6",
|
||||
"es2015.promise"
|
||||
],
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"strict": false,
|
||||
"noUnusedParameters": false,
|
||||
"strictNullChecks": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@microsoft/ads-kerberos@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/ads-kerberos/-/ads-kerberos-1.1.3.tgz#a10c6d2d0751c0b67548d51a4bc45a7c33d00088"
|
||||
integrity sha512-jlji4IUfkA/4idYBN9tyouCwurfyGZrDzMGlaUmx9/vtwRvdoEFCg959WfhShcfMPVLku/bztmLeZtN1ifccnQ==
|
||||
dependencies:
|
||||
nan "^2.14.0"
|
||||
|
||||
"@types/caseless@*":
|
||||
version "0.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
|
||||
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
||||
|
||||
"@types/node@*":
|
||||
version "12.12.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.6.tgz#a47240c10d86a9a57bb0c633f0b2e0aea9ce9253"
|
||||
integrity sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA==
|
||||
|
||||
"@types/request@^2.48.3":
|
||||
version "2.48.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.3.tgz#970b8ed2317568c390361d29c555a95e74bd6135"
|
||||
integrity sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==
|
||||
dependencies:
|
||||
"@types/caseless" "*"
|
||||
"@types/node" "*"
|
||||
"@types/tough-cookie" "*"
|
||||
form-data "^2.5.0"
|
||||
|
||||
"@types/tough-cookie@*":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d"
|
||||
integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==
|
||||
|
||||
ajv@^6.5.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||
dependencies:
|
||||
safer-buffer "~2.1.0"
|
||||
|
||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
|
||||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@^2.5.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
|
||||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
getpass@^0.1.1:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
|
||||
dependencies:
|
||||
ajv "^6.5.5"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema@0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
|
||||
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
|
||||
integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
|
||||
dependencies:
|
||||
assert-plus "1.0.0"
|
||||
extsprintf "1.3.0"
|
||||
json-schema "0.4.0"
|
||||
verror "1.10.0"
|
||||
|
||||
mime-db@1.40.0:
|
||||
version "1.40.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||
version "2.1.24"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
|
||||
dependencies:
|
||||
mime-db "1.40.0"
|
||||
|
||||
nan@^2.14.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.1.32"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db"
|
||||
integrity sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
|
||||
|
||||
request@^2.88.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.6"
|
||||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.19"
|
||||
oauth-sign "~0.9.0"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
bcrypt-pbkdf "^1.0.0"
|
||||
dashdash "^1.12.0"
|
||||
ecc-jsbn "~0.1.1"
|
||||
getpass "^0.1.1"
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vscode-nls@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
|
||||
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
|
||||
@@ -90,9 +90,6 @@ const AKV_URL = 'https://sqltoolssecretstore.vault.azure.net/';
|
||||
const SECRET_AZURE_SERVER = 'ads-integration-test-azure-server';
|
||||
const SECRET_AZURE_SERVER_USERNAME = 'ads-integration-test-azure-server-username';
|
||||
const SECRET_AZURE_SERVER_PASSWORD = 'ads-integration-test-azure-server-password';
|
||||
const SECRET_BDC_SERVER = 'ads-integration-test-bdc-server';
|
||||
const SECRET_BDC_SERVER_USERNAME = 'ads-integration-test-bdc-server-username';
|
||||
const SECRET_BDC_SERVER_PASSWORD = 'ads-integration-test-bdc-server-password';
|
||||
const SECRET_STANDALONE_SERVER = 'ads-integration-test-standalone-server';
|
||||
const SECRET_STANDALONE_SERVER_USERNAME = 'ads-integration-test-standalone-server-username';
|
||||
const SECRET_STANDALONE_SERVER_PASSWORD = 'ads-integration-test-standalone-server-password';
|
||||
@@ -104,9 +101,6 @@ const SECRET_STANDALONE_SERVER_PASSWORD_2019 = 'ads-integration-test-standalone-
|
||||
const ENVAR_AZURE_SERVER = 'AZURE_SQL';
|
||||
const ENVAR_AZURE_SERVER_USERNAME = 'AZURE_SQL_USERNAME';
|
||||
const ENVAR_AZURE_SERVER_PASSWORD = 'AZURE_SQL_PWD';
|
||||
const ENVAR_BDC_SERVER = 'BDC_BACKEND_HOSTNAME';
|
||||
const ENVAR_BDC_SERVER_USERNAME = 'BDC_BACKEND_USERNAME';
|
||||
const ENVAR_BDC_SERVER_PASSWORD = 'BDC_BACKEND_PWD';
|
||||
const ENVAR_STANDALONE_SERVER = 'STANDALONE_SQL';
|
||||
const ENVAR_STANDALONE_SERVER_USERNAME = 'STANDALONE_SQL_USERNAME';
|
||||
const ENVAR_STANDALONE_SERVER_PASSWORD = 'STANDALONE_SQL_PWD';
|
||||
@@ -115,16 +109,12 @@ const ENVAR_STANDALONE_SERVER_USERNAME_2019 = 'STANDALONE_SQL_USERNAME_2019';
|
||||
const ENVAR_STANDALONE_SERVER_PASSWORD_2019 = 'STANDALONE_SQL_PWD_2019';
|
||||
const ENVAR_PYTHON_INSTALL_PATH = 'PYTHON_TEST_PATH';
|
||||
const ENVAR_RUN_PYTHON3_TEST = 'RUN_PYTHON3_TEST';
|
||||
const ENVAR_RUN_PYSPARK_TEST = 'RUN_PYSPARK_TEST';
|
||||
|
||||
// Mapping between AKV secret and the environment variable names
|
||||
const SecretEnVarMapping = [];
|
||||
SecretEnVarMapping.push([SECRET_AZURE_SERVER, ENVAR_AZURE_SERVER]);
|
||||
SecretEnVarMapping.push([SECRET_AZURE_SERVER_PASSWORD, ENVAR_AZURE_SERVER_PASSWORD]);
|
||||
SecretEnVarMapping.push([SECRET_AZURE_SERVER_USERNAME, ENVAR_AZURE_SERVER_USERNAME]);
|
||||
SecretEnVarMapping.push([SECRET_BDC_SERVER, ENVAR_BDC_SERVER]);
|
||||
SecretEnVarMapping.push([SECRET_BDC_SERVER_PASSWORD, ENVAR_BDC_SERVER_PASSWORD]);
|
||||
SecretEnVarMapping.push([SECRET_BDC_SERVER_USERNAME, ENVAR_BDC_SERVER_USERNAME]);
|
||||
SecretEnVarMapping.push([SECRET_STANDALONE_SERVER, ENVAR_STANDALONE_SERVER]);
|
||||
SecretEnVarMapping.push([SECRET_STANDALONE_SERVER_PASSWORD, ENVAR_STANDALONE_SERVER_PASSWORD]);
|
||||
SecretEnVarMapping.push([SECRET_STANDALONE_SERVER_USERNAME, ENVAR_STANDALONE_SERVER_USERNAME]);
|
||||
@@ -135,7 +125,6 @@ SecretEnVarMapping.push([SECRET_STANDALONE_SERVER_PASSWORD_2019, ENVAR_STANDALON
|
||||
// Set the values that are not stored in AKV here
|
||||
process.env[ENVAR_PYTHON_INSTALL_PATH] = NOTEBOOK_PYTHON_INSTALL_PATH;
|
||||
process.env[ENVAR_RUN_PYTHON3_TEST] = '1';
|
||||
process.env[ENVAR_RUN_PYSPARK_TEST] = '0';
|
||||
|
||||
const credential = new DefaultAzureCredential();
|
||||
const client = new SecretClient(AKV_URL, credential);
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as assert from 'assert';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { sqlNotebookContent, writeNotebookToFile, sqlKernelMetadata, getTempFilePath, pySparkNotebookContent, pySparkKernelMetadata, pythonKernelMetadata, sqlNotebookMultipleCellsContent, notebookContentForCellLanguageTest, sqlKernelSpec, pythonKernelSpec, pySparkKernelSpec, CellTypes } from './notebook.util';
|
||||
import { sqlNotebookContent, writeNotebookToFile, sqlKernelMetadata, getTempFilePath, pythonKernelMetadata, sqlNotebookMultipleCellsContent, notebookContentForCellLanguageTest, sqlKernelSpec, pythonKernelSpec, CellTypes, pythonNotebookContent, powershellKernelSpec } from './notebook.util';
|
||||
import { getConfigValue, EnvironmentVariable_PYTHON_PATH, TestServerProfile, getStandaloneServer } from './testConfig';
|
||||
import { connectToServer, sleep, testServerProfileToIConnectionProfile } from './utils';
|
||||
import * as fs from 'fs';
|
||||
@@ -149,7 +149,7 @@ suite('Notebook integration test suite', function () {
|
||||
|
||||
if (process.env['RUN_PYTHON3_TEST'] === '1') {
|
||||
test('Python3 notebook test', async function () {
|
||||
let notebook = await openNotebook(pySparkNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
let notebook = await openNotebook(pythonNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
await runCell(notebook);
|
||||
let cellOutputs = notebook.document.cells[0].contents.outputs;
|
||||
console.log('Got cell outputs ---');
|
||||
@@ -161,7 +161,7 @@ suite('Notebook integration test suite', function () {
|
||||
});
|
||||
|
||||
test('Clear all outputs - Python3 notebook ', async function () {
|
||||
let notebook = await openNotebook(pySparkNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
let notebook = await openNotebook(pythonNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
await runCell(notebook);
|
||||
await verifyClearAllOutputs(notebook);
|
||||
});
|
||||
@@ -197,7 +197,7 @@ suite('Notebook integration test suite', function () {
|
||||
});
|
||||
|
||||
test('Change kernel different provider Python to SQL to Python', async function () {
|
||||
let notebook = await openNotebook(pySparkNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
let notebook = await openNotebook(pythonNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
await runCell(notebook);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(notebook.document.kernelSpec.name === 'python3', `Expected first kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
@@ -211,48 +211,23 @@ suite('Notebook integration test suite', function () {
|
||||
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected third kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
});
|
||||
|
||||
test('Change kernel same provider Python to PySpark to Python', async function () {
|
||||
let notebook = await openNotebook(pySparkNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
await runCell(notebook);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(notebook.document.kernelSpec.name === 'python3', `Expected first kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
|
||||
let kernelChanged = await notebook.changeKernel(pySparkKernelSpec);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(kernelChanged && notebook.document.kernelSpec.name === 'pysparkkernel', `Expected second kernel name: pysparkkernel, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
|
||||
kernelChanged = await notebook.changeKernel(pythonKernelSpec);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected third kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env['RUN_PYSPARK_TEST'] === '1') {
|
||||
test('PySpark notebook test', async function () {
|
||||
let notebook = await openNotebook(pySparkNotebookContent, pySparkKernelMetadata, this.test.title);
|
||||
test('Change kernel same provider Python to Powershell to Python', async function () {
|
||||
let notebook = await openNotebook(pythonNotebookContent, pythonKernelMetadata, this.test.title);
|
||||
await runCell(notebook);
|
||||
let cellOutputs = notebook.document.cells[0].contents.outputs;
|
||||
let sparkResult = (<azdata.nb.IStreamResult>cellOutputs[3]).text;
|
||||
assert(sparkResult === '2', `Expected spark result: 2, Actual: ${sparkResult}`);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(notebook.document.kernelSpec.name === 'python3', `Expected first kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
|
||||
let kernelChanged = await notebook.changeKernel(powershellKernelSpec);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(kernelChanged && notebook.document.kernelSpec.name === 'powershell', `Expected second kernel name: powershell, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
|
||||
kernelChanged = await notebook.changeKernel(pythonKernelSpec);
|
||||
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected third kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
/* After https://github.com/microsoft/azuredatastudio/issues/5598 is fixed, enable these tests.
|
||||
test('scala language test', async function () {
|
||||
let language = 'scala';
|
||||
await cellLanguageTest(notebookContentForCellLanguageTest, this.test.title, language, {
|
||||
'kernelspec': {
|
||||
'name': '',
|
||||
'display_name': ''
|
||||
},
|
||||
'language_info': {
|
||||
name: language,
|
||||
version: '',
|
||||
mimetype: ''
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('empty language test', async function () {
|
||||
let language = '';
|
||||
await cellLanguageTest(notebookContentForCellLanguageTest, this.test.title, language, {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class CellTypes {
|
||||
public static readonly Raw = 'raw';
|
||||
}
|
||||
|
||||
export const pySparkNotebookContent: azdata.nb.INotebookContents = {
|
||||
export const pythonNotebookContent: azdata.nb.INotebookContents = {
|
||||
cells: [{
|
||||
cell_type: CellTypes.Code,
|
||||
source: '1+1',
|
||||
@@ -28,8 +28,8 @@ export const pySparkNotebookContent: azdata.nb.INotebookContents = {
|
||||
}],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
name: 'pysparkkernel',
|
||||
display_name: 'PySpark'
|
||||
name: 'python3',
|
||||
display_name: 'Python 3'
|
||||
}
|
||||
},
|
||||
nbformat: NBFORMAT,
|
||||
@@ -129,18 +129,6 @@ export const sqlNotebookMultipleCellsContent: azdata.nb.INotebookContents = {
|
||||
nbformat_minor: NBFORMAT_MINOR
|
||||
};
|
||||
|
||||
export const pySparkKernelMetadata = {
|
||||
kernelspec: {
|
||||
name: 'pysparkkernel',
|
||||
display_name: 'PySpark'
|
||||
}
|
||||
};
|
||||
|
||||
export const pySparkKernelSpec = {
|
||||
name: 'pyspark',
|
||||
display_name: 'PySpark'
|
||||
};
|
||||
|
||||
export const sqlKernelMetadata = {
|
||||
kernelspec: {
|
||||
name: 'SQL',
|
||||
@@ -165,6 +153,11 @@ export const pythonKernelSpec: azdata.nb.IKernelSpec = {
|
||||
display_name: 'Python 3'
|
||||
};
|
||||
|
||||
export const powershellKernelSpec: azdata.nb.IKernelSpec = {
|
||||
name: 'powershell',
|
||||
display_name: 'PowerShell'
|
||||
};
|
||||
|
||||
export function writeNotebookToFile(pythonNotebook: azdata.nb.INotebookContents, relativeFilePath: string): vscode.Uri {
|
||||
let fileName = getTempFilePath(relativeFilePath);
|
||||
let notebookContentString = JSON.stringify(pythonNotebook);
|
||||
|
||||
@@ -5,16 +5,11 @@
|
||||
|
||||
import 'mocha';
|
||||
import * as azdata from 'azdata';
|
||||
import { getBdcServer, TestServerProfile, getAzureServer, getStandaloneServer } from './testConfig';
|
||||
import { TestServerProfile, getAzureServer, getStandaloneServer } from './testConfig';
|
||||
import { connectToServer, createDB, DefaultConnectTimeoutInMs, asyncTimeout, tryDeleteDB } from './utils';
|
||||
import * as assert from 'assert';
|
||||
|
||||
suite('Object Explorer integration suite', () => {
|
||||
test.skip('BDC instance node label test', async function () {
|
||||
const expectedNodeLabel = ['Databases', 'Security', 'Server Objects'];
|
||||
const server = await getBdcServer();
|
||||
await verifyOeNode(server, DefaultConnectTimeoutInMs, expectedNodeLabel);
|
||||
});
|
||||
test('Standalone instance node label test', async function () {
|
||||
if (process.platform === 'win32') {
|
||||
const expectedNodeLabel = ['Databases', 'Security', 'Server Objects'];
|
||||
@@ -27,18 +22,6 @@ suite('Object Explorer integration suite', () => {
|
||||
const server = await getAzureServer();
|
||||
await verifyOeNode(server, DefaultConnectTimeoutInMs, expectedNodeLabel);
|
||||
});
|
||||
test.skip('BDC instance context menu test', async function () {
|
||||
const server = await getBdcServer();
|
||||
let expectedActions: string[];
|
||||
// Properties comes from the admin-tool-ext-win extension which is for Windows only, so the item won't show up on non-Win32 platforms
|
||||
if (process.platform === 'win32') {
|
||||
expectedActions = ['Manage', 'New Query', 'New Notebook', 'Disconnect', 'Delete Connection', 'Refresh', 'Data-tier Application wizard', 'Launch Profiler', 'Properties'];
|
||||
}
|
||||
else {
|
||||
expectedActions = ['Manage', 'New Query', 'New Notebook', 'Disconnect', 'Delete Connection', 'Refresh', 'Data-tier Application wizard', 'Launch Profiler'];
|
||||
}
|
||||
return await verifyContextMenu(server, expectedActions);
|
||||
});
|
||||
test('Azure SQL DB context menu test @UNSTABLE@', async function () {
|
||||
const server = await getAzureServer();
|
||||
const expectedActions = ['Manage', 'New Query', 'New Notebook', 'Disconnect', 'Delete Connection', 'Refresh', 'Data-tier Application wizard', 'Launch Profiler'];
|
||||
|
||||
@@ -40,7 +40,6 @@ export enum ConnectionProvider {
|
||||
export enum EngineType {
|
||||
Standalone,
|
||||
Azure,
|
||||
BigDataCluster
|
||||
}
|
||||
|
||||
let connectionProviderMapping: { [key: string]: { name: string; displayName: string } } = {};
|
||||
@@ -55,9 +54,6 @@ export function getConfigValue(name: string): string {
|
||||
return configValue ? configValue.toString() : '';
|
||||
}
|
||||
|
||||
export const EnvironmentVariable_BDC_SERVER: string = 'BDC_BACKEND_HOSTNAME';
|
||||
export const EnvironmentVariable_BDC_USERNAME: string = 'BDC_BACKEND_USERNAME';
|
||||
export const EnvironmentVariable_BDC_PASSWORD: string = 'BDC_BACKEND_PWD';
|
||||
export const EnvironmentVariable_STANDALONE_SERVER: string = 'STANDALONE_SQL';
|
||||
export const EnvironmentVariable_STANDALONE_USERNAME: string = 'STANDALONE_SQL_USERNAME';
|
||||
export const EnvironmentVariable_STANDALONE_PASSWORD: string = 'STANDALONE_SQL_PWD';
|
||||
@@ -116,17 +112,6 @@ let TestingServers: TestServerProfile[] = [
|
||||
version: '2012',
|
||||
engineType: EngineType.Azure
|
||||
}),
|
||||
new TestServerProfile(
|
||||
{
|
||||
serverName: getConfigValue(EnvironmentVariable_BDC_SERVER),
|
||||
userName: getConfigValue(EnvironmentVariable_BDC_USERNAME),
|
||||
password: getConfigValue(EnvironmentVariable_BDC_PASSWORD),
|
||||
authenticationType: AuthenticationType.SqlLogin,
|
||||
database: 'master',
|
||||
provider: ConnectionProvider.SQLServer,
|
||||
version: '2019',
|
||||
engineType: EngineType.BigDataCluster
|
||||
}),
|
||||
new TestServerProfile(
|
||||
{
|
||||
serverName: getConfigValue(EnvironmentVariable_STANDALONE_SERVER_2019),
|
||||
@@ -159,11 +144,6 @@ export async function getStandaloneServer(version: '2017' | '2019' = '2017'): Pr
|
||||
return servers.filter(s => s.version === version && s.engineType === EngineType.Standalone)[0];
|
||||
}
|
||||
|
||||
export async function getBdcServer(): Promise<TestServerProfile> {
|
||||
let servers = await getTestingServers();
|
||||
return servers.filter(s => s.version === '2019' && s.engineType === EngineType.BigDataCluster)[0];
|
||||
}
|
||||
|
||||
export async function getTestingServers(): Promise<TestServerProfile[]> {
|
||||
let promise = new Promise<TestServerProfile[]>(resolve => {
|
||||
resolve(TestingServers);
|
||||
|
||||
@@ -21,9 +21,6 @@ export const kustoProviderName = 'KUSTO';
|
||||
|
||||
export const UNTITLED_SCHEMA = 'untitled';
|
||||
|
||||
export const clusterEndpointsProperty = 'clusterEndpoints';
|
||||
export const hdfsRootPath = '/';
|
||||
|
||||
// SERVICE NAMES //////////////////////////////////////////////////////////
|
||||
export const ObjectExplorerService = 'objectexplorer';
|
||||
export const objectExplorerPrefix: string = 'objectexplorer://';
|
||||
|
||||
@@ -1,389 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.6.6",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"\n",
|
||||
"\n",
|
||||
"# View the status of your SQL Server Big Data Cluster\n",
|
||||
"This notebook allows you to see the status of the controller, master instance, and pools in your SQL Server Big Data Cluster.\n",
|
||||
"\n",
|
||||
"> ## **Important Instructions**\n",
|
||||
"> ### **Before you begin, you will need:**\n",
|
||||
">* Big Data Cluster name\n",
|
||||
">* Controller username\n",
|
||||
">* Controller password\n",
|
||||
">* Controller endpoint \n",
|
||||
"\n",
|
||||
"You can find the controller endpoint from the SQL Big Data Cluster dashboard in the Service Endpoints table. The endpoint is listed as **Cluster Management Service.**\n",
|
||||
"\n",
|
||||
"If you do not know the credentials, ask the admin who deployed your cluster.\n",
|
||||
"\n",
|
||||
"### **Prerequisites**\n",
|
||||
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||
"\n",
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|azdata | Command-line tool for installing and managing a Big Data Cluster |[Installation](https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata?view=sqlallproducts-allversions) |\n",
|
||||
"|Pandas Package | Python package for data manipulation | Will be installed by the notebook if not present |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### **Instructions**\n",
|
||||
"* For the best experience, click **Run Cells** on the toolbar above. This will automatically execute all code cells below and show the cluster status in each table.\n",
|
||||
"* When you click **Run Cells** for this Notebook, you will be prompted at the *Log in to your Big Data Cluster* code cell to provide your login credentials. Follow the prompts and press enter to proceed.\n",
|
||||
"* **You won't need to modify any of the code cell contents** in this Notebook. If you accidentally made a change, you can reopen this Notebook from the cluster dashboard.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
""
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": "### **Check azdata version**",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import sys, os\r\n",
|
||||
"cmd = f'azdata --version'\r\n",
|
||||
"cmdOutput = !{cmd}\r\n",
|
||||
"azdataStr = '\\'azdata\\''\r\n",
|
||||
"if len(cmdOutput) > 0 and ('command not found' in cmdOutput[1] or f'{azdataStr} is not recognized as an internal or external command' in cmdOutput[0]):\r\n",
|
||||
" raise SystemExit('azdata not found! Please make sure azdata is installed and added to path' + '.\\n')\r\n",
|
||||
"if '15.0' in cmdOutput[0]:\r\n",
|
||||
" print('azdata version: ' + cmdOutput[0])\r\n",
|
||||
""
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": "### **Install latest version of pandas**",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"#install pandas\r\n",
|
||||
"import pandas\r\n",
|
||||
"pandas_version = pandas.__version__.split('.')\r\n",
|
||||
"pandas_major = int(pandas_version[0])\r\n",
|
||||
"pandas_minor = int(pandas_version[1])\r\n",
|
||||
"pandas_patch = int(pandas_version[2])\r\n",
|
||||
"if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\r\n",
|
||||
" pandasVersion = 'pandas==0.24.2'\r\n",
|
||||
" cmd = f'{sys.executable} -m pip install {pandasVersion}'\r\n",
|
||||
" cmdOutput = !{cmd}\r\n",
|
||||
" print(f'\\nSuccess: Upgraded pandas to 0.24.2.')\r\n",
|
||||
"else:\r\n",
|
||||
" print('Pandas required version is already installed!') "
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## **Log in to your Big Data Cluster**\r\n",
|
||||
"To view cluster status, you will need to connect to your Big Data Cluster through azdata. \r\n",
|
||||
"\r\n",
|
||||
"When you run this code cell, you will be prompted for:\r\n",
|
||||
"- Cluster name\r\n",
|
||||
"- Controller username\r\n",
|
||||
"- Controller password\r\n",
|
||||
"\r\n",
|
||||
"To proceed:\r\n",
|
||||
"- **Click** on the input box\r\n",
|
||||
"- **Type** the login info\r\n",
|
||||
"- **Press** enter.\r\n",
|
||||
"\r\n",
|
||||
"If your cluster is missing a configuration file, you will be asked to provide your controller endpoint. (Format: **https://00.00.00.000:00000**) You can find the controller endpoint from the Big Data Cluster dashboard in the Service Endpoints table. The endpoint is listed as **Cluster Management Service.**\r\n",
|
||||
""
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import os, getpass, json\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"from IPython.display import *\n",
|
||||
"\n",
|
||||
"def PromptForInfo(promptMsg, isPassword, errorMsg):\n",
|
||||
" if isPassword:\n",
|
||||
" promptResponse = getpass.getpass(prompt=promptMsg)\n",
|
||||
" else:\n",
|
||||
" promptResponse = input(promptMsg)\n",
|
||||
" if promptResponse == \"\":\n",
|
||||
" raise SystemExit(errorMsg + '\\n')\n",
|
||||
" return promptResponse\n",
|
||||
"\n",
|
||||
"# Prompt user inputs:\n",
|
||||
"cluster_name = PromptForInfo('Please provide your Cluster Name: ', False, 'Cluster Name is required!')\n",
|
||||
"\n",
|
||||
"controller_username = PromptForInfo('Please provide your Controller Username for login: ', False, 'Controller Username is required!')\n",
|
||||
"\n",
|
||||
"controller_password = PromptForInfo('Controller Password: ', True, 'Password is required!')\n",
|
||||
"print('***********')\n",
|
||||
"\n",
|
||||
"!azdata logout\n",
|
||||
"# Login in to your Big Data Cluster \n",
|
||||
"cmd = f'azdata login --namespace {cluster_name} -u {controller_username} -a yes'\n",
|
||||
"print(\"Start \" + cmd)\n",
|
||||
"os.environ['CONTROLLER_USERNAME'] = controller_username\n",
|
||||
"os.environ['CONTROLLER_PASSWORD'] = controller_password\n",
|
||||
"os.environ['ACCEPT_EULA'] = 'yes'\n",
|
||||
"\n",
|
||||
"loginResult = !{cmd}\n",
|
||||
"if 'ERROR: Please check your kube config or specify the correct controller endpoint with: --controller-endpoint https://<ip>:<port>.' in loginResult[0] or 'ERROR' in loginResult[0]:\n",
|
||||
" controller_ip = input('Please provide your Controller endpoint: ')\n",
|
||||
" if controller_ip == \"\":\n",
|
||||
" raise SystemExit(f'Controller IP is required!' + '\\n')\n",
|
||||
" else:\n",
|
||||
" cmd = f'azdata login --namespace {cluster_name} -e {controller_ip} -u {controller_username} -a yes'\n",
|
||||
" loginResult = !{cmd}\n",
|
||||
"print(loginResult)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## **Status of Big Data Cluster**\r\n",
|
||||
"After you successfully login to your bdc, you can view the overall status of each container before drilling down into each component."
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Helper methods for formatting\n",
|
||||
"def formatColumnNames(column):\n",
|
||||
" return ' '.join(word[0].upper() + word[1:] for word in column.split())\n",
|
||||
"\n",
|
||||
"pd.set_option('display.max_colwidth', -1)\n",
|
||||
"def show_results(results):\n",
|
||||
" strResult = ''.join(results)\n",
|
||||
" jsonResults = json.loads(strResult)\n",
|
||||
" results = jsonResults['result']\n",
|
||||
" if isinstance(results, list):\n",
|
||||
" for result in results:\n",
|
||||
" if isinstance(result, list):\n",
|
||||
" show_formattedArray(result)\n",
|
||||
" else:\n",
|
||||
" show_keys(result)\n",
|
||||
" else:\n",
|
||||
" show_keys(results)\n",
|
||||
"\n",
|
||||
"def show_keys(results):\n",
|
||||
" listKeys = []\n",
|
||||
" if isinstance(results, dict):\n",
|
||||
" for key in results.keys():\n",
|
||||
" if results[key] and not isinstance(results[key], list):\n",
|
||||
" print('\\033[1m' + formatColumnNames(key) + ': \\033[0m' + results[key])\n",
|
||||
" if results[key] and isinstance(results[key], list):\n",
|
||||
" listKeys.append(key)\n",
|
||||
" for key in listKeys:\n",
|
||||
" show_formattedArray(results[key])\n",
|
||||
" if isinstance(results, str):\n",
|
||||
" print('\\033[1m' + results + ': \\033[0m')\n",
|
||||
"\n",
|
||||
"def show_formattedArray(results):\n",
|
||||
" fomattedRow = []\n",
|
||||
" if not isinstance(results, list):\n",
|
||||
" show_formattedResults(results)\n",
|
||||
" else:\n",
|
||||
" for row in results:\n",
|
||||
" if isinstance(row, str):\n",
|
||||
" show_keys(row)\n",
|
||||
" else:\n",
|
||||
" fomattedRow.append({ k : v for k,v in row.items() if isinstance(v, str) or v is None})\n",
|
||||
" df = pd.DataFrame(fomattedRow)\n",
|
||||
" df.columns = [formatColumnNames(n) for n in fomattedRow[0].keys()]\n",
|
||||
" mydata = HTML(df.to_html(render_links=True))\n",
|
||||
" display(mydata)\n",
|
||||
" nameKeys = [k for k in fomattedRow[0].keys() if 'Name' in k]\n",
|
||||
" for key in results[0].keys():\n",
|
||||
" if key not in fomattedRow[0].keys():\n",
|
||||
" for result in results:\n",
|
||||
" print('\\033[1m' + formatColumnNames(nameKeys[0]) + ': \\033[0m' + result[nameKeys[0]])\n",
|
||||
" show_formattedArray(result[key])\n",
|
||||
"\n",
|
||||
"def show_formattedResults(input):\n",
|
||||
" df = pd.DataFrame([input])\n",
|
||||
" df.columns = [formatColumnNames(n) for n in [input][0].keys()]\n",
|
||||
" mydata = HTML(df.to_html(render_links=True))\n",
|
||||
" display(mydata)\n",
|
||||
" \n",
|
||||
"# Display status of Big Data Cluster\n",
|
||||
"results = !azdata bdc status show -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## **Cluster Status**\r\n",
|
||||
"For each cluster component below, running each code cell will generate a table. This table will include:\r\n",
|
||||
"\r\n",
|
||||
"|Column Name|Description|\r\n",
|
||||
"|---|---|\r\n",
|
||||
"|**Kind** | Identifies if component is a pod or a set. |\r\n",
|
||||
"|**LogsURL** | Link to [Kibana](https://www.elastic.co/guide/en/kibana/current/introduction.html) logs which is used for troubleshooting. |\r\n",
|
||||
"|**Name** | Provides the specific name of the pod or set. |\r\n",
|
||||
"|**NodeMetricsURL** | Link to [Grafana](https://grafana.com/docs/guides/basic_concepts/) dashboard to view key metrics of the node. |\r\n",
|
||||
"|**SQLMetricsURL** | Link to [Grafana](https://grafana.com/docs/guides/basic_concepts/) dashboard to view key metrics of the SQL instance. |\r\n",
|
||||
"|**State** | Indicates state of the pod or set. |\r\n",
|
||||
"\r\n",
|
||||
"----------------------------------------------------------------"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Controller status**\n",
|
||||
"To learn more about the controller, [read here.](https://docs.microsoft.com/sql/big-data-cluster/concept-controller?view=sql-server-ver15)"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Display status of controller\n",
|
||||
"results = !azdata bdc control status show --all -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Master Instance status**\n",
|
||||
"To learn more about the master instance, [read here.](https://docs.microsoft.com/sql/big-data-cluster/concept-master-instance?view=sqlallproducts-allversions)"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"results = !azdata bdc sql status show --resource master --all -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Compute Pool status**\n",
|
||||
"To learn more about compute pool, [read here.](https://docs.microsoft.com/sql/big-data-cluster/concept-compute-pool?view=sqlallproducts-allversions)"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Display status of compute pool\n",
|
||||
"results = !azdata bdc sql status show --resource compute-0 --all -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Storage Pool status**\n",
|
||||
"To learn more about storage pool, [read here.](https://docs.microsoft.com/sql/big-data-cluster/concept-storage-pool?view=sqlallproducts-allversions)"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Display status of storage pools\n",
|
||||
"results = !azdata bdc sql status show --resource storage-0 --all -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Data Pool status**\n",
|
||||
"To learn more about data pool, [read here.](https://docs.microsoft.com/sql/big-data-cluster/concept-data-pool?view=sqlallproducts-allversions)"
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Display status of data pools\n",
|
||||
"results = !azdata bdc sql status show --resource data-0 --all -o json\n",
|
||||
"show_results(results)"
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Spark Pool status**\n",
|
||||
"Displays status of spark pool if it exists. Otherwise, will show as \"No spark pool.\""
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Display status of spark pool\n",
|
||||
"results = !azdata bdc spark status show --all -o json\n",
|
||||
"show_results(results)\n",
|
||||
""
|
||||
],
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"execution_count": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,82 +44,6 @@
|
||||
"light": "resources/light/export_blue_light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.uploadFiles",
|
||||
"title": "%mssqlCluster.uploadFiles%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.mkdir",
|
||||
"title": "%mssqlCluster.mkdir%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.deleteFiles",
|
||||
"title": "%mssqlCluster.deleteFiles%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.previewFile",
|
||||
"title": "%mssqlCluster.previewFile%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.saveFile",
|
||||
"title": "%mssqlCluster.saveFile%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.copyPath",
|
||||
"title": "%mssqlCluster.copyPath%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.manageAccess",
|
||||
"title": "%mssqlCluster.manageAccess%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.newNotebook",
|
||||
"title": "%notebook.command.new%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/new_notebook.svg",
|
||||
"light": "resources/light/new_notebook.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.openNotebook",
|
||||
"title": "%notebook.command.open%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitSparkJob",
|
||||
"title": "%title.submitSparkJob%"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.task.submitSparkJob",
|
||||
"title": "%title.newSparkJob%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/new_spark_job_inverse.svg",
|
||||
"light": "resources/light/new_spark_job.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.openClusterDashboard",
|
||||
"title": "%title.openClusterDashboard%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/cluster_status_inverse.svg",
|
||||
"light": "resources/light/cluster_status.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.task.openYarnHistory",
|
||||
"title": "%title.openYarnHistory%",
|
||||
"icon": {
|
||||
"dark": "resources/light/hadoop.svg",
|
||||
"light": "resources/light/hadoop.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
||||
"title": "%title.submitSparkJob%"
|
||||
},
|
||||
{
|
||||
"command": "mssql.searchServers",
|
||||
"title": "%title.searchServers%"
|
||||
@@ -434,54 +358,6 @@
|
||||
"command": "mssql.exportNotebookToSql",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.uploadFiles",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.mkdir",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.deleteFiles",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.previewFile",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.saveFile",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.copyPath",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.manageAccess",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.newNotebook",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.openNotebook",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.task.submitSparkJob",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.task.openClusterDashboard",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssql.newTable",
|
||||
"when": "false"
|
||||
@@ -492,51 +368,6 @@
|
||||
}
|
||||
],
|
||||
"objectExplorer/item/context": [
|
||||
{
|
||||
"command": "mssqlCluster.uploadFiles",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:message && nodeType != mssqlCluster:file && nodeSubType=~/^(?!:mount).*$/",
|
||||
"group": "1mssqlCluster@1"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.mkdir",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:message && nodeType != mssqlCluster:file && nodeSubType=~/^(?!:mount).*$/",
|
||||
"group": "1mssqlCluster@1"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.saveFile",
|
||||
"when": "nodeType == mssqlCluster:file",
|
||||
"group": "1mssqlCluster@1"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.previewFile",
|
||||
"when": "nodeType == mssqlCluster:file",
|
||||
"group": "1mssqlCluster@2"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.copyPath",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:connection && nodeType != mssqlCluster:message && nodeType != mssqlCluster:hdfs",
|
||||
"group": "1mssqlCluster@3"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.manageAccess",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:connection && nodeType != mssqlCluster:message",
|
||||
"group": "1mssqlCluster@3"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.deleteFiles",
|
||||
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:hdfs && nodeType != mssqlCluster:connection && viewItem != mssqlCluster:connection && nodeType != mssqlCluster:message && nodeSubType=~/^(?!:mount).*$/",
|
||||
"group": "1mssqlCluster@4"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitSparkJob",
|
||||
"when": "nodeType == mssqlCluster:hdfs",
|
||||
"group": "1mssqlCluster@7"
|
||||
},
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
||||
"when": "nodeType == mssqlCluster:file && nodeSubType =~/:spark:/",
|
||||
"group": "1mssqlCluster@6"
|
||||
},
|
||||
{
|
||||
"command": "mssql.designTable",
|
||||
"when": "connectionProvider == MSSQL && nodeType == Table && nodeSubType != LedgerDropped",
|
||||
@@ -743,57 +574,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "mssql-big-data-cluster",
|
||||
"description": "%tab.bigDataClusterDescription%",
|
||||
"provider": "MSSQL",
|
||||
"title": "%title.bigDataCluster%",
|
||||
"group": "home",
|
||||
"when": "connectionProvider == 'MSSQL' && mssql:iscluster && dashboardContext == 'server'",
|
||||
"container": {
|
||||
"grid-container": [
|
||||
{
|
||||
"name": "%title.tasks%",
|
||||
"row": 0,
|
||||
"col": 0,
|
||||
"colspan": 1,
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"mssqlCluster.task.newNotebook",
|
||||
"mssqlCluster.task.openNotebook",
|
||||
"mssqlCluster.livy.task.submitSparkJob",
|
||||
"mssqlCluster.task.openClusterDashboard"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "%title.endpoints%",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"rowspan": 2.5,
|
||||
"colspan": 2,
|
||||
"widget": {
|
||||
"modelview": {
|
||||
"id": "bdc-endpoints"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "%title.books%",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"colspan": 1,
|
||||
"widget": {
|
||||
"modelview": {
|
||||
"id": "books-widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"connectionProvider": {
|
||||
"providerId": "MSSQL",
|
||||
"displayName": "%mssql.provider.displayName%",
|
||||
@@ -810,13 +590,6 @@
|
||||
"light": "resources/light/azureDB.svg",
|
||||
"dark": "resources/dark/azureDB_inverse.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mssql:cluster",
|
||||
"path": {
|
||||
"light": "resources/light/sql_bigdata_cluster.svg",
|
||||
"dark": "resources/dark/sql_bigdata_cluster_inverse.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connectionOptions": [
|
||||
|
||||
@@ -6,34 +6,9 @@
|
||||
"json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.",
|
||||
"json.format.enable.desc": "Enable/disable default JSON formatter (requires restart)",
|
||||
|
||||
"mssqlCluster.uploadFiles": "Upload files",
|
||||
"mssqlCluster.mkdir": "New directory",
|
||||
"mssqlCluster.deleteFiles": "Delete",
|
||||
"mssqlCluster.previewFile": "Preview",
|
||||
"mssqlCluster.saveFile": "Save",
|
||||
"mssqlCluster.copyPath": "Copy Path",
|
||||
"mssqlCluster.manageAccess": "Manage Access",
|
||||
|
||||
"notebook.command.new": "New Notebook",
|
||||
"notebook.command.open": "Open Notebook",
|
||||
|
||||
"tab.bigDataClusterDescription": "Tasks and information about your SQL Server Big Data Cluster",
|
||||
"title.bigDataCluster": "SQL Server Big Data Cluster",
|
||||
"title.submitSparkJob": "Submit Spark Job",
|
||||
"title.newSparkJob": "New Spark Job",
|
||||
"title.openSparkHistory": "View Spark History",
|
||||
"title.openYarnHistory": "View Yarn History",
|
||||
"title.tasks": "Tasks",
|
||||
"title.installPackages": "Install Packages",
|
||||
"title.configurePython": "Configure Python for Notebooks",
|
||||
"title.openClusterDashboard": "Cluster\nDashboard",
|
||||
|
||||
"title.searchServers": "Search: Servers",
|
||||
"title.clearSearchServerResult": "Search: Clear Search Server Results",
|
||||
|
||||
"title.endpoints": "Service Endpoints",
|
||||
"title.books": "Notebooks",
|
||||
|
||||
"title.showLogFile": "Show Log File",
|
||||
|
||||
"mssql.disabled": "Disabled",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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:#fff;}</style></defs><title>cluster_inverse</title><path class="cls-1" d="M14,7a1.94,1.94,0,0,1,.78.16,2,2,0,0,1,1.07,1.07,2,2,0,0,1,0,1.55,2,2,0,0,1-1.07,1.07,2,2,0,0,1-1.51,0,2.05,2.05,0,0,1-1.05-1,1.88,1.88,0,0,1-.2-.72L10.84,9a3,3,0,0,1-.56,1,3,3,0,0,1-.87.7L9.86,12H10a1.94,1.94,0,0,1,.78.16,2,2,0,0,1,1.07,1.07,2,2,0,0,1,0,1.55,2,2,0,0,1-1.07,1.07,2,2,0,0,1-1.55,0,2,2,0,0,1-1.07-1.07A2,2,0,0,1,8.25,13a2,2,0,0,1,.67-.72L8.46,11l-.23,0H8a3,3,0,0,1-1.36-.32,3,3,0,0,1-1.07-.9L4,10.58a2,2,0,0,1-.11,1.2,2,2,0,0,1-1.07,1.07A1.94,1.94,0,0,1,2,13a1.94,1.94,0,0,1-.78-.16A2,2,0,0,1,.16,11.78a2,2,0,0,1,0-1.55A2,2,0,0,1,1.22,9.16,1.94,1.94,0,0,1,2,9a2,2,0,0,1,.83.18,2,2,0,0,1,.68.51l1.63-.81A3,3,0,0,1,5.2,6.93,2.91,2.91,0,0,1,5.77,6L4.82,4.82A2,2,0,0,1,4,5a1.94,1.94,0,0,1-.78-.16A2,2,0,0,1,2.16,3.78a2,2,0,0,1,0-1.55A2,2,0,0,1,3.22,1.16a2,2,0,0,1,1.55,0A2,2,0,0,1,5.84,2.22,1.94,1.94,0,0,1,6,3a1.94,1.94,0,0,1-.4,1.2l.94,1.18a3.24,3.24,0,0,1,.71-.28A2.94,2.94,0,0,1,8,5a3,3,0,0,1,1.23.26l1.28-1.92a2,2,0,0,1-.37-.62A2,2,0,0,1,10,2a1.94,1.94,0,0,1,.16-.78A2,2,0,0,1,11.22.16a2,2,0,0,1,1.55,0,2,2,0,0,1,1.07,1.07A1.94,1.94,0,0,1,14,2a1.94,1.94,0,0,1-.16.78,2,2,0,0,1-1.07,1.07A1.94,1.94,0,0,1,12,4a2.06,2.06,0,0,1-.66-.11L10.05,5.82A3,3,0,0,1,11,8l1.17.2a2,2,0,0,1,.74-.86,2.14,2.14,0,0,1,.52-.24A1.92,1.92,0,0,1,14,7ZM2,12a1,1,0,0,0,.39-.08,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53A1,1,0,0,0,2,12ZM3,3a1,1,0,0,0,.08.39,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53A1,1,0,0,0,3,3Zm5,7a1.94,1.94,0,0,0,.78-.16A2,2,0,0,0,9.84,8.78a2,2,0,0,0,0-1.55A2,2,0,0,0,8.78,6.16a2,2,0,0,0-1.55,0A2,2,0,0,0,6.16,7.22a2,2,0,0,0,0,1.55A2,2,0,0,0,7.22,9.84,1.94,1.94,0,0,0,8,10Zm3,4a1,1,0,0,0-.08-.39,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53A1,1,0,0,0,11,14ZM12,1a1,1,0,0,0-.39.08,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53A1,1,0,0,0,12,1Zm2,9a1,1,0,0,0,.39-.08,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53A1,1,0,0,0,14,10Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<title>new_notebook_inverse</title>
|
||||
<g>
|
||||
<path class="st0" d="M0,2h16v12H0V2z M15,3H1v5h1.7l1.8-3.6l2.5,5l2-4L10.3,8H15V3z M1,13h14V9H9.7L9,7.6l-2,4l-2.5-5L3.3,9H1V13z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 585 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 3V6H5V3H11ZM10 5V4H6V5H10ZM1 0H14V16H1V13H0V12H1V10H0V9H1V7H0V6H1V4H0V3H1V0ZM13 15V1H2V3H3V4H2V6H3V7H2V9H3V10H2V12H3V13H2V15H13Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -1 +0,0 @@
|
||||
<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:#fff;}.cls-2{fill:#0095d7;}</style></defs><title>open_notebook_inverse</title><path class="cls-1" d="M12.55,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9.18a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.29,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.93,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.67,3.2H2v.9H.15V15.85H13.72V5.48ZM2.86,4.1H4.67a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.86ZM1,15V5H2v9H4.67a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39ZM12.8,15H7.11a2.7,2.7,0,0,1,.47-.39A2.83,2.83,0,0,1,8,14.28a3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9.18,14h2.73V5h.89Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><path class="cls-2" d="M13.19,3.57h0v0Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><polygon class="cls-2" points="14.21 1.65 14.19 1.65 14.19 1.63 14.21 1.65"/><path class="cls-2" d="M15.91,2.1,14.2,3.81l-.38.38-.62-.61v0l1-1H12.79a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47A3.41,3.41,0,0,0,9.5,6.11H8.6a4.68,4.68,0,0,1,.16-1.19A4.74,4.74,0,0,1,9,4.26a2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16,2.79,2.79,0,0,1,.34-.18l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.82,0Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>cluster</title><path d="M14,7a1.94,1.94,0,0,1,.78.16,2,2,0,0,1,1.07,1.07,2,2,0,0,1,0,1.55,2,2,0,0,1-1.07,1.07,2,2,0,0,1-1.51,0,2.05,2.05,0,0,1-1.05-1,1.88,1.88,0,0,1-.2-.72L10.84,9a3,3,0,0,1-.56,1,3,3,0,0,1-.87.7L9.86,12H10a1.94,1.94,0,0,1,.78.16,2,2,0,0,1,1.07,1.07,2,2,0,0,1,0,1.55,2,2,0,0,1-1.07,1.07,2,2,0,0,1-1.55,0,2,2,0,0,1-1.07-1.07A2,2,0,0,1,8.25,13a2,2,0,0,1,.67-.72L8.46,11l-.23,0H8a3,3,0,0,1-1.36-.32,3,3,0,0,1-1.07-.9L4,10.58a2,2,0,0,1-.11,1.2,2,2,0,0,1-1.07,1.07A1.94,1.94,0,0,1,2,13a1.94,1.94,0,0,1-.78-.16A2,2,0,0,1,.16,11.78a2,2,0,0,1,0-1.55A2,2,0,0,1,1.22,9.16,1.94,1.94,0,0,1,2,9a2,2,0,0,1,.83.18,2,2,0,0,1,.68.51l1.63-.81A3,3,0,0,1,5.2,6.93,2.91,2.91,0,0,1,5.77,6L4.82,4.82A2,2,0,0,1,4,5a1.94,1.94,0,0,1-.78-.16A2,2,0,0,1,2.16,3.78a2,2,0,0,1,0-1.55A2,2,0,0,1,3.22,1.16a2,2,0,0,1,1.55,0A2,2,0,0,1,5.84,2.22,1.94,1.94,0,0,1,6,3a1.94,1.94,0,0,1-.4,1.2l.94,1.18a3.24,3.24,0,0,1,.71-.28A2.94,2.94,0,0,1,8,5a3,3,0,0,1,1.23.26l1.28-1.92a2,2,0,0,1-.37-.62A2,2,0,0,1,10,2a1.94,1.94,0,0,1,.16-.78A2,2,0,0,1,11.22.16a2,2,0,0,1,1.55,0,2,2,0,0,1,1.07,1.07A1.94,1.94,0,0,1,14,2a1.94,1.94,0,0,1-.16.78,2,2,0,0,1-1.07,1.07A1.94,1.94,0,0,1,12,4a2.06,2.06,0,0,1-.66-.11L10.05,5.82A3,3,0,0,1,11,8l1.17.2a2,2,0,0,1,.74-.86,2.14,2.14,0,0,1,.52-.24A1.92,1.92,0,0,1,14,7ZM2,12a1,1,0,0,0,.39-.08,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53A1,1,0,0,0,2,12ZM3,3a1,1,0,0,0,.08.39,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53A1,1,0,0,0,3,3Zm5,7a1.94,1.94,0,0,0,.78-.16A2,2,0,0,0,9.84,8.78a2,2,0,0,0,0-1.55A2,2,0,0,0,8.78,6.16a2,2,0,0,0-1.55,0A2,2,0,0,0,6.16,7.22a2,2,0,0,0,0,1.55A2,2,0,0,0,7.22,9.84,1.94,1.94,0,0,0,8,10Zm3,4a1,1,0,0,0-.08-.39,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53A1,1,0,0,0,11,14ZM12,1a1,1,0,0,0-.39.08,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53,1,1,0,0,0,.78,0,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53A1,1,0,0,0,12,1Zm2,9a1,1,0,0,0,.39-.08,1,1,0,0,0,.53-.53,1,1,0,0,0,0-.78,1,1,0,0,0-.53-.53,1,1,0,0,0-.78,0,1,1,0,0,0-.53.53,1,1,0,0,0,0,.78,1,1,0,0,0,.53.53A1,1,0,0,0,14,10Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<title>new_notebook</title>
|
||||
<g>
|
||||
<path d="M0,2h16v12H0V2z M15,3H1v5h1.7l1.8-3.6l2.5,5l2-4L10.3,8H15V3z M1,13h14V9H9.7L9,7.6l-2,4l-2.5-5L3.3,9H1V13z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 38 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 3V6H5V3H11ZM10 5V4H6V5H10ZM1 0H14V16H1V13H0V12H1V10H0V9H1V7H0V6H1V4H0V3H1V0ZM13 15V1H2V3H3V4H2V6H3V7H2V9H3V10H2V12H3V13H2V15H13Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 263 B |
@@ -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 16 16"><defs><style>.cls-1,.cls-2{fill:none;}.cls-1{clip-rule:evenodd;}.cls-3{clip-path:url(#clip-path);}.cls-4{fill:#e25a1c;}.cls-5{clip-path:url(#clip-path-2);}.cls-6{fill:#3c3a3e;}.cls-7{clip-path:url(#clip-path-3);}.cls-8{clip-path:url(#clip-path-4);}.cls-9{clip-path:url(#clip-path-5);}.cls-10{clip-path:url(#clip-path-6);}.cls-11{clip-path:url(#clip-path-7);}</style><clipPath id="clip-path"><path class="cls-1" d="M14.58,6.89l0-.06L14,5.7a.07.07,0,0,1,0-.09l.95-1.11a.1.1,0,0,0,0,0l-.28.07-1.15.3a.05.05,0,0,1-.07,0l-.65-1.09a.15.15,0,0,0,0-.05l-.05.29-.18,1s0,.07,0,.11,0,0-.05.06l-1.35.43-.06,0L12.14,6l0,0-.69.45a.07.07,0,0,1-.08,0l-.83-.37a.85.85,0,0,1-.32-.23.43.43,0,0,1,.1-.68,1.23,1.23,0,0,1,.28-.13l1.33-.42A.08.08,0,0,0,12,4.62l.18-1a1.78,1.78,0,0,1,.14-.54.9.9,0,0,1,.12-.18.39.39,0,0,1,.61,0,1.15,1.15,0,0,1,.16.21l.61,1a.07.07,0,0,0,.09,0l1.48-.39a.7.7,0,0,1,.31,0,.3.3,0,0,1,.25.44.84.84,0,0,1-.16.26l-1,1.21a.07.07,0,0,0,0,.09l.62,1.17a.65.65,0,0,1,.09.3.48.48,0,0,1-.42.48.87.87,0,0,1-.39,0l-.93-.28a.05.05,0,0,1,0-.05c0-.22-.07-.44-.11-.65a.14.14,0,0,1,0,0l1.07.29"/></clipPath><clipPath id="clip-path-2"><path class="cls-1" d="M14,10.07h-.84a.08.08,0,0,1-.08,0l-1-1.51,0-.06-.21,1.6h-.73l0-.21.21-1.63.21-1.56a.07.07,0,0,1,0,0l.76-.49h0l-.23,1.74h0l1.2-1.33,0,.18c0,.17.06.33.09.5a.08.08,0,0,1,0,.08l-.77.8,0,0,0,0L13.95,10l0,0h0"/></clipPath><clipPath id="clip-path-3"><path class="cls-1" d="M3.39,9.86l-.06.47-.08.61s0,0,0,0H2.59l0-.29.13-1c.05-.39.09-.77.16-1.16A1.81,1.81,0,0,1,4.29,7.1a1.42,1.42,0,0,1,1.11.18A1.24,1.24,0,0,1,6,8.22a1.66,1.66,0,0,1-.55,1.43,1.7,1.7,0,0,1-.95.47,1.4,1.4,0,0,1-1-.23Zm1.93-1.4a1.71,1.71,0,0,0,0-.22.75.75,0,0,0-.91-.49,1,1,0,0,0-.8.9A.73.73,0,0,0,4,9.42a.86.86,0,0,0,.76-.09A1,1,0,0,0,5.32,8.46Z"/></clipPath><clipPath id="clip-path-4"><path class="cls-1" d="M3.06,6.64l-.66.49L2.3,7a.51.51,0,0,0-.38-.24.43.43,0,0,0-.36.14.25.25,0,0,0,0,.33c.09.12.19.23.29.33l.5.53a1.12,1.12,0,0,1,.3.57,1.16,1.16,0,0,1-.13.75,1.43,1.43,0,0,1-1.08.76,1.42,1.42,0,0,1-.63,0,.93.93,0,0,1-.59-.52c0-.09-.08-.19-.12-.28l.72-.38L.81,9a2.14,2.14,0,0,0,.12.24.49.49,0,0,0,.64.18.7.7,0,0,0,.17-.11.37.37,0,0,0,.07-.51,2.49,2.49,0,0,0-.23-.28c-.2-.22-.4-.43-.59-.65a1,1,0,0,1-.25-.53.91.91,0,0,1,.13-.62A1.34,1.34,0,0,1,2.13,6a1,1,0,0,1,.76.4l.17.23"/></clipPath><clipPath id="clip-path-5"><path class="cls-1" d="M8.4,9.14l-.11.81a.05.05,0,0,1,0,0A1.45,1.45,0,0,1,6.56,9.7a1.31,1.31,0,0,1-.33-1A1.8,1.8,0,0,1,7.79,7.08,1.33,1.33,0,0,1,9,7.52a1.24,1.24,0,0,1,.31.9c0,.22,0,.44-.07.67s-.08.63-.12.94v0H8.48l0-.21c0-.36.1-.72.14-1.09a1.16,1.16,0,0,0-.09-.66A.64.64,0,0,0,8,7.74a1,1,0,0,0-1.09.79.75.75,0,0,0,.3.81A.82.82,0,0,0,8,9.4a1,1,0,0,0,.37-.26"/></clipPath><clipPath id="clip-path-6"><path class="cls-1" d="M11.15,7.14l-.09.68h-.41a.26.26,0,0,0-.24.17.71.71,0,0,0,0,.12l-.2,1.56-.05.39H9.45l0-.29.13-1c0-.29.07-.58.12-.87a.94.94,0,0,1,.84-.75h.57"/></clipPath><clipPath id="clip-path-7"><path class="cls-2" d="M14.28,9.68v.38h-.06V9.68h-.11V9.62h.27v.06Zm.5.38V9.69h0l-.11.37h0l-.11-.37h0v.37h-.06V9.62h.09l.1.34.1-.34h.09v.44Z"/></clipPath></defs><title>new_spark_job</title><g class="cls-3"><rect class="cls-4" x="5.65" y="-1.69" width="14.7" height="13.77"/></g><g class="cls-5"><rect class="cls-6" x="6.67" y="1.7" width="11.72" height="12.78"/></g><g class="cls-7"><rect class="cls-6" x="-1.83" y="2.64" width="12.23" height="12.75"/></g><g class="cls-8"><rect class="cls-6" x="-4.35" y="1.59" width="11.83" height="12.99"/></g><g class="cls-9"><rect class="cls-6" x="1.82" y="2.64" width="11.93" height="11.91"/></g><g class="cls-10"><rect class="cls-6" x="5.03" y="2.72" width="10.53" height="11.76"/></g><g class="cls-11"><rect class="cls-6" x="9.7" y="5.2" width="9.55" height="9.27"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1 +0,0 @@
|
||||
<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:#00539c;}</style></defs><title>open_notebook</title><path d="M12.4,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.14,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.78,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.52,3.2H1.81v.9H0V15.85H13.57V5.48ZM2.71,4.1H4.52a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.71ZM.9,15V5h.91v9H4.52a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39Zm11.75,0H7a2.7,2.7,0,0,1,.47-.39,2.83,2.83,0,0,1,.47-.29,3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9,14h2.73V5h.89Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><path class="cls-1" d="M13,3.57h0v0Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><polygon class="cls-1" points="14.06 1.65 14.04 1.65 14.04 1.63 14.06 1.65"/><path class="cls-1" d="M15.76,2.1,14,3.81l-.38.38L13,3.58v0l1-1H12.64a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47,3.41,3.41,0,0,0-.27,1.39h-.9a4.68,4.68,0,0,1,.16-1.19,4.74,4.74,0,0,1,.25-.66,2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16A2.79,2.79,0,0,1,11,2.08l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.67,0Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,45 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -9,30 +9,8 @@ export const serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/
|
||||
export const extensionConfigSectionName = 'mssql';
|
||||
|
||||
// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
|
||||
export const mssqlClusterProviderName = 'mssqlCluster';
|
||||
export const hadoopEndpointNameGateway = 'gateway';
|
||||
export const protocolVersion = '1.0';
|
||||
export const authenticationTypePropName = 'authenticationType';
|
||||
export const integratedAuth = 'integrated';
|
||||
export const hostPropName = 'host';
|
||||
export const userPropName = 'user';
|
||||
export const knoxPortPropName = 'knoxport';
|
||||
export const passwordPropName = 'password';
|
||||
export const groupIdPropName = 'groupId';
|
||||
export const defaultKnoxPort = 30443;
|
||||
export const groupIdName = 'groupId';
|
||||
export const sqlProviderName = 'MSSQL';
|
||||
|
||||
export const UNTITLED_SCHEMA = 'untitled';
|
||||
|
||||
export const hadoopConnectionTimeoutSeconds = 15;
|
||||
export const hdfsRootPath = '/';
|
||||
|
||||
export const clusterEndpointsProperty = 'clusterEndpoints';
|
||||
export const isBigDataClusterProperty = 'isBigDataCluster';
|
||||
|
||||
export const ViewType = 'view';
|
||||
|
||||
// SERVICE NAMES //////////////////////////////////////////////////////////
|
||||
export const ObjectExplorerService = 'objectexplorer';
|
||||
export const CmsService = 'cmsService';
|
||||
@@ -44,39 +22,3 @@ export const SqlAssessmentService = 'sqlAssessmentService';
|
||||
export const SqlMigrationService = 'sqlMigrationService';
|
||||
export const NotebookConvertService = 'notebookConvertService';
|
||||
export const AzureBlobService = 'azureBlobService';
|
||||
|
||||
export enum BuiltInCommands {
|
||||
SetContext = 'setContext'
|
||||
}
|
||||
|
||||
export enum CommandContext {
|
||||
WizardServiceEnabled = 'wizardservice:enabled'
|
||||
}
|
||||
|
||||
export enum MssqlClusterItems {
|
||||
Connection = 'mssqlCluster:connection',
|
||||
Folder = 'mssqlCluster:folder',
|
||||
File = 'mssqlCluster:file',
|
||||
Error = 'mssqlCluster:error'
|
||||
}
|
||||
|
||||
export enum MssqlClusterItemsSubType {
|
||||
Mount = ':mount:',
|
||||
MountChild = ':mountChild:',
|
||||
Spark = ':spark:'
|
||||
}
|
||||
|
||||
// SPARK JOB SUBMISSION //////////////////////////////////////////////////////////
|
||||
export const mssqlClusterNewNotebookTask = 'mssqlCluster.task.newNotebook';
|
||||
export const mssqlClusterOpenNotebookTask = 'mssqlCluster.task.openNotebook';
|
||||
export const mssqlOpenClusterDashboard = 'mssqlCluster.task.openClusterDashboard';
|
||||
export const mssqlClusterLivySubmitSparkJobCommand = 'mssqlCluster.livy.cmd.submitSparkJob';
|
||||
export const mssqlClusterLivySubmitSparkJobFromFileCommand = 'mssqlCluster.livy.cmd.submitFileToSparkJob';
|
||||
export const mssqlClusterLivySubmitSparkJobTask = 'mssqlCluster.livy.task.submitSparkJob';
|
||||
export const mssqlClusterLivyOpenSparkHistory = 'mssqlCluster.livy.task.openSparkHistory';
|
||||
export const mssqlClusterLivyOpenYarnHistory = 'mssqlCluster.livy.task.openYarnHistory';
|
||||
export const mssqlClusterLivySubmitPath = '/gateway/default/livy/v1/batches';
|
||||
export const mssqlClusterLivyTimeInMSForCheckYarnApp = 1000;
|
||||
export const mssqlClusterLivyRetryTimesForCheckYarnApp = 20;
|
||||
export const mssqlClusterSparkJobFileSelectorButtonWidth = '30px';
|
||||
export const mssqlClusterSparkJobFileSelectorButtonHeight = '30px';
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import * as types from './types';
|
||||
import * as Constants from './constants';
|
||||
|
||||
enum BuiltInCommands {
|
||||
SetContext = 'setContext',
|
||||
@@ -16,7 +15,6 @@ enum BuiltInCommands {
|
||||
enum ContextKeys {
|
||||
ISCLOUD = 'mssql:iscloud',
|
||||
EDITIONID = 'mssql:engineedition',
|
||||
ISCLUSTER = 'mssql:iscluster',
|
||||
SERVERMAJORVERSION = 'mssql:servermajorversion'
|
||||
}
|
||||
|
||||
@@ -41,7 +39,6 @@ export default class ContextProvider {
|
||||
public onDashboardOpen(e: azdata.DashboardDocument): void {
|
||||
let iscloud: boolean;
|
||||
let edition: number;
|
||||
let isCluster: boolean = false;
|
||||
let serverMajorVersion: number;
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
|
||||
if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
|
||||
@@ -51,13 +48,6 @@ export default class ContextProvider {
|
||||
}
|
||||
|
||||
edition = e.serverInfo.engineEditionId;
|
||||
|
||||
if (!types.isUndefinedOrNull(e.serverInfo.options)) {
|
||||
let isBigDataCluster = e.serverInfo.options[Constants.isBigDataClusterProperty];
|
||||
if (isBigDataCluster) {
|
||||
isCluster = isBigDataCluster;
|
||||
}
|
||||
}
|
||||
serverMajorVersion = e.serverInfo.serverMajorVersion;
|
||||
}
|
||||
|
||||
@@ -69,10 +59,6 @@ export default class ContextProvider {
|
||||
void setCommandContext(ContextKeys.EDITIONID, edition);
|
||||
}
|
||||
|
||||
if (!types.isUndefinedOrNull(isCluster)) {
|
||||
void setCommandContext(ContextKeys.ISCLUSTER, isCluster);
|
||||
}
|
||||
|
||||
if (!types.isUndefinedOrNull(serverMajorVersion)) {
|
||||
void setCommandContext(ContextKeys.SERVERMAJORVERSION, serverMajorVersion);
|
||||
}
|
||||
|
||||
@@ -1,180 +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 azdata from 'azdata';
|
||||
import * as bdc from 'bdc';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as utils from '../utils';
|
||||
|
||||
const mgmtProxyName = 'mgmtproxy';
|
||||
const grafanaEndpointName = 'metricsui';
|
||||
const grafanaDescription = localize('grafana', "Metrics Dashboard");
|
||||
const logsuiEndpointName = 'logsui';
|
||||
const logsuiDescription = localize('kibana', "Log Search Dashboard");
|
||||
const sparkHistoryEndpointName = 'spark-history';
|
||||
const sparkHistoryDescription = localize('sparkHistory', "Spark Jobs Management and Monitoring Dashboard");
|
||||
const yarnUiEndpointName = 'yarn-ui';
|
||||
const yarnHistoryDescription = localize('yarnHistory', "Spark Diagnostics and Monitoring Dashboard");
|
||||
const hyperlinkedEndpoints = [grafanaEndpointName, logsuiEndpointName, sparkHistoryEndpointName, yarnUiEndpointName];
|
||||
|
||||
export function registerServiceEndpoints(context: vscode.ExtensionContext): void {
|
||||
azdata.ui.registerModelViewProvider('bdc-endpoints', async (view) => {
|
||||
let endpointsArray: Array<bdc.IEndpointModel> = Object.assign([], utils.getClusterEndpoints(view.serverInfo));
|
||||
|
||||
if (endpointsArray.length > 0) {
|
||||
const grafanaEp = endpointsArray.find(e => e.name === grafanaEndpointName);
|
||||
if (grafanaEp && grafanaEp.endpoint && grafanaEp.endpoint.indexOf('/d/wZx3OUdmz') === -1) {
|
||||
// Update to have correct URL
|
||||
grafanaEp.endpoint += '/d/wZx3OUdmz';
|
||||
}
|
||||
const kibanaEp = endpointsArray.find(e => e.name === logsuiEndpointName);
|
||||
if (kibanaEp && kibanaEp.endpoint && kibanaEp.endpoint.indexOf('/app/kibana#/discover') === -1) {
|
||||
// Update to have correct URL
|
||||
kibanaEp.endpoint += '/app/kibana#/discover';
|
||||
}
|
||||
|
||||
if (!grafanaEp) {
|
||||
// We are on older CTP, need to manually add some endpoints.
|
||||
// TODO remove once CTP support goes away
|
||||
const managementProxyEp = endpointsArray.find(e => e.name === mgmtProxyName);
|
||||
if (managementProxyEp) {
|
||||
endpointsArray.push(getCustomEndpoint(managementProxyEp, grafanaEndpointName, grafanaDescription, '/grafana/d/wZx3OUdmz'));
|
||||
endpointsArray.push(getCustomEndpoint(managementProxyEp, logsuiEndpointName, logsuiDescription, '/kibana/app/kibana#/discover'));
|
||||
}
|
||||
|
||||
const gatewayEp = endpointsArray.find(e => e.name === 'gateway');
|
||||
if (gatewayEp) {
|
||||
endpointsArray.push(getCustomEndpoint(gatewayEp, sparkHistoryEndpointName, sparkHistoryDescription, '/gateway/default/sparkhistory'));
|
||||
endpointsArray.push(getCustomEndpoint(gatewayEp, yarnUiEndpointName, yarnHistoryDescription, '/gateway/default/yarn'));
|
||||
}
|
||||
}
|
||||
|
||||
endpointsArray = endpointsArray.map(e => {
|
||||
e.description = getEndpointDisplayText(e.name, e.description);
|
||||
return e;
|
||||
});
|
||||
|
||||
// Sort the endpoints. The sort method is that SQL Server Master is first - followed by all
|
||||
// others in alphabetical order by endpoint
|
||||
const sqlServerMasterEndpoints = endpointsArray.filter(e => e.name === Endpoint.sqlServerMaster);
|
||||
endpointsArray = endpointsArray.filter(e => e.name !== Endpoint.sqlServerMaster)
|
||||
.sort((e1, e2) => e1.endpoint.localeCompare(e2.endpoint));
|
||||
endpointsArray.unshift(...sqlServerMasterEndpoints);
|
||||
|
||||
const container = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%' }).component();
|
||||
endpointsArray.forEach(endpointInfo => {
|
||||
const endPointRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
||||
const nameCell = view.modelBuilder.text().withProps({ value: endpointInfo.description }).component();
|
||||
endPointRow.addItem(nameCell, { CSSStyles: { 'width': '35%', 'font-weight': '600', 'user-select': 'text' } });
|
||||
if (hyperlinkedEndpoints.findIndex(e => e === endpointInfo.name) >= 0) {
|
||||
const linkCell = view.modelBuilder.hyperlink()
|
||||
.withProps({
|
||||
label: endpointInfo.endpoint,
|
||||
title: endpointInfo.endpoint,
|
||||
url: endpointInfo.endpoint
|
||||
}).component();
|
||||
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px', 'overflow': 'hidden', 'text-overflow': 'ellipsis' } });
|
||||
}
|
||||
else {
|
||||
const endpointCell =
|
||||
view.modelBuilder.text()
|
||||
.withProps(
|
||||
{
|
||||
value: endpointInfo.endpoint,
|
||||
title: endpointInfo.endpoint,
|
||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis' }
|
||||
})
|
||||
.component();
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': '62%', 'user-select': 'text' } });
|
||||
}
|
||||
const copyValueCell = view.modelBuilder.button().component();
|
||||
copyValueCell.iconPath = { light: context.asAbsolutePath('resources/light/copy.png'), dark: context.asAbsolutePath('resources/dark/copy_inverse.png') };
|
||||
copyValueCell.onDidClick(() => {
|
||||
void vscode.env.clipboard.writeText(endpointInfo.endpoint);
|
||||
});
|
||||
copyValueCell.title = localize("copyText", "Copy");
|
||||
copyValueCell.iconHeight = '14px';
|
||||
copyValueCell.iconWidth = '14px';
|
||||
endPointRow.addItem(copyValueCell, { CSSStyles: { 'width': '3%', 'padding-top': '10px' } });
|
||||
|
||||
container.addItem(endPointRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
|
||||
});
|
||||
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '540px', height: '100%', position: 'absolute' }).component();
|
||||
endpointsContainer.addItem(container, { CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' } });
|
||||
|
||||
await view.initializeModel(endpointsContainer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getCustomEndpoint(parentEndpoint: bdc.IEndpointModel, serviceName: string, description: string, serviceUrl?: string): bdc.IEndpointModel {
|
||||
if (parentEndpoint) {
|
||||
let endpoint: bdc.IEndpointModel = {
|
||||
name: serviceName,
|
||||
description: description,
|
||||
endpoint: parentEndpoint.endpoint + serviceUrl,
|
||||
protocol: 'https'
|
||||
};
|
||||
return endpoint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export enum Endpoint {
|
||||
gateway = 'gateway',
|
||||
sparkHistory = 'spark-history',
|
||||
yarnUi = 'yarn-ui',
|
||||
appProxy = 'app-proxy',
|
||||
mgmtproxy = 'mgmtproxy',
|
||||
managementProxy = 'management-proxy',
|
||||
logsui = 'logsui',
|
||||
metricsui = 'metricsui',
|
||||
controller = 'controller',
|
||||
sqlServerMaster = 'sql-server-master',
|
||||
webhdfs = 'webhdfs',
|
||||
livy = 'livy'
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localized text to display for a corresponding endpoint
|
||||
* @param endpointName The endpoint name to get the display text for
|
||||
* @param description The backup description to use if we don't have our own
|
||||
*/
|
||||
function getEndpointDisplayText(endpointName?: string, description?: string): string {
|
||||
endpointName = endpointName || '';
|
||||
switch (endpointName.toLowerCase()) {
|
||||
case Endpoint.appProxy:
|
||||
return localize('endpoint.appproxy', "Application Proxy");
|
||||
case Endpoint.controller:
|
||||
return localize('endpoint.controller', "Cluster Management Service");
|
||||
case Endpoint.gateway:
|
||||
return localize('endpoint.gateway', "Gateway to access HDFS files, Spark");
|
||||
case Endpoint.managementProxy:
|
||||
return localize('endpoint.managementproxy', "Management Proxy");
|
||||
case Endpoint.mgmtproxy:
|
||||
return localize('endpoint.mgmtproxy', "Management Proxy");
|
||||
case Endpoint.sqlServerMaster:
|
||||
return localize('endpoint.sqlServerEndpoint', "SQL Server Master Instance Front-End");
|
||||
case Endpoint.metricsui:
|
||||
return localize('endpoint.grafana', "Metrics Dashboard");
|
||||
case Endpoint.logsui:
|
||||
return localize('endpoint.kibana', "Log Search Dashboard");
|
||||
case Endpoint.yarnUi:
|
||||
return localize('endpoint.yarnHistory', "Spark Diagnostics and Monitoring Dashboard");
|
||||
case Endpoint.sparkHistory:
|
||||
return localize('endpoint.sparkHistory', "Spark Jobs Management and Monitoring Dashboard");
|
||||
case Endpoint.webhdfs:
|
||||
return localize('endpoint.webhdfs', "HDFS File System Proxy");
|
||||
case Endpoint.livy:
|
||||
return localize('endpoint.livy', "Proxy for running Spark statements, jobs, applications");
|
||||
default:
|
||||
// Default is to use the description if one was given, otherwise worst case just fall back to using the
|
||||
// original endpoint name
|
||||
return description && description.length > 0 ? description : endpointName;
|
||||
}
|
||||
}
|
||||
@@ -1,384 +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 { IconPathHelper, IconPath } from '../iconHelper';
|
||||
import { groupBy } from '../util/arrays';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
/**
|
||||
* The permission status of an HDFS path - this consists of :
|
||||
* - The sticky bit for that path
|
||||
* - The permission bits for the owner, group and other
|
||||
* - (Optional) Set of additional ACL entries on this path
|
||||
*/
|
||||
export class PermissionStatus {
|
||||
/**
|
||||
*
|
||||
* @param owner The ACL entry object for the owner permissions
|
||||
* @param group The ACL entry object for the group permissions
|
||||
* @param other The ACL entry object for the other permissions
|
||||
* @param stickyBit The sticky bit status for the object. If true the owner/root are
|
||||
* the only ones who can delete the resource or its contents (if a folder)
|
||||
* @param aclEntries The ACL entries defined for the object
|
||||
*/
|
||||
constructor(public owner: AclEntry, public group: AclEntry, public other: AclEntry, public stickyBit: boolean, public aclEntries: AclEntry[]) { }
|
||||
|
||||
/**
|
||||
* The permission octal for the path in the form [#]### with each # mapping to :
|
||||
* 0 (optional) - The sticky bit (1 or 0)
|
||||
* 1 - The owner permission digit
|
||||
* 2 - The group permission digit
|
||||
* 3 - The other permission digit
|
||||
* @see AclEntryPermission for more information on the permission digits
|
||||
*/
|
||||
public get permissionOctal(): string {
|
||||
// Always use the access scope for the permission octal - it doesn't have a concept of other scopes
|
||||
return `${this.stickyBit ? '1' : ''}${this.owner.getPermissionDigit(AclEntryScope.access)}${this.group.getPermissionDigit(AclEntryScope.access)}${this.other.getPermissionDigit(AclEntryScope.access)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of an ACL entry. Corresponds to the first (or second if a scope is present) field of
|
||||
* an ACL entry - e.g. user:bob:rwx (user) or default:group::r-- (group)
|
||||
*/
|
||||
export enum AclType {
|
||||
/**
|
||||
* An ACL entry applied to a specific user.
|
||||
*/
|
||||
user = 'user',
|
||||
/**
|
||||
* An ACL entry applied to a specific group.
|
||||
*/
|
||||
group = 'group',
|
||||
/**
|
||||
* An ACL mask entry.
|
||||
*/
|
||||
mask = 'mask',
|
||||
/**
|
||||
* An ACL entry that applies to all other users that were not covered by one of the more specific ACL entry types.
|
||||
*/
|
||||
other = 'other'
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of permission on a file - this corresponds to the field in the file status used in commands such as chmod.
|
||||
* Typically this value is represented as a 3 digit octal - e.g. 740 - where the first digit is the owner, the second
|
||||
* the group and the third other. @see parseAclPermissionFromOctal
|
||||
*/
|
||||
export enum PermissionType {
|
||||
owner = 'owner',
|
||||
group = 'group',
|
||||
other = 'other'
|
||||
}
|
||||
|
||||
export enum AclEntryScope {
|
||||
/**
|
||||
* An ACL entry that is inspected during permission checks to enforce permissions.
|
||||
*/
|
||||
access = 'access',
|
||||
/**
|
||||
* An ACL entry to be applied to a directory's children that do not otherwise have their own ACL defined.
|
||||
*/
|
||||
default = 'default'
|
||||
}
|
||||
|
||||
/**
|
||||
* The read, write and execute permissions for an ACL
|
||||
*/
|
||||
export class AclEntryPermission {
|
||||
|
||||
constructor(public read: boolean, public write: boolean, public execute: boolean) { }
|
||||
|
||||
/**
|
||||
* Returns the string representation of the permissions in the form [r-][w-][x-].
|
||||
* e.g.
|
||||
* rwx
|
||||
* r--
|
||||
* ---
|
||||
*/
|
||||
public toString() {
|
||||
return `${this.read ? 'r' : '-'}${this.write ? 'w' : '-'}${this.execute ? 'x' : '-'}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the digit for a permission octal for this permission. This digit is a value
|
||||
* between 0 and 7 inclusive, which is a bitwise OR the permission flags (r/w/x).
|
||||
*/
|
||||
public get permissionDigit(): number {
|
||||
return (this.read ? 4 : 0) + (this.write ? 2 : 0) + (this.execute ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string representation of a permission into an AclPermission object. The string must consist
|
||||
* of 3 characters for the read, write and execute permissions where each character is either a r/w/x or
|
||||
* a -.
|
||||
* e.g. The following are all valid strings
|
||||
* rwx
|
||||
* ---
|
||||
* -w-
|
||||
* @param permissionString The string representation of the permission
|
||||
*/
|
||||
function parseAclPermission(permissionString: string): AclEntryPermission {
|
||||
permissionString = permissionString.toLowerCase();
|
||||
if (!/^[r\-][w\-][x\-]$/i.test(permissionString)) {
|
||||
throw new Error(`Invalid permission string ${permissionString}- must match /^[r\-][w\-][x\-]$/i`);
|
||||
}
|
||||
return new AclEntryPermission(permissionString[0] === 'r', permissionString[1] === 'w', permissionString[2] === 'x');
|
||||
}
|
||||
|
||||
/**
|
||||
* A single ACL Permission entry
|
||||
* scope - The scope of the entry @see AclEntryScope
|
||||
* type - The type of the entry @see AclEntryType
|
||||
* name - The name of the user/group used to set ACLs Optional.
|
||||
* displayName - The name to display in the UI
|
||||
* permission - The permission set for this ACL. @see AclPermission
|
||||
*/
|
||||
export class AclEntry {
|
||||
private readonly permissions = new Map<AclEntryScope, AclEntryPermission>();
|
||||
|
||||
constructor(
|
||||
public readonly type: AclType | PermissionType,
|
||||
public readonly name: string,
|
||||
public readonly displayName: string,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Adds a new permission at the specified scope, overwriting the existing permission at that scope if it
|
||||
* exists
|
||||
* @param scope The scope to add the new permission at
|
||||
* @param permission The permission to set
|
||||
*/
|
||||
public addPermission(scope: AclEntryScope, permission: AclEntryPermission): void {
|
||||
this.permissions.set(scope, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the permission at the specified scope.
|
||||
* @param scope The scope to delete the permission for
|
||||
* @returns True if the entry was successfully deleted, false if not (it didn't exist)
|
||||
*/
|
||||
public removePermission(scope: AclEntryScope): boolean {
|
||||
return this.permissions.delete(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission at the specified scope if one exists
|
||||
* @param scope The scope to retrieve the permission for
|
||||
*/
|
||||
public getPermission(scope: AclEntryScope): AclEntryPermission | undefined {
|
||||
return this.permissions.get(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full list of permissions and their scopes for this entry
|
||||
*/
|
||||
public getAllPermissions(): { scope: AclEntryScope, permission: AclEntryPermission }[] {
|
||||
return Array.from(this.permissions.entries()).map((entry: [AclEntryScope, AclEntryPermission]) => {
|
||||
return { scope: entry[0], permission: entry[1] };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the octal number representing the permission for the specified scope of
|
||||
* this entry. This will either be a number between 0 and 7 inclusive (which is
|
||||
* a bitwise OR the permission flags rwx) or undefined if the scope doesn't exist
|
||||
* for this entry.
|
||||
*/
|
||||
public getPermissionDigit(scope: AclEntryScope): number | undefined {
|
||||
return this.permissions.has(scope) ? this.permissions.get(scope).permissionDigit : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of each ACL Entry in the form [SCOPE:]TYPE:NAME:PERMISSION.
|
||||
* Note that SCOPE is only displayed if it's default - access is implied if there is no scope
|
||||
* specified.
|
||||
* The name is optional and so may be empty.
|
||||
* Example strings :
|
||||
* user:bob:rwx
|
||||
* default:user:bob:rwx
|
||||
* user::r-x
|
||||
* default:group::r--
|
||||
*/
|
||||
toAclStrings(includeDefaults: boolean = true): string[] {
|
||||
return Array.from(this.permissions.entries()).filter((entry: [AclEntryScope, AclEntryPermission]) => includeDefaults || entry[0] !== AclEntryScope.default).map((entry: [AclEntryScope, AclEntryPermission]) => {
|
||||
return `${entry[0] === AclEntryScope.default ? 'default:' : ''}${getAclEntryType(this.type)}:${this.name}:${entry[1].toString()}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this and the specified AclEntry are equal. Two entries are considered equal
|
||||
* if their scope, type and name are equal.
|
||||
* @param other The other entry to compare against
|
||||
*/
|
||||
public isEqual(other: AclEntry): boolean {
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return AclEntry.compare(this, other) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two AclEntry objects for ordering
|
||||
* @param a The first AclEntry to compare
|
||||
* @param b The second AclEntry to compare
|
||||
*/
|
||||
static compare(a: AclEntry, b: AclEntry): number {
|
||||
if (a.name === b.name) {
|
||||
if (a.type === b.type) {
|
||||
return 0;
|
||||
}
|
||||
return a.type.localeCompare(b.type);
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the possible entry types into their corresponding values for using in an ACL string
|
||||
* @param type The type to convert
|
||||
*/
|
||||
function getAclEntryType(type: AclType | PermissionType): AclType {
|
||||
// We only need to map AclPermissionType - AclEntryType is already the
|
||||
// correct values we're mapping to.
|
||||
if (type in PermissionType) {
|
||||
switch (type) {
|
||||
case PermissionType.owner:
|
||||
return AclType.user;
|
||||
case PermissionType.group:
|
||||
return AclType.group;
|
||||
case PermissionType.other:
|
||||
return AclType.other;
|
||||
default:
|
||||
throw new Error(`Unknown AclPermissionType : ${type}`);
|
||||
}
|
||||
}
|
||||
return <AclType>type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a complete ACL string into separate AclEntry objects for each entry. A valid string consists of multiple entries
|
||||
* separated by a comma.
|
||||
*
|
||||
* A valid entry must match (default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})
|
||||
* e.g. the following are all valid entries
|
||||
* user:bob:rwx
|
||||
* user::rwx
|
||||
* default::bob:rwx
|
||||
* group::r-x
|
||||
* default:other:r--
|
||||
*
|
||||
* So a valid ACL string might look like this
|
||||
* user:bob:rwx,user::rwx,default::bob:rwx,group::r-x,default:other:r--
|
||||
* @param aclString The string representation of the ACL
|
||||
*/
|
||||
export function parseAclList(aclString: string): AclEntry[] {
|
||||
if (aclString === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!/^(default:)?(user|group|mask|other):([A-Za-z_][A-Za-z0-9._-]*)?:([rwx-]{3})?(,(default:)?(user|group|mask|other):([A-Za-z_][A-Za-z0-9._-]*)?:([rwx-]{3})?)*$/.test(aclString)) {
|
||||
throw new Error(`Invalid ACL string ${aclString}. Expected to match ^(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?(,(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?)*$`);
|
||||
}
|
||||
return mergeAclEntries(aclString.split(',').map(aclEntryString => parseAclEntry(aclEntryString)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a given string representation of an ACL Entry into an AclEntry object. This method
|
||||
* assumes the string has already been checked for validity.
|
||||
* @param aclString The string representation of the ACL entry
|
||||
*/
|
||||
function parseAclEntry(aclString: string): AclEntry {
|
||||
const parts: string[] = aclString.split(':');
|
||||
let i = 0;
|
||||
const scope: AclEntryScope = parts.length === 4 && parts[i++] === 'default' ? AclEntryScope.default : AclEntryScope.access;
|
||||
let type: AclType;
|
||||
switch (parts[i++]) {
|
||||
case 'user':
|
||||
type = AclType.user;
|
||||
break;
|
||||
case 'group':
|
||||
type = AclType.group;
|
||||
break;
|
||||
case 'mask':
|
||||
type = AclType.mask;
|
||||
break;
|
||||
case 'other':
|
||||
type = AclType.other;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown ACL Entry type ${parts[i - 1]}`);
|
||||
}
|
||||
const name = parts[i++];
|
||||
const permission = parseAclPermission(parts[i++]);
|
||||
const entry = new AclEntry(type, name, name);
|
||||
entry.addPermission(scope, permission);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an octal in the form [#]### into a combination of an optional sticky bit and a set
|
||||
* of @see AclEntryPermission. Each digit in the octal corresponds to the sticky bit or a
|
||||
* particular user type - owner, group and other respectively.
|
||||
* If the sticky bit exists and its value is 1 then the sticky bit value is set to true.
|
||||
* Each permission digit is then expected to be a value between 0 and 7 inclusive, which is a bitwise OR the permission flags
|
||||
* for the file.
|
||||
* 4 - Read
|
||||
* 2 - Write
|
||||
* 1 - Execute
|
||||
* So an octal of 1730 would map to :
|
||||
* - sticky === true
|
||||
* - The owner with rwx permissions
|
||||
* - The group with -wx permissions
|
||||
* - All others with --- permissions
|
||||
* @param octal The octal string to parse
|
||||
*/
|
||||
export function parseAclPermissionFromOctal(octal: string): { sticky: boolean, owner: AclEntryPermission, group: AclEntryPermission, other: AclEntryPermission } {
|
||||
if (!octal || (octal.length !== 3 && octal.length !== 4)) {
|
||||
throw new Error(`Invalid octal ${octal} - it must be a 3 or 4 digit string`);
|
||||
}
|
||||
|
||||
const sticky = octal.length === 4 ? octal[0] === '1' : false;
|
||||
const ownerPermissionDigit = parseInt(octal[octal.length - 3]);
|
||||
const groupPermissionDigit = parseInt(octal[octal.length - 2]);
|
||||
const otherPermissionDigit = parseInt(octal[octal.length - 1]);
|
||||
|
||||
return {
|
||||
sticky: sticky,
|
||||
owner: new AclEntryPermission((ownerPermissionDigit & 4) === 4, (ownerPermissionDigit & 2) === 2, (ownerPermissionDigit & 1) === 1),
|
||||
group: new AclEntryPermission((groupPermissionDigit & 4) === 4, (groupPermissionDigit & 2) === 2, (groupPermissionDigit & 1) === 1),
|
||||
other: new AclEntryPermission((otherPermissionDigit & 4) === 4, (otherPermissionDigit & 2) === 2, (otherPermissionDigit & 1) === 1)
|
||||
};
|
||||
}
|
||||
|
||||
export function getImageForType(type: AclType | PermissionType): { iconPath: IconPath, title: string } {
|
||||
switch (type) {
|
||||
case AclType.user:
|
||||
case PermissionType.owner:
|
||||
return { iconPath: IconPathHelper.user, title: loc.owner };
|
||||
case AclType.group:
|
||||
case PermissionType.group:
|
||||
case PermissionType.other:
|
||||
return { iconPath: IconPathHelper.group, title: loc.group };
|
||||
}
|
||||
return { iconPath: { dark: '', light: '' }, title: '' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a list of AclEntry objects such that the resulting list contains only a single entry for each name/type pair with
|
||||
* a separate permission for each separate AclEntry
|
||||
* @param entries The set of AclEntries to merge
|
||||
*/
|
||||
function mergeAclEntries(entries: AclEntry[]): AclEntry[] {
|
||||
const groupedEntries = groupBy(entries, (a, b) => AclEntry.compare(a, b)); // First group the entries together
|
||||
return groupedEntries.map(entryGroup => { // Now make a single AclEntry for each group and add all the permissions from each group
|
||||
const entry = new AclEntry(entryGroup[0].type, entryGroup[0].name, entryGroup[0].displayName);
|
||||
entryGroup.forEach(e => {
|
||||
e.getAllPermissions().forEach(sp => entry.addPermission(sp.scope, sp.permission));
|
||||
});
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
@@ -1,112 +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 { FileType } from '../objectExplorerNodeProvider/fileSources';
|
||||
|
||||
export const enum HdfsFileType {
|
||||
File = 'File',
|
||||
Directory = 'Directory',
|
||||
Symlink = 'Symlink'
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a @see HdfsFileType to its corresponding @see FileType. Will return undefined if
|
||||
* passed in type is undefined.
|
||||
* @param hdfsFileType The HdfsFileType to map from
|
||||
*/
|
||||
export function hdfsFileTypeToFileType(hdfsFileType: HdfsFileType | undefined): FileType | undefined {
|
||||
switch (hdfsFileType) {
|
||||
case HdfsFileType.Directory:
|
||||
return FileType.Directory;
|
||||
case HdfsFileType.File:
|
||||
return FileType.File;
|
||||
case HdfsFileType.Symlink:
|
||||
return FileType.Symlink;
|
||||
case undefined:
|
||||
return undefined;
|
||||
default:
|
||||
throw new Error(`Unexpected file type ${hdfsFileType}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStatus {
|
||||
/**
|
||||
*
|
||||
* @param accessTime
|
||||
* @param blockSize
|
||||
* @param group The ACL entry object for the group permissions
|
||||
* @param length
|
||||
* @param modificationTime
|
||||
* @param owner The ACL entry object for the owner permissions
|
||||
* @param pathSuffix
|
||||
* @param permission
|
||||
* @param replication
|
||||
* @param snapshotEnabled
|
||||
* @param type
|
||||
*/
|
||||
constructor(
|
||||
/**
|
||||
* Access time for the file
|
||||
*/
|
||||
public readonly accessTime: string,
|
||||
/**
|
||||
* The block size of a file.
|
||||
*/
|
||||
public readonly blockSize: string,
|
||||
/**
|
||||
* The group owner.
|
||||
*/
|
||||
public readonly group: string,
|
||||
/**
|
||||
* The number of bytes in a file. (0 for directories)
|
||||
*/
|
||||
public readonly length: string,
|
||||
/**
|
||||
* The modification time.
|
||||
*/
|
||||
public readonly modificationTime: string,
|
||||
/**
|
||||
* The user who is the owner.
|
||||
*/
|
||||
public readonly owner: string,
|
||||
/**
|
||||
* The path suffix.
|
||||
*/
|
||||
public readonly pathSuffix: string,
|
||||
/**
|
||||
* The permission represented as a octal string.
|
||||
*/
|
||||
public readonly permission: string,
|
||||
/**
|
||||
* The number of replication of a file.
|
||||
*/
|
||||
public readonly replication: string,
|
||||
/**
|
||||
* Whether a directory is snapshot enabled or not
|
||||
*/
|
||||
public readonly snapshotEnabled: string,
|
||||
/**
|
||||
* The type of the path object.
|
||||
*/
|
||||
public readonly type: HdfsFileType
|
||||
) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a fileType string into the corresponding @see HdfsFileType
|
||||
* @param fileType The fileType string to parse
|
||||
*/
|
||||
export function parseHdfsFileType(fileType: string): HdfsFileType {
|
||||
switch (fileType.toLowerCase()) {
|
||||
case 'file':
|
||||
return HdfsFileType.File;
|
||||
case 'directory':
|
||||
return HdfsFileType.Directory;
|
||||
case 'symlink':
|
||||
return HdfsFileType.Symlink;
|
||||
default:
|
||||
throw new Error(`Unknown HdfsFileType '${fileType}'`);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +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 * as vscode from 'vscode';
|
||||
import { IFileSource, FileType } from '../objectExplorerNodeProvider/fileSources';
|
||||
import { PermissionStatus, AclEntry, AclEntryScope, AclType, AclEntryPermission } from './aclEntry';
|
||||
import { FileStatus, hdfsFileTypeToFileType } from './fileStatus';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* Model for storing the state of a specified file/folder in HDFS
|
||||
*/
|
||||
export class HdfsModel {
|
||||
|
||||
private readonly _onPermissionStatusUpdated = new vscode.EventEmitter<PermissionStatus>();
|
||||
/**
|
||||
* Event that's fired anytime changes are made by the model to the @see PermissionStatus
|
||||
*/
|
||||
public onPermissionStatusUpdated = this._onPermissionStatusUpdated.event;
|
||||
|
||||
/**
|
||||
* The @see PermissionStatus of the file/folder
|
||||
*/
|
||||
public permissionStatus: PermissionStatus;
|
||||
|
||||
/**
|
||||
* The @see FileStatus of the file/folder
|
||||
*/
|
||||
public fileStatus: FileStatus;
|
||||
|
||||
constructor(private readonly fileSource: IFileSource, private readonly path: string) {
|
||||
this.refresh().catch(err => console.error('Error refreshing HDFS Model ', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the ACL status with the current values on HDFS
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
[this.permissionStatus, this.fileStatus] = await Promise.all([
|
||||
this.fileSource.getAclStatus(this.path),
|
||||
this.fileSource.getFileStatus(this.path)]);
|
||||
this._onPermissionStatusUpdated.fire(this.permissionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ACL Entry and adds it to the list of current entries. Will do nothing
|
||||
* if a duplicate entry (@see AclEntry.isEqual) exists
|
||||
* @param name The name of the ACL Entry
|
||||
* @param type The type of ACL to create
|
||||
*/
|
||||
public createAndAddAclEntry(name: string, type: AclType): void {
|
||||
if (!this.permissionStatus || !name || name.length < 1) {
|
||||
return;
|
||||
}
|
||||
const newEntry = new AclEntry(type, name, name);
|
||||
newEntry.addPermission(AclEntryScope.access, new AclEntryPermission(true, true, true));
|
||||
// Don't add duplicates. This also checks the owner, group and other items
|
||||
if ([this.permissionStatus.owner, this.permissionStatus.group, this.permissionStatus.other].concat(this.permissionStatus.aclEntries).find(entry => entry.isEqual(newEntry))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.permissionStatus.aclEntries.push(newEntry);
|
||||
this._onPermissionStatusUpdated.fire(this.permissionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified entry from the list of registered
|
||||
* @param entryToDelete The entry to delete
|
||||
*/
|
||||
public deleteAclEntry(entryToDelete: AclEntry): void {
|
||||
this.permissionStatus.aclEntries = this.permissionStatus.aclEntries.filter(entry => !entry.isEqual(entryToDelete));
|
||||
this._onPermissionStatusUpdated.fire(this.permissionStatus);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies the changes made to this model to HDFS. Note that this will overwrite ALL permissions so any
|
||||
* permissions that shouldn't change need to still exist and have the same values.
|
||||
* @param recursive Whether to apply the changes recursively (to all sub-folders and files)
|
||||
*/
|
||||
public async apply(recursive: boolean = false): Promise<void> {
|
||||
await this.applyAclChanges(this.path, hdfsFileTypeToFileType(this.fileStatus ? this.fileStatus.type : undefined));
|
||||
if (recursive) {
|
||||
azdata.tasks.startBackgroundOperation(
|
||||
{
|
||||
connection: undefined,
|
||||
displayName: localize('mssql.recursivePermissionOpStarted', "Applying permission changes recursively under '{0}'", this.path),
|
||||
description: '',
|
||||
isCancelable: false,
|
||||
operation: async op => {
|
||||
await this.applyToChildrenRecursive(op, this.path);
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded, localize('mssql.recursivePermissionOpSucceeded', "Permission changes applied successfully."));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive call to apply the current set of changes to all children of this path (if any)
|
||||
* @param op Background operation used to track status of the task
|
||||
* @param path The path
|
||||
*/
|
||||
private async applyToChildrenRecursive(op: azdata.BackgroundOperation, path: string): Promise<void> {
|
||||
try {
|
||||
op.updateStatus(azdata.TaskStatus.InProgress, localize('mssql.recursivePermissionOpProgress', "Applying permission changes to '{0}'.", path));
|
||||
const files = await this.fileSource.enumerateFiles(path, true);
|
||||
// Apply changes to all children of this path and then recursively apply to children of any directories
|
||||
await Promise.all(
|
||||
files.map(file => this.applyAclChanges(file.path, file.fileType)).concat(
|
||||
files.filter(f => f.fileType === FileType.Directory).map(d => this.applyToChildrenRecursive(op, d.path)))
|
||||
);
|
||||
} catch (error) {
|
||||
const errMsg = localize('mssql.recursivePermissionOpError', "Error applying permission changes: {0}", (error instanceof Error ? error.message : error));
|
||||
void vscode.window.showErrorMessage(errMsg);
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the current set of Permissions/ACLs to the specified path
|
||||
* @param path The path to apply the changes to
|
||||
*/
|
||||
private async applyAclChanges(path: string, fileType: FileType | undefined): Promise<any> {
|
||||
// HDFS won't remove existing default ACLs even if you call setAcl with no default ACLs specified. You
|
||||
// need to call removeDefaultAcl specifically to remove them.
|
||||
if (!this.permissionStatus.owner.getPermission(AclEntryScope.default) &&
|
||||
!this.permissionStatus.group.getPermission(AclEntryScope.default) &&
|
||||
!this.permissionStatus.other.getPermission(AclEntryScope.default)) {
|
||||
await this.fileSource.removeDefaultAcl(path);
|
||||
}
|
||||
return Promise.all([
|
||||
this.fileSource.setAcl(path, fileType, this.permissionStatus),
|
||||
this.fileSource.setPermission(path, this.permissionStatus)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Information about a HDFS mount to a remote directory
|
||||
*/
|
||||
export interface Mount {
|
||||
mountPath: string;
|
||||
mountStatus: string;
|
||||
remotePath: string;
|
||||
}
|
||||
|
||||
export enum MountStatus {
|
||||
None = 0,
|
||||
Mount = 1,
|
||||
Mount_Child = 2
|
||||
}
|
||||
@@ -1,641 +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 * as vscode from 'vscode';
|
||||
import { HdfsModel } from '../hdfsModel';
|
||||
import { IFileSource } from '../../objectExplorerNodeProvider/fileSources';
|
||||
import { PermissionStatus, AclEntry, AclType, getImageForType, AclEntryScope, AclEntryPermission, PermissionType } from '../../hdfs/aclEntry';
|
||||
import { cssStyles } from './uiConstants';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import { HdfsError } from '../webhdfs';
|
||||
import { IconPathHelper } from '../../iconHelper';
|
||||
import { HdfsFileType } from '../fileStatus';
|
||||
|
||||
const permissionsTypeIconColumnWidth = 35;
|
||||
const permissionsDeleteColumnWidth = 50;
|
||||
|
||||
const permissionsCheckboxColumnWidth = 50;
|
||||
|
||||
const permissionsRowHeight = 35;
|
||||
const locationLabelHeight = 23; // Fits the text size without too much white space
|
||||
|
||||
const checkboxSize = 20;
|
||||
|
||||
|
||||
type PermissionCheckboxesMapping = {
|
||||
model: AclEntry,
|
||||
access: { read: azdata.CheckBoxComponent, write: azdata.CheckBoxComponent, execute: azdata.CheckBoxComponent },
|
||||
default: { read: azdata.CheckBoxComponent, write: azdata.CheckBoxComponent, execute: azdata.CheckBoxComponent }
|
||||
};
|
||||
|
||||
export class ManageAccessDialog {
|
||||
|
||||
private hdfsModel: HdfsModel;
|
||||
private viewInitialized: boolean = false;
|
||||
private modelInitialized: boolean = false;
|
||||
private modelBuilder: azdata.ModelBuilder;
|
||||
private rootContainer: azdata.FlexContainer;
|
||||
private rootLoadingComponent: azdata.LoadingComponent;
|
||||
private stickyCheckbox: azdata.CheckBoxComponent;
|
||||
private inheritDefaultsCheckbox: azdata.CheckBoxComponent;
|
||||
private posixPermissionsContainer: azdata.FlexContainer;
|
||||
private namedUsersAndGroupsPermissionsContainer: azdata.FlexContainer;
|
||||
private addUserOrGroupInput: azdata.InputBoxComponent;
|
||||
private dialog: azdata.window.Dialog;
|
||||
private applyRecursivelyButton: azdata.window.Button;
|
||||
private posixPermissionCheckboxesMapping: PermissionCheckboxesMapping[] = [];
|
||||
private namedSectionInheritCheckboxes: azdata.CheckBoxComponent[] = [];
|
||||
private addUserOrGroupSelectedType: AclType;
|
||||
private onViewInitializedEvent: vscode.EventEmitter<void> = new vscode.EventEmitter();
|
||||
|
||||
constructor(private hdfsPath: string, private fileSource: IFileSource) {
|
||||
this.hdfsModel = new HdfsModel(this.fileSource, this.hdfsPath);
|
||||
this.hdfsModel.onPermissionStatusUpdated(permissionStatus => this.handlePermissionStatusUpdated(permissionStatus));
|
||||
}
|
||||
|
||||
public openDialog(): void {
|
||||
if (!this.dialog) {
|
||||
this.dialog = azdata.window.createModelViewDialog(loc.manageAccessTitle, 'HdfsManageAccess', true);
|
||||
this.dialog.okButton.label = loc.applyText;
|
||||
|
||||
this.applyRecursivelyButton = azdata.window.createButton(loc.applyRecursivelyText);
|
||||
this.applyRecursivelyButton.onClick(async () => {
|
||||
try {
|
||||
azdata.window.closeDialog(this.dialog);
|
||||
await this.hdfsModel.apply(true);
|
||||
} catch (err) {
|
||||
void vscode.window.showErrorMessage(loc.errorApplyingAclChanges(err instanceof HdfsError ? err.message : err));
|
||||
}
|
||||
});
|
||||
this.dialog.customButtons = [this.applyRecursivelyButton];
|
||||
this.dialog.registerCloseValidator(async (): Promise<boolean> => {
|
||||
try {
|
||||
await this.hdfsModel.apply();
|
||||
return true;
|
||||
} catch (err) {
|
||||
void vscode.window.showErrorMessage(loc.errorApplyingAclChanges(err instanceof HdfsError ? err.message : err));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const tab = azdata.window.createTab(loc.manageAccessTitle);
|
||||
tab.registerContent(async (modelView: azdata.ModelView) => {
|
||||
this.modelBuilder = modelView.modelBuilder;
|
||||
|
||||
this.rootContainer = modelView.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column', width: '100%', height: '100%' })
|
||||
.component();
|
||||
|
||||
this.rootLoadingComponent = modelView.modelBuilder.loadingComponent().withItem(this.rootContainer).component();
|
||||
|
||||
await modelView.initializeModel(this.rootLoadingComponent);
|
||||
this.modelInitialized = true;
|
||||
this.handlePermissionStatusUpdated(this.hdfsModel.permissionStatus);
|
||||
});
|
||||
this.dialog.content = [tab];
|
||||
}
|
||||
|
||||
this.applyRecursivelyButton.hidden = true; // Always hide the button until we get the status back saying whether this is a directory or not
|
||||
azdata.window.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeView(permissionStatus: PermissionStatus): void {
|
||||
// We nest the content inside another container for the margins - getting them on the root container isn't supported
|
||||
const contentContainer = this.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column', width: '100%', height: '100%' })
|
||||
.component();
|
||||
this.rootContainer.addItem(contentContainer, { CSSStyles: { 'margin-left': '20px', 'margin-right': '20px' } });
|
||||
|
||||
const locationContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
|
||||
const locationLabel = this.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.locationTitle,
|
||||
CSSStyles: { ...cssStyles.titleCss }
|
||||
}).component();
|
||||
|
||||
const pathLabel = this.modelBuilder.text()
|
||||
.withProps({
|
||||
value: this.hdfsPath,
|
||||
title: this.hdfsPath,
|
||||
height: locationLabelHeight,
|
||||
CSSStyles: { 'user-select': 'text', 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.titleCss }
|
||||
}).component();
|
||||
|
||||
locationContainer.addItem(locationLabel,
|
||||
{
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: { 'margin-bottom': '5px' }
|
||||
});
|
||||
locationContainer.addItem(pathLabel,
|
||||
{
|
||||
flex: '1 1 auto',
|
||||
CSSStyles: { 'border': '1px solid #ccc', 'padding': '5px', 'margin-left': '10px', 'min-height': `${locationLabelHeight}px` }
|
||||
});
|
||||
|
||||
contentContainer.addItem(locationContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '20px' } });
|
||||
|
||||
// =====================
|
||||
// = Permissions Title =
|
||||
// =====================
|
||||
const permissionsTitle = this.modelBuilder.text()
|
||||
.withProps({ value: loc.permissionsHeader })
|
||||
.component();
|
||||
contentContainer.addItem(permissionsTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } });
|
||||
|
||||
// ====================
|
||||
// = Inherit Defaults =
|
||||
// ====================
|
||||
|
||||
// Defaults are only settable for directories
|
||||
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
|
||||
contentContainer.addItem(this.createInheritDefaultsCheckbox());
|
||||
}
|
||||
|
||||
// ==========
|
||||
// = Sticky =
|
||||
// ==========
|
||||
this.stickyCheckbox = this.modelBuilder.checkBox()
|
||||
.withProps({
|
||||
width: checkboxSize,
|
||||
height: checkboxSize,
|
||||
checked: permissionStatus.stickyBit,
|
||||
label: loc.stickyLabel
|
||||
}).component();
|
||||
this.stickyCheckbox.onChanged(() => {
|
||||
this.hdfsModel.permissionStatus.stickyBit = this.stickyCheckbox.checked;
|
||||
});
|
||||
contentContainer.addItem(this.stickyCheckbox);
|
||||
|
||||
// =============================
|
||||
// = POSIX permissions section =
|
||||
// =============================
|
||||
|
||||
const posixPermissionsSectionHeaderRow = this.createPermissionsSectionHeaderRow(0, 0);
|
||||
contentContainer.addItem(posixPermissionsSectionHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
|
||||
|
||||
this.posixPermissionsContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
contentContainer.addItem(this.posixPermissionsContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } });
|
||||
|
||||
// ===========================
|
||||
// = Add User Or Group Input =
|
||||
// ===========================
|
||||
|
||||
const addUserOrGroupTitle = this.modelBuilder.text()
|
||||
.withProps({ value: loc.addUserOrGroupHeader, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } })
|
||||
.component();
|
||||
contentContainer.addItem(addUserOrGroupTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } });
|
||||
|
||||
const typeContainer = this.modelBuilder.flexContainer().component();
|
||||
const aclEntryTypeGroup = 'aclEntryType';
|
||||
const userTypeButton = this.createRadioButton(this.modelBuilder, loc.userLabel, aclEntryTypeGroup, AclType.user);
|
||||
const groupTypeButton = this.createRadioButton(this.modelBuilder, loc.groupLabel, aclEntryTypeGroup, AclType.group);
|
||||
userTypeButton.checked = true;
|
||||
this.addUserOrGroupSelectedType = AclType.user;
|
||||
|
||||
typeContainer.addItems([userTypeButton, groupTypeButton], { flex: '0 0 auto' });
|
||||
contentContainer.addItem(typeContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '5px' } });
|
||||
const addUserOrGroupInputRow = this.modelBuilder.flexContainer().component();
|
||||
|
||||
this.addUserOrGroupInput = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
inputType: 'text',
|
||||
placeHolder: loc.enterNamePlaceholder,
|
||||
width: 250,
|
||||
stopEnterPropagation: true
|
||||
})
|
||||
.component();
|
||||
this.addUserOrGroupInput.onEnterKeyPressed((value: string) => {
|
||||
this.hdfsModel.createAndAddAclEntry(value, this.addUserOrGroupSelectedType);
|
||||
this.addUserOrGroupInput.value = '';
|
||||
});
|
||||
const addUserOrGroupButton = this.modelBuilder.button().withProps({
|
||||
label: loc.addLabel,
|
||||
width: 75,
|
||||
secondary: true
|
||||
}).component();
|
||||
addUserOrGroupButton.onDidClick(() => {
|
||||
this.hdfsModel.createAndAddAclEntry(this.addUserOrGroupInput.value, this.addUserOrGroupSelectedType);
|
||||
this.addUserOrGroupInput.value = '';
|
||||
});
|
||||
addUserOrGroupButton.enabled = false; // Init to disabled since we don't have any name entered in yet
|
||||
this.addUserOrGroupInput.onTextChanged(() => {
|
||||
addUserOrGroupButton.enabled = this.addUserOrGroupInput.value !== '';
|
||||
});
|
||||
|
||||
addUserOrGroupInputRow.addItem(this.addUserOrGroupInput, { flex: '0 0 auto' });
|
||||
addUserOrGroupInputRow.addItem(addUserOrGroupButton, { flex: '0 0 auto', CSSStyles: { 'margin-left': '20px' } });
|
||||
|
||||
contentContainer.addItem(addUserOrGroupInputRow, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } });
|
||||
|
||||
// =================================================
|
||||
// = Named Users and Groups permissions header row =
|
||||
// =================================================
|
||||
|
||||
const namedUsersAndGroupsSectionsHeaderRow = this.createPermissionsSectionHeaderRow(permissionsDeleteColumnWidth, permissionsCheckboxColumnWidth);
|
||||
contentContainer.addItem(namedUsersAndGroupsSectionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
|
||||
|
||||
this.namedUsersAndGroupsPermissionsContainer = this.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
contentContainer.addItem(this.namedUsersAndGroupsPermissionsContainer, { flex: '1', CSSStyles: { 'overflow': 'scroll', 'min-height': '200px' } });
|
||||
this.viewInitialized = true;
|
||||
this.onViewInitializedEvent.fire();
|
||||
}
|
||||
|
||||
private handlePermissionStatusUpdated(permissionStatus: PermissionStatus): void {
|
||||
if (!permissionStatus || !this.modelInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the first time go through and create the UI components now that we have a model to use
|
||||
if (!this.viewInitialized) {
|
||||
this.initializeView(permissionStatus);
|
||||
}
|
||||
|
||||
this.eventuallyRunOnInitialized(() => {
|
||||
this.stickyCheckbox.checked = permissionStatus.stickyBit;
|
||||
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
|
||||
this.inheritDefaultsCheckbox.checked =
|
||||
!permissionStatus.owner.getPermission(AclEntryScope.default) &&
|
||||
!permissionStatus.group.getPermission(AclEntryScope.default) &&
|
||||
!permissionStatus.other.getPermission(AclEntryScope.default);
|
||||
}
|
||||
|
||||
this.applyRecursivelyButton.hidden = this.hdfsModel.fileStatus.type !== HdfsFileType.Directory;
|
||||
|
||||
this.posixPermissionsContainer.clearItems();
|
||||
|
||||
const posixPermissionData = [permissionStatus.owner, permissionStatus.group, permissionStatus.other].map(aclEntry => {
|
||||
return this.createPermissionsTableRow(aclEntry, false/*includeDelete*/, false/*includeInherit*/);
|
||||
});
|
||||
|
||||
const posixPermissionsNamesColumnWidth = 800 + (this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? 0 : permissionsCheckboxColumnWidth * 3);
|
||||
const namedUsersAndGroupsPermissionsNamesColumnWidth = 700 + (this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? 0 : permissionsCheckboxColumnWidth * 3);
|
||||
|
||||
// Default set of columns that are always shown
|
||||
let posixPermissionsColumns = [
|
||||
this.createTableColumn('', loc.userOrGroupIcon, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn('', loc.defaultUserAndGroups, posixPermissionsNamesColumnWidth, azdata.DeclarativeDataType.string),
|
||||
this.createTableColumn(loc.readHeader, `${loc.accessHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.writeHeader, `${loc.accessHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.executeHeader, `${loc.accessHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component)];
|
||||
let namedUsersAndGroupsColumns = [
|
||||
this.createTableColumn('', loc.userOrGroupIcon, 50, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.namedUsersAndGroupsHeader, loc.namedUsersAndGroupsHeader, namedUsersAndGroupsPermissionsNamesColumnWidth, azdata.DeclarativeDataType.string),
|
||||
this.createTableColumn(loc.readHeader, `${loc.accessHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.writeHeader, `${loc.accessHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.executeHeader, `${loc.accessHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component)];
|
||||
|
||||
// Additional columns that are only shown for directories
|
||||
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
|
||||
posixPermissionsColumns = posixPermissionsColumns.concat([
|
||||
this.createTableColumn(loc.readHeader, `${loc.defaultHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.writeHeader, `${loc.defaultHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.executeHeader, `${loc.defaultHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component)
|
||||
]);
|
||||
namedUsersAndGroupsColumns = namedUsersAndGroupsColumns.concat([
|
||||
this.createTableColumn(loc.inheritDefaultsLabel, loc.inheritDefaultsLabel, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.readHeader, `${loc.defaultHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.writeHeader, `${loc.defaultHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
this.createTableColumn(loc.executeHeader, `${loc.defaultHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component),
|
||||
]);
|
||||
}
|
||||
namedUsersAndGroupsColumns.push(this.createTableColumn('', loc.deleteTitle, permissionsDeleteColumnWidth, azdata.DeclarativeDataType.component));
|
||||
|
||||
const posixPermissionsTable = this.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: posixPermissionsColumns,
|
||||
data: posixPermissionData
|
||||
}).component();
|
||||
|
||||
this.posixPermissionsContainer.addItem(posixPermissionsTable, { CSSStyles: { 'margin-right': '12px' } });
|
||||
|
||||
this.namedUsersAndGroupsPermissionsContainer.clearItems();
|
||||
|
||||
const namedUsersAndGroupsData = permissionStatus.aclEntries.map(aclEntry => {
|
||||
return this.createPermissionsTableRow(aclEntry, true/*includeDelete*/, this.hdfsModel.fileStatus.type === HdfsFileType.Directory/*includeInherit*/);
|
||||
});
|
||||
|
||||
const namedUsersAndGroupsTable = this.modelBuilder.declarativeTable()
|
||||
.withProps(
|
||||
{
|
||||
columns: namedUsersAndGroupsColumns,
|
||||
data: namedUsersAndGroupsData
|
||||
}).component();
|
||||
|
||||
this.namedUsersAndGroupsPermissionsContainer.addItem(namedUsersAndGroupsTable);
|
||||
|
||||
this.rootLoadingComponent.loading = false;
|
||||
|
||||
void this.addUserOrGroupInput.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private createRadioButton(modelBuilder: azdata.ModelBuilder, label: string, name: string, aclEntryType: AclType): azdata.RadioButtonComponent {
|
||||
const button = modelBuilder.radioButton().withProps({ label: label, name: name }).component();
|
||||
button.onDidClick(() => {
|
||||
this.addUserOrGroupSelectedType = aclEntryType;
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
private createTableColumn(header: string, ariaLabel: string, width: number, type: azdata.DeclarativeDataType): azdata.DeclarativeTableColumn {
|
||||
return {
|
||||
displayName: header,
|
||||
ariaLabel: ariaLabel,
|
||||
valueType: type,
|
||||
isReadOnly: true,
|
||||
width: width,
|
||||
headerCssStyles: {
|
||||
'border': 'none',
|
||||
'padding': '0px',
|
||||
...cssStyles.permissionsTableHeaderCss
|
||||
},
|
||||
rowCssStyles: {
|
||||
'border-top': 'solid 1px #ccc',
|
||||
'border-bottom': 'solid 1px #ccc',
|
||||
'border-left': 'none',
|
||||
'border-right': 'none',
|
||||
'padding': '0px'
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private createImageComponent(type: AclType | PermissionType): azdata.ImageComponent {
|
||||
const imageProperties = getImageForType(type);
|
||||
return this.modelBuilder.image()
|
||||
.withProps({
|
||||
iconPath: imageProperties.iconPath,
|
||||
width: permissionsTypeIconColumnWidth,
|
||||
height: permissionsRowHeight,
|
||||
iconWidth: 20,
|
||||
iconHeight: 20,
|
||||
title: imageProperties.title
|
||||
}).component();
|
||||
}
|
||||
|
||||
private createPermissionsTableRow(aclEntry: AclEntry, includeDelete: boolean, includeInherit: boolean): any[] {
|
||||
// Access Read
|
||||
const accessReadComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).read, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.readHeader}`);
|
||||
accessReadComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.access).read = accessReadComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
// Access Write
|
||||
const accessWriteComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).write, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.writeHeader}`);
|
||||
accessWriteComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.access).write = accessWriteComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
// Access Execute
|
||||
const accessExecuteComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).execute, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.executeHeader}`);
|
||||
accessExecuteComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.access).execute = accessExecuteComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
const permissionsCheckboxesMapping: PermissionCheckboxesMapping = {
|
||||
model: aclEntry,
|
||||
access: { read: accessReadComponents.checkbox, write: accessWriteComponents.checkbox, execute: accessExecuteComponents.checkbox },
|
||||
default: { read: undefined, write: undefined, execute: undefined }
|
||||
};
|
||||
|
||||
let row = [
|
||||
this.createImageComponent(aclEntry.type),
|
||||
aclEntry.displayName,
|
||||
accessReadComponents.container,
|
||||
accessWriteComponents.container,
|
||||
accessExecuteComponents.container
|
||||
];
|
||||
|
||||
// Default permissions can only be set on directories
|
||||
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
|
||||
const defaultPermission = aclEntry.getPermission(AclEntryScope.default);
|
||||
|
||||
// Default Read
|
||||
const defaultReadCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.read, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.readHeader}`);
|
||||
defaultReadCheckboxComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.default).read = defaultReadCheckboxComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
// Default Write
|
||||
const defaultWriteCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.write, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.writeHeader}`);
|
||||
defaultWriteCheckboxComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.default).write = defaultWriteCheckboxComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
// Default Execute
|
||||
const defaultExecuteCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.execute, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.executeHeader}`);
|
||||
defaultExecuteCheckboxComponents.checkbox.onChanged(() => {
|
||||
aclEntry.getPermission(AclEntryScope.default).execute = defaultExecuteCheckboxComponents.checkbox.checked;
|
||||
});
|
||||
|
||||
permissionsCheckboxesMapping.default = { read: defaultReadCheckboxComponents.checkbox, write: defaultWriteCheckboxComponents.checkbox, execute: defaultExecuteCheckboxComponents.checkbox };
|
||||
|
||||
if (includeInherit) {
|
||||
const inheritCheckboxComponents = createCheckbox(this.modelBuilder, !defaultPermission, !this.inheritDefaultsCheckbox.checked, permissionsCheckboxColumnWidth, permissionsRowHeight, loc.inheritDefaultsLabel);
|
||||
inheritCheckboxComponents.checkbox.onChanged(() => {
|
||||
defaultReadCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked;
|
||||
defaultWriteCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked;
|
||||
defaultExecuteCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked;
|
||||
if (inheritCheckboxComponents.checkbox.checked) {
|
||||
aclEntry.removePermission(AclEntryScope.default);
|
||||
defaultReadCheckboxComponents.checkbox.checked = false;
|
||||
defaultWriteCheckboxComponents.checkbox.checked = false;
|
||||
defaultExecuteCheckboxComponents.checkbox.checked = false;
|
||||
} else {
|
||||
// Default to the access settings - this is what HDFS does if you don't
|
||||
// specify the complete set of default ACLs for owner, owning group and other
|
||||
const accessRead = accessReadComponents.checkbox.checked;
|
||||
const accessWrite = accessWriteComponents.checkbox.checked;
|
||||
const accessExecute = accessExecuteComponents.checkbox.checked;
|
||||
defaultReadCheckboxComponents.checkbox.checked = accessRead;
|
||||
defaultWriteCheckboxComponents.checkbox.checked = accessWrite;
|
||||
defaultExecuteCheckboxComponents.checkbox.checked = accessExecute;
|
||||
aclEntry.addPermission(AclEntryScope.default,
|
||||
new AclEntryPermission(accessRead, accessWrite, accessExecute));
|
||||
}
|
||||
});
|
||||
this.namedSectionInheritCheckboxes.push(inheritCheckboxComponents.checkbox);
|
||||
row.push(inheritCheckboxComponents.container);
|
||||
}
|
||||
|
||||
this.posixPermissionCheckboxesMapping.push(permissionsCheckboxesMapping);
|
||||
|
||||
row = row.concat([
|
||||
defaultReadCheckboxComponents.container,
|
||||
defaultWriteCheckboxComponents.container,
|
||||
defaultExecuteCheckboxComponents.container
|
||||
]);
|
||||
}
|
||||
|
||||
if (includeDelete) {
|
||||
const deleteButton = this.modelBuilder.button()
|
||||
.withProps(
|
||||
{
|
||||
label: '',
|
||||
title: loc.deleteTitle,
|
||||
iconPath: IconPathHelper.delete,
|
||||
width: 20,
|
||||
height: 20
|
||||
})
|
||||
.component();
|
||||
deleteButton.onDidClick(() => { this.hdfsModel.deleteAclEntry(aclEntry); });
|
||||
row.push(deleteButton);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private createInheritDefaultsCheckbox(): azdata.CheckBoxComponent {
|
||||
this.inheritDefaultsCheckbox = this.modelBuilder.checkBox()
|
||||
.withProps({
|
||||
width: checkboxSize,
|
||||
height: checkboxSize,
|
||||
checked: false, // Will be set when we get the model update
|
||||
label: loc.inheritDefaultsLabel
|
||||
})
|
||||
.component();
|
||||
|
||||
this.inheritDefaultsCheckbox.onChanged(() => {
|
||||
if (this.inheritDefaultsCheckbox.checked) {
|
||||
this.namedSectionInheritCheckboxes.forEach(c => {
|
||||
c.enabled = false;
|
||||
c.checked = true;
|
||||
});
|
||||
} else {
|
||||
this.namedSectionInheritCheckboxes.forEach(c => {
|
||||
c.enabled = true;
|
||||
c.checked = false;
|
||||
});
|
||||
}
|
||||
// Go through each of the rows for owner/owning group/other and update
|
||||
// their checkboxes based on the new value of the inherit checkbox
|
||||
this.posixPermissionCheckboxesMapping.forEach(m => {
|
||||
m.default.read.enabled = !this.inheritDefaultsCheckbox.checked;
|
||||
m.default.write.enabled = !this.inheritDefaultsCheckbox.checked;
|
||||
m.default.execute.enabled = !this.inheritDefaultsCheckbox.checked;
|
||||
if (this.inheritDefaultsCheckbox.checked) {
|
||||
m.model.removePermission(AclEntryScope.default);
|
||||
m.default.read.checked = false;
|
||||
m.default.write.checked = false;
|
||||
m.default.execute.checked = false;
|
||||
} else {
|
||||
// Default to the access settings - this is what HDFS does if you don't
|
||||
// specify the complete set of default ACLs for owner, owning group and other
|
||||
const accessRead = m.access.read.checked;
|
||||
const accessWrite = m.access.write.checked;
|
||||
const accessExecute = m.access.execute.checked;
|
||||
m.default.read.checked = accessRead;
|
||||
m.default.write.checked = accessWrite;
|
||||
m.default.execute.checked = accessExecute;
|
||||
m.model.addPermission(AclEntryScope.default, new AclEntryPermission(accessRead, accessWrite, accessExecute));
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.inheritDefaultsCheckbox;
|
||||
}
|
||||
/**
|
||||
* Creates the header row for the permissions tables. This contains headers for the name and read/write/execute for the
|
||||
* access section. If the path is for a directory then a default section is included for specifying default permissions.
|
||||
* @param rightSpacerWidth The amount of space to include on the right to correctly align the headers with the
|
||||
* @param middleSpacerWidth The amount of space to include between the text to correctly align the headers with the table sections
|
||||
*/
|
||||
private createPermissionsSectionHeaderRow(rightSpacerWidth: number, middleSpacerWidth: number): azdata.FlexContainer {
|
||||
// Section Headers
|
||||
const sectionHeaderContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component();
|
||||
|
||||
// Access
|
||||
const accessSectionHeader = this.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.accessHeader,
|
||||
ariaHidden: true,
|
||||
CSSStyles: {
|
||||
// This covers 3 checkbox columns
|
||||
'width': `${permissionsCheckboxColumnWidth * 3}px`,
|
||||
'min-width': `${permissionsCheckboxColumnWidth * 3}px`,
|
||||
...cssStyles.permissionsTableHeaderCss
|
||||
}
|
||||
})
|
||||
.component();
|
||||
sectionHeaderContainer.addItem(accessSectionHeader, { flex: '0 0 auto' });
|
||||
|
||||
// Only show default section for directories
|
||||
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
|
||||
// Middle spacer
|
||||
const middleSpacer = this.modelBuilder.text().withProps({ CSSStyles: { 'width': `${middleSpacerWidth}px`, 'min-width': `${middleSpacerWidth}px` } }).component();
|
||||
sectionHeaderContainer.addItem(middleSpacer, { flex: '0 0 auto' });
|
||||
|
||||
// Default
|
||||
const defaultSectionHeader = this.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.defaultHeader,
|
||||
ariaHidden: true,
|
||||
CSSStyles: {
|
||||
// This covers 3 checkbox columns
|
||||
'width': `${permissionsCheckboxColumnWidth * 3}px`,
|
||||
'min-width': `${permissionsCheckboxColumnWidth * 3}px`,
|
||||
...cssStyles.permissionsTableHeaderCss
|
||||
}
|
||||
})
|
||||
.component();
|
||||
sectionHeaderContainer.addItem(defaultSectionHeader, { flex: '0 0 auto' });
|
||||
}
|
||||
|
||||
// Right spacer
|
||||
const rightSpacer = this.modelBuilder.text().withProps({ CSSStyles: { 'width': `${rightSpacerWidth}px`, 'min-width': `${rightSpacerWidth}px` } }).component();
|
||||
sectionHeaderContainer.addItem(rightSpacer, { flex: '0 0 auto' });
|
||||
|
||||
return sectionHeaderContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action when the component is initialized. If already initialized just runs
|
||||
* the action immediately.
|
||||
* @param action The action to be ran when the page is initialized
|
||||
*/
|
||||
protected eventuallyRunOnInitialized(action: () => void): void {
|
||||
if (!this.viewInitialized) {
|
||||
this.onViewInitializedEvent.event(() => {
|
||||
try {
|
||||
action();
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error running onInitialized action for Manage Access dialog : ${error}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a checkbox to be hosted inside of a table cell
|
||||
* @param builder The ModelBuilder used to create the components
|
||||
* @param checked Whether the checkbox is initially checked or not
|
||||
* @param enabled Whether the checkbox is initially enabled or not
|
||||
* @param containerWidth The width of the container holding the checkbox
|
||||
* @param containerHeight The height of the container holding the checkbox
|
||||
* @param ariaLabel The aria label to apply to the checkbox
|
||||
*/
|
||||
function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, enabled: boolean, containerWidth: number, containerHeight: number, ariaLabel: string): { container: azdata.FlexContainer, checkbox: azdata.CheckBoxComponent } {
|
||||
const checkbox = builder.checkBox()
|
||||
.withProps({
|
||||
checked: checked,
|
||||
enabled: enabled,
|
||||
height: checkboxSize,
|
||||
width: checkboxSize,
|
||||
ariaLabel: ariaLabel
|
||||
}).component();
|
||||
const container = builder.flexContainer()
|
||||
.withLayout({ width: containerWidth, height: containerHeight })
|
||||
.component();
|
||||
container.addItem(checkbox, { CSSStyles: { ...cssStyles.permissionCheckboxCss } });
|
||||
return {
|
||||
container: container,
|
||||
checkbox: checkbox
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export namespace cssStyles {
|
||||
export const tableBorderCss = '1px solid #ccc';
|
||||
export const titleCss = { 'font-size': '20px', 'font-weight': '600', 'margin-block-end': '0px', 'margin-block-start': '0px' };
|
||||
export const tableHeaderCss = { 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' };
|
||||
export const permissionsTableHeaderCss = { ...tableHeaderCss, 'text-align': 'center' };
|
||||
export const permissionCheckboxCss = { 'margin-top': '5px', 'margin-left': '13px' };
|
||||
export const tableHeaderLayoutCss = { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text', 'margin-right': '12px' };
|
||||
}
|
||||
@@ -1,995 +0,0 @@
|
||||
// This code is originally from https://github.com/harrisiirak/webhdfs
|
||||
// License: https://github.com/harrisiirak/webhdfs/blob/master/LICENSE
|
||||
|
||||
import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as querystring from 'querystring';
|
||||
import * as request from 'request';
|
||||
import * as BufferStreamReader from 'buffer-stream-reader';
|
||||
import { Cookie } from 'tough-cookie';
|
||||
import * as through from 'through2';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as auth from '../util/auth';
|
||||
import { IHdfsOptions, IRequestParams, FileType } from '../objectExplorerNodeProvider/fileSources';
|
||||
import { PermissionStatus, AclEntry, parseAclList, PermissionType, parseAclPermissionFromOctal, AclEntryScope, AclType } from './aclEntry';
|
||||
import { Mount } from './mount';
|
||||
import { everyoneName, ownerPostfix, owningGroupPostfix } from '../localizedConstants';
|
||||
import { FileStatus, parseHdfsFileType } from './fileStatus';
|
||||
import { Readable, Transform } from 'stream';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const ErrorMessageInvalidDataStructure = localize('webhdfs.invalidDataStructure', "Invalid Data Structure");
|
||||
|
||||
const emitError = (instance: request.Request | Transform, err: any) => {
|
||||
const isErrorEmitted = (instance as any).errorEmitted;
|
||||
|
||||
if (!isErrorEmitted) {
|
||||
instance.emit('error', err);
|
||||
instance.emit('finish');
|
||||
}
|
||||
|
||||
(instance as any).errorEmitted = true;
|
||||
};
|
||||
|
||||
export class WebHDFS {
|
||||
private _requestParams: IRequestParams;
|
||||
private _opts: IHdfsOptions;
|
||||
private _url: any;
|
||||
private _authCookie: Cookie;
|
||||
constructor(opts: IHdfsOptions, requestParams: IRequestParams) {
|
||||
if (!(this instanceof WebHDFS)) {
|
||||
return new WebHDFS(opts, requestParams);
|
||||
}
|
||||
|
||||
let missingProps = ['host', 'port', 'path']
|
||||
.filter((p: keyof IHdfsOptions) => !opts.hasOwnProperty(p) || !opts[p]);
|
||||
if (missingProps && missingProps.length > 0) {
|
||||
throw new Error(localize('webhdfs.missingProperties',
|
||||
"Unable to create WebHDFS client due to missing options: ${0}", missingProps.join(', ')));
|
||||
}
|
||||
|
||||
this._requestParams = requestParams || {};
|
||||
this._requestParams.timeout = this._requestParams.timeout || 10000;
|
||||
|
||||
this._opts = opts;
|
||||
this._url = {
|
||||
protocol: opts.protocol || 'http',
|
||||
hostname: opts.host.trim(),
|
||||
port: opts.port || 80,
|
||||
pathname: opts.path
|
||||
};
|
||||
}
|
||||
|
||||
private checkArgDefined(argName: string, argValue: any): void {
|
||||
if (!argValue) {
|
||||
throw new Error(localize('webhdfs.undefinedArgument', "'${0}' is undefined.", argName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WebHDFS REST API endpoint URL for given operation
|
||||
*
|
||||
* @param operation WebHDFS operation name
|
||||
* @returns WebHDFS REST API endpoint URL
|
||||
*/
|
||||
private getOperationEndpoint(operation: string, path: string, params?: object): string {
|
||||
let endpoint = this._url;
|
||||
endpoint.pathname = encodeURI(this._opts.path + path);
|
||||
let searchOpts = Object.assign(
|
||||
{ 'op': operation },
|
||||
this._opts.user ? { 'user.name': this._opts.user } : {},
|
||||
params || {}
|
||||
);
|
||||
endpoint.search = querystring.stringify(searchOpts);
|
||||
return url.format(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets localized status message for given status code
|
||||
*
|
||||
* @param statusCode Http status code
|
||||
* @returns status message
|
||||
*/
|
||||
private toStatusMessage(statusCode: number): string {
|
||||
let statusMessage: string = undefined;
|
||||
switch (statusCode) {
|
||||
case 400: statusMessage = localize('webhdfs.httpError400', "Bad Request"); break;
|
||||
case 401: statusMessage = localize('webhdfs.httpError401', "Unauthorized"); break;
|
||||
case 403: statusMessage = localize('webhdfs.httpError403', "Forbidden"); break;
|
||||
case 404: statusMessage = localize('webhdfs.httpError404', "Not Found"); break;
|
||||
case 500: statusMessage = localize('webhdfs.httpError500', "Internal Server Error"); break;
|
||||
// TODO: define more messages here
|
||||
default: break;
|
||||
}
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status message from response
|
||||
*
|
||||
* @param response response object
|
||||
* @returns Error message interpreted by status code
|
||||
*/
|
||||
private getStatusMessage(response: request.Response): string {
|
||||
if (!response) { return undefined; }
|
||||
let statusMessage: string = this.toStatusMessage(response.statusCode)
|
||||
|| (response && response.statusMessage);
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets remote exception message from response body
|
||||
*
|
||||
* @param responseBody response body
|
||||
* @returns Error message interpreted by status code
|
||||
*/
|
||||
private getRemoteExceptionMessage(responseBody: any): string {
|
||||
if (!responseBody) { return undefined; }
|
||||
if (typeof responseBody === 'string') {
|
||||
try {
|
||||
responseBody = JSON.parse(responseBody);
|
||||
} catch { }
|
||||
}
|
||||
let remoteExceptionMessage: string = undefined;
|
||||
if (responseBody.hasOwnProperty('RemoteException')
|
||||
&& responseBody.RemoteException.hasOwnProperty('message')) {
|
||||
remoteExceptionMessage = responseBody.RemoteException.message;
|
||||
}
|
||||
return remoteExceptionMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates error message descriptive as much as possible
|
||||
*
|
||||
* @param statusMessage status message
|
||||
* @param [remoteExceptionMessage] remote exception message
|
||||
* @param [error] error
|
||||
* @returns error message
|
||||
*/
|
||||
private getErrorMessage(statusMessage: string, remoteExceptionMessage?: string, error?: any): string {
|
||||
statusMessage = statusMessage === '' ? undefined : statusMessage;
|
||||
remoteExceptionMessage = remoteExceptionMessage === '' ? undefined : remoteExceptionMessage;
|
||||
let messageFromError: string = error ? (error['message'] || error.toString()) : undefined;
|
||||
return statusMessage && remoteExceptionMessage ?
|
||||
`${statusMessage} (${remoteExceptionMessage})` :
|
||||
statusMessage || remoteExceptionMessage || messageFromError ||
|
||||
localize('webhdfs.unknownError', "Unknown Error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse error state from response and return valid Error object
|
||||
*
|
||||
* @param response response object
|
||||
* @param [responseBody] response body
|
||||
* @param [error] error
|
||||
* @returns HdfsError object
|
||||
*/
|
||||
private parseError(response: request.Response, responseBody?: any, error?: any): HdfsError {
|
||||
let statusMessage: string = this.getStatusMessage(response);
|
||||
if (!responseBody && response) {
|
||||
responseBody = response.body;
|
||||
}
|
||||
let remoteExceptionMessage: string = this.getRemoteExceptionMessage(responseBody);
|
||||
let errorMessage: string = this.getErrorMessage(statusMessage, remoteExceptionMessage, error);
|
||||
return new HdfsError(errorMessage, response && response.statusCode,
|
||||
response && response.statusMessage, remoteExceptionMessage, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response is redirect
|
||||
*
|
||||
* @param response response object
|
||||
* @returns if response is redirect
|
||||
*/
|
||||
private isRedirect(response: request.Response): boolean {
|
||||
return [301, 307].indexOf(response.statusCode) !== -1 &&
|
||||
response.headers.hasOwnProperty('location');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response is successful
|
||||
*
|
||||
* @param response response object
|
||||
* @returns if response is successful
|
||||
*/
|
||||
private isSuccess(response: request.Response): boolean {
|
||||
return [200, 201].indexOf(response.statusCode) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response is error
|
||||
*
|
||||
* @param response response object
|
||||
* @returns if response is error
|
||||
*/
|
||||
private isError(response: request.Response): boolean {
|
||||
return [400, 401, 402, 403, 404, 500].indexOf(response.statusCode) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to WebHDFS REST API
|
||||
*
|
||||
* @param method HTTP method
|
||||
* @param urlValue
|
||||
* @param opts Options for request
|
||||
* @returns void
|
||||
*/
|
||||
private sendRequest(method: string, urlValue: string, opts: object, callback: (error: HdfsError, response: request.Response) => void): void {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
let requestParams = Object.assign(
|
||||
{ method: method, url: urlValue, json: true },
|
||||
this._requestParams,
|
||||
opts || {}
|
||||
);
|
||||
this.ensureCookie(requestParams);
|
||||
// Add a wrapper to handle unauthorized requests by adding kerberos auth steps
|
||||
let handler = (error: any, response: request.Response) => {
|
||||
if (error && error.statusCode === 401 && this._requestParams.isKerberos) {
|
||||
this.requestWithKerberosSync(requestParams, callback);
|
||||
} else {
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
this.doSendRequest(requestParams, handler);
|
||||
}
|
||||
|
||||
private ensureCookie(requestParams: { headers?: { [key: string]: string } }) {
|
||||
if (this._authCookie && this._authCookie.expiryTime() > Date.now()) {
|
||||
requestParams.headers = requestParams.headers || {};
|
||||
requestParams.headers['cookie'] = `${this._authCookie.key}=${this._authCookie.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
private doSendRequest(requestParams: any, callback: (error: HdfsError, response: any) => void): void {
|
||||
request(requestParams, (error: any, response: request.Response, body: any) => {
|
||||
if (error || this.isError(response)) {
|
||||
let hdfsError = this.parseError(response, body, error);
|
||||
callback(hdfsError, response);
|
||||
}
|
||||
else if (this.isSuccess(response)) {
|
||||
callback(undefined, response);
|
||||
}
|
||||
else {
|
||||
let hdfsError = new HdfsError(localize('webhdfs.unexpectedRedirect', "Unexpected Redirect"), response && response.statusCode, response && response.statusMessage, this.getRemoteExceptionMessage(body || response.body), error);
|
||||
callback(hdfsError, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates using kerberos as part of a request, and saves cookie if successful.
|
||||
* Ideally would use request's built-in cookie functionality but this isn't working with non-public domains.
|
||||
* Instead, save the cookie in this module and reuse if not expired
|
||||
*/
|
||||
private requestWithKerberosSync(requestParams: any, callback: (error: HdfsError, response: request.Response) => void) {
|
||||
this.setKerberosAuthOnParams(requestParams).then(() => {
|
||||
this.doSendRequest(requestParams, (error, response) => {
|
||||
if (error) {
|
||||
// Pass on the callback
|
||||
callback(error, response);
|
||||
}
|
||||
else {
|
||||
// Capture cookie for future requests
|
||||
this.setAuthCookie(response);
|
||||
callback(error, response);
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
callback(err, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
private async setKerberosAuthOnParams(requestParams: any): Promise<void> {
|
||||
let kerberosToken = await auth.authenticateKerberos(this._opts.host);
|
||||
requestParams.headers = { Authorization: `Negotiate ${kerberosToken}` };
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
private setAuthCookie(response: request.Response) {
|
||||
try {
|
||||
if (response && response.headers && response.headers['set-cookie']) {
|
||||
let cookies: Cookie[];
|
||||
if (response.headers['set-cookie'] instanceof Array) {
|
||||
cookies = response.headers['set-cookie'].map(c => Cookie.parse(c));
|
||||
}
|
||||
else {
|
||||
cookies = [Cookie.parse(response.headers['set-cookie'])];
|
||||
}
|
||||
this._authCookie = cookies[0];
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Change file permissions
|
||||
* @returns void
|
||||
*/
|
||||
public chmod(path: string, mode: string, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('mode', mode);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('setpermission', path, { permission: mode });
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
return callback && callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change file owner
|
||||
*
|
||||
* @param path
|
||||
* @param userId User name
|
||||
* @param groupId Group name
|
||||
* @param callback
|
||||
* @returns void
|
||||
*/
|
||||
public chown(path: string, userId: string, groupId: string, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('userId', userId);
|
||||
this.checkArgDefined('groupId', groupId);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('setowner', path, {
|
||||
owner: userId,
|
||||
group: groupId
|
||||
});
|
||||
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List the status of a path
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public listStatus(path: string, callback: (error: HdfsError, files: FileStatus[]) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('liststatus', path);
|
||||
this.sendRequest('GET', endpoint, undefined, (error, response) => {
|
||||
if (!callback) { return; }
|
||||
|
||||
let files: any[] = [];
|
||||
if (error) {
|
||||
callback(error, undefined);
|
||||
} else if (response.body.hasOwnProperty('FileStatuses')
|
||||
&& response.body.FileStatuses.hasOwnProperty('FileStatus')) {
|
||||
files = (<any[]>response.body.FileStatuses.FileStatus).map(fs => {
|
||||
return new FileStatus(
|
||||
fs.accessTime || '',
|
||||
fs.blockSize || '',
|
||||
fs.group || '',
|
||||
fs.length || '',
|
||||
fs.modificationTime || '',
|
||||
fs.owner || '',
|
||||
fs.pathSuffix || '',
|
||||
fs.permission || '',
|
||||
fs.replication || '',
|
||||
fs.snapshotEnabled || '',
|
||||
parseHdfsFileType(fs.type)
|
||||
);
|
||||
});
|
||||
callback(undefined, files);
|
||||
} else {
|
||||
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make new directory
|
||||
* @returns void
|
||||
*/
|
||||
public mkdir(path: string, permission: string = '0755', callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('mkdirs', path, {
|
||||
permission: permission
|
||||
});
|
||||
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename path
|
||||
* @returns void
|
||||
*/
|
||||
public rename(path: string, destination: string, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('destination', destination);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('rename', path, {
|
||||
destination: destination
|
||||
});
|
||||
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getFileStatus(path: string, callback: (error: HdfsError, fileStatus: FileStatus) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('getfilestatus', path);
|
||||
this.sendRequest('GET', endpoint, undefined, (error, response) => {
|
||||
if (!callback) { return; }
|
||||
if (error) {
|
||||
callback(error, undefined);
|
||||
} else if (response.body.hasOwnProperty('FileStatus')) {
|
||||
const fileStatus = new FileStatus(
|
||||
response.body.FileStatus.accessTime || '',
|
||||
response.body.FileStatus.blockSize || '',
|
||||
response.body.FileStatus.group || '',
|
||||
response.body.FileStatus.length || '',
|
||||
response.body.FileStatus.modificationTime || '',
|
||||
response.body.FileStatus.owner || '',
|
||||
response.body.FileStatus.pathSuffix || '',
|
||||
response.body.FileStatus.permission || '',
|
||||
response.body.FileStatus.replication || '',
|
||||
response.body.FileStatus.snapshotEnabled || '',
|
||||
parseHdfsFileType(response.body.FileStatus.type || 'undefined')
|
||||
);
|
||||
callback(undefined, fileStatus);
|
||||
} else {
|
||||
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ACL status for given path
|
||||
* @param path The path to the file/folder to get the status of
|
||||
* @param callback Callback to handle the response
|
||||
* @returns void
|
||||
*/
|
||||
public getAclStatus(path: string, callback: (error: HdfsError, permissionStatus: PermissionStatus) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('getaclstatus', path);
|
||||
this.sendRequest('GET', endpoint, undefined, (error, response) => {
|
||||
if (!callback) { return; }
|
||||
if (error) {
|
||||
callback(error, undefined);
|
||||
} else if (response.body.hasOwnProperty('AclStatus')) {
|
||||
const permissions = parseAclPermissionFromOctal(response.body.AclStatus.permission);
|
||||
const ownerEntry = new AclEntry(PermissionType.owner, '', `${response.body.AclStatus.owner || ''}${ownerPostfix}`);
|
||||
ownerEntry.addPermission(AclEntryScope.access, permissions.owner);
|
||||
const groupEntry = new AclEntry(PermissionType.group, '', `${response.body.AclStatus.group || ''}${owningGroupPostfix}`);
|
||||
groupEntry.addPermission(AclEntryScope.access, permissions.group);
|
||||
const otherEntry = new AclEntry(PermissionType.other, '', everyoneName);
|
||||
otherEntry.addPermission(AclEntryScope.access, permissions.other);
|
||||
const parsedEntries = parseAclList((<any[]>response.body.AclStatus.entries).join(','));
|
||||
|
||||
// First go through and apply any ACLs for the unnamed entries (which correspond to the permissions in
|
||||
// the permission octal)
|
||||
parsedEntries.filter(e => e.name === '').forEach(e => {
|
||||
let targetEntry: AclEntry;
|
||||
switch (e.type) {
|
||||
case AclType.user:
|
||||
targetEntry = ownerEntry;
|
||||
break;
|
||||
case AclType.group:
|
||||
targetEntry = groupEntry;
|
||||
break;
|
||||
case AclType.other:
|
||||
targetEntry = otherEntry;
|
||||
break;
|
||||
default:
|
||||
// Unknown type - just ignore since we don't currently support the other types
|
||||
return;
|
||||
}
|
||||
e.getAllPermissions().forEach(sp => {
|
||||
targetEntry.addPermission(sp.scope, sp.permission);
|
||||
});
|
||||
});
|
||||
|
||||
const permissionStatus = new PermissionStatus(
|
||||
ownerEntry,
|
||||
groupEntry,
|
||||
otherEntry,
|
||||
!!response.body.AclStatus.stickyBit,
|
||||
// We filter out empty names here since those have already been merged into the
|
||||
// owner/owning group/other entries
|
||||
parsedEntries.filter(e => e.name !== ''));
|
||||
callback(undefined, permissionStatus);
|
||||
} else {
|
||||
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ACL for the given path. The owner, group and other fields are required - other entries are optional.
|
||||
* @param path The path to the file/folder to set the ACL on
|
||||
* @param fileType The type of file we're setting to determine if defaults should be applied. Use undefined if type is unknown
|
||||
* @param permissionStatus The status containing the permissions to set
|
||||
* @param callback Callback to handle the response
|
||||
*/
|
||||
public setAcl(path: string, fileType: FileType | undefined, permissionStatus: PermissionStatus, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('permissionStatus', permissionStatus);
|
||||
const concatEntries = [permissionStatus.owner, permissionStatus.group, permissionStatus.other].concat(permissionStatus.aclEntries);
|
||||
const aclSpec = concatEntries.reduce((acc, entry: AclEntry) => acc.concat(entry.toAclStrings(fileType !== FileType.File)), []).join(',');
|
||||
let endpoint = this.getOperationEndpoint('setacl', path, { aclspec: aclSpec });
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
return callback && callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission octal (sticky, owner, group & other) for a file/folder
|
||||
* @param path The path to the file/folder to set the permission of
|
||||
* @param permissionStatus The status containing the permission to set
|
||||
* @param callback Callback to handle the response
|
||||
*/
|
||||
public setPermission(path: string, permissionStatus: PermissionStatus, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('permissionStatus', permissionStatus);
|
||||
let endpoint = this.getOperationEndpoint('setpermission', path, { permission: permissionStatus.permissionOctal });
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
return callback && callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the default ACLs for the specified path
|
||||
* @param path The path to remove the default ACLs for
|
||||
* @param callback Callback to handle the response
|
||||
*/
|
||||
public removeDefaultAcl(path: string, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
let endpoint = this.getOperationEndpoint('removedefaultacl', path);
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
return callback && callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all mounts for a HDFS connection
|
||||
* @param callback Callback to handle the response
|
||||
* @returns void
|
||||
*/
|
||||
public getMounts(callback: (error: HdfsError, mounts: Mount[]) => void): void {
|
||||
let endpoint = this.getOperationEndpoint('listmounts', '');
|
||||
this.sendRequest('GET', endpoint, undefined, (error, response) => {
|
||||
if (!callback) { return; }
|
||||
if (error) {
|
||||
callback(error, undefined);
|
||||
} else if (response.body.hasOwnProperty('Mounts')) {
|
||||
const mounts = response.body.Mounts;
|
||||
callback(undefined, mounts);
|
||||
} else {
|
||||
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file existence
|
||||
* Wraps stat method
|
||||
*
|
||||
* @see WebHDFS.stat
|
||||
* @returns void
|
||||
*/
|
||||
public exists(path: string, callback: (error: HdfsError, exists: boolean) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
this.listStatus(path, (error, fileStatus) => {
|
||||
let exists = !fileStatus ? false : true;
|
||||
callback(error, exists);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the file
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
* @param append If set to true then append data to the file
|
||||
* @param opts
|
||||
* @param callback
|
||||
*/
|
||||
public writeFile(path: string, data: string | Buffer, append: boolean, opts: object,
|
||||
callback: (error: HdfsError) => void): fs.WriteStream {
|
||||
this.checkArgDefined('path', path);
|
||||
this.checkArgDefined('data', data);
|
||||
|
||||
let error: HdfsError = null;
|
||||
let localStream = new BufferStreamReader(data);
|
||||
let remoteStream: fs.WriteStream = this.createWriteStream(path, !!append, opts || {});
|
||||
|
||||
// Handle events
|
||||
remoteStream.once('error', (err) => {
|
||||
error = <HdfsError>err;
|
||||
});
|
||||
|
||||
remoteStream.once('finish', () => {
|
||||
if (callback && error) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
|
||||
localStream.pipe(remoteStream); // Pipe data
|
||||
return remoteStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append data to the file
|
||||
*
|
||||
* @see writeFile
|
||||
*/
|
||||
public appendFile(path: string, data: string | Buffer, opts: object, callback: (error: HdfsError) => void): fs.WriteStream {
|
||||
return this.writeFile(path, data, true, opts, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the file
|
||||
*
|
||||
* @fires Request#data
|
||||
* @fires WebHDFS#finish
|
||||
* @returns void
|
||||
*/
|
||||
public readFile(path: string, callback: (error: HdfsError, buffer: Buffer) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let remoteFileStream = this.createReadStream(path);
|
||||
let data: any[] = [];
|
||||
let error: HdfsError = undefined;
|
||||
|
||||
remoteFileStream.once('error', (err) => {
|
||||
error = <HdfsError>err;
|
||||
});
|
||||
|
||||
remoteFileStream.on('data', (dataChunk) => {
|
||||
data.push(dataChunk);
|
||||
});
|
||||
|
||||
remoteFileStream.once('finish', () => {
|
||||
if (!callback) { return; }
|
||||
if (!error) {
|
||||
callback(undefined, Buffer.concat(data));
|
||||
} else {
|
||||
callback(error, undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create writable stream for given path
|
||||
*
|
||||
* @fires WebHDFS#finish
|
||||
* @param path
|
||||
* @param append If set to true then append data to the file
|
||||
* @param opts
|
||||
* @example
|
||||
* let hdfs = WebHDFS.createClient();
|
||||
*
|
||||
* let localFileStream = hdfs.createReadStream('/path/to/local/file');
|
||||
* let remoteFileStream = hdfs.createWriteStream('/path/to/remote/file');
|
||||
*
|
||||
* localFileStream.pipe(remoteFileStream);
|
||||
*
|
||||
* remoteFileStream.on('error', (err) => {
|
||||
* // Do something with the error
|
||||
* });
|
||||
*
|
||||
* remoteFileStream.on('finish', () => {
|
||||
* // Upload is done
|
||||
* });
|
||||
*/
|
||||
public createWriteStream(path: string, append?: boolean, opts?: object): fs.WriteStream {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint(
|
||||
append ? 'append' : 'create',
|
||||
path,
|
||||
Object.assign(
|
||||
{
|
||||
overwrite: true,
|
||||
permission: '0755'
|
||||
},
|
||||
opts || {}
|
||||
)
|
||||
);
|
||||
|
||||
let params: any = Object.assign(
|
||||
{
|
||||
method: append ? 'POST' : 'PUT',
|
||||
url: endpoint,
|
||||
json: true,
|
||||
},
|
||||
this._requestParams
|
||||
);
|
||||
params.headers = params.headers || {};
|
||||
params.headers['content-type'] = 'application/octet-stream';
|
||||
|
||||
if (!this._requestParams.isKerberos) {
|
||||
return this.doCreateWriteStream(params);
|
||||
}
|
||||
// Else, must add kerberos token and handle redirects
|
||||
return this.createKerberosWriteStream(params);
|
||||
}
|
||||
|
||||
private createKerberosWriteStream(params: any): fs.WriteStream {
|
||||
params.followRedirect = false;
|
||||
// Create an intermediate stream that pauses until we get a positive
|
||||
// response from the server
|
||||
let isWaiting = true;
|
||||
let firstCb: Function = undefined;
|
||||
let replyStream = through(function (chunk, enc, cb) {
|
||||
this.push(chunk, enc);
|
||||
if (isWaiting) {
|
||||
firstCb = cb;
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
let handleErr = (err: any) => {
|
||||
replyStream.emit('error', err);
|
||||
replyStream.end();
|
||||
};
|
||||
let initRedirectedStream = () => {
|
||||
// After redirect, create valid stream to correct location
|
||||
// and pipe the intermediate stream to it, unblocking the data flow
|
||||
params.headers['content-type'] = 'application/octet-stream';
|
||||
let upload = request(params, (err: any, res: request.Response, bo: any) => {
|
||||
if (err || this.isError(res)) {
|
||||
emitError(replyStream, this.parseError(res, bo, err));
|
||||
replyStream.end();
|
||||
}
|
||||
else if (res.headers.hasOwnProperty('location')) {
|
||||
replyStream.emit('finish', res.headers.location);
|
||||
}
|
||||
else {
|
||||
replyStream.emit('finish');
|
||||
}
|
||||
});
|
||||
isWaiting = false;
|
||||
replyStream.pipe(upload);
|
||||
if (firstCb) {
|
||||
firstCb();
|
||||
}
|
||||
};
|
||||
this.requestWithRedirectAndAuth(params, initRedirectedStream, handleErr);
|
||||
return <fs.WriteStream><any>replyStream;
|
||||
}
|
||||
|
||||
private doCreateWriteStream(params: any): fs.WriteStream {
|
||||
|
||||
let canResume: boolean = true;
|
||||
let stream: Readable;
|
||||
let req = request(params, (error: any, response: request.Response, body: any) => {
|
||||
// Handle redirect only if there was not an error (e.g. res is defined)
|
||||
if (response && this.isRedirect(response)) {
|
||||
let upload = request(Object.assign(params, { url: response.headers.location }), (err: any, res: request.Response, bo: any) => {
|
||||
if (err || this.isError(res)) {
|
||||
emitError(req, this.parseError(res, bo, err));
|
||||
req.end();
|
||||
}
|
||||
else if (res.headers.hasOwnProperty('location')) {
|
||||
req.emit('finish', res.headers.location);
|
||||
}
|
||||
else {
|
||||
req.emit('finish');
|
||||
}
|
||||
});
|
||||
canResume = true; // Enable resume
|
||||
stream.pipe(upload);
|
||||
stream.resume();
|
||||
}
|
||||
if (error || this.isError(response)) {
|
||||
emitError(req, this.parseError(response, body, error));
|
||||
}
|
||||
});
|
||||
req.on('pipe', (src: Readable) => {
|
||||
// Pause read stream
|
||||
stream = src;
|
||||
stream.pause();
|
||||
// This is not an elegant solution but here we go
|
||||
// Basically we don't allow pipe() method to resume reading input
|
||||
// and set internal _readableState.flowing to false
|
||||
canResume = false;
|
||||
stream.on('resume', () => {
|
||||
if (!canResume) {
|
||||
(stream as any)._readableState.flowing = false; // i guess we are unsafely accessing this
|
||||
}
|
||||
});
|
||||
// Unpipe initial request
|
||||
src.unpipe(req);
|
||||
req.end();
|
||||
});
|
||||
return <fs.WriteStream><any>req;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create readable stream for given path
|
||||
*
|
||||
* @fires Request#data
|
||||
* @fires WebHDFS#finish
|
||||
*
|
||||
* @example
|
||||
* let hdfs = WebHDFS.createClient();
|
||||
*
|
||||
* let remoteFileStream = hdfs.createReadStream('/path/to/remote/file');
|
||||
*
|
||||
* remoteFileStream.on('error', (err) => {
|
||||
* // Do something with the error
|
||||
* });
|
||||
*
|
||||
* remoteFileStream.on('data', (dataChunk) => {
|
||||
* // Do something with the data chunk
|
||||
* });
|
||||
*
|
||||
* remoteFileStream.on('finish', () => {
|
||||
* // Upload is done
|
||||
* });
|
||||
*/
|
||||
public createReadStream(path: string, opts?: object): fs.ReadStream {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('open', path, opts);
|
||||
let params: request.OptionsWithUrl = Object.assign(
|
||||
{
|
||||
method: 'GET',
|
||||
url: endpoint,
|
||||
json: true
|
||||
},
|
||||
this._requestParams
|
||||
);
|
||||
if (!this._requestParams.isKerberos) {
|
||||
return <fs.ReadStream><any>this.doCreateReadStream(params);
|
||||
}
|
||||
// Else, must add kerberos token and handle redirects
|
||||
params.followRedirect = false;
|
||||
let replyStream = through();
|
||||
let handleErr = (err: any) => {
|
||||
replyStream.emit('error', err);
|
||||
replyStream.end();
|
||||
};
|
||||
let initRedirectedStream = () => {
|
||||
let redirectedStream = this.doCreateReadStream(params);
|
||||
redirectedStream.pipe(replyStream);
|
||||
};
|
||||
this.requestWithRedirectAndAuth(params, initRedirectedStream, handleErr);
|
||||
|
||||
return <fs.ReadStream><any>replyStream;
|
||||
}
|
||||
|
||||
private requestWithRedirectAndAuth(params: request.OptionsWithUrl, onRedirected: () => void, handleErr: (err: any) => void) {
|
||||
this.requestWithKerberosSync(params, (err, response: request.Response) => {
|
||||
if (err && err.statusCode === 307 && response.headers['location']) {
|
||||
// It's a redirect
|
||||
params.url = response.headers['location'];
|
||||
this.setKerberosAuthOnParams(params)
|
||||
.then(onRedirected)
|
||||
.catch(handleErr);
|
||||
} else {
|
||||
handleErr(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private doCreateReadStream(params: request.OptionsWithUrl): fs.ReadStream {
|
||||
|
||||
let req: request.Request = request(params);
|
||||
req.on('complete', (response) => {
|
||||
req.emit('finish');
|
||||
});
|
||||
req.on('response', (response) => {
|
||||
// Handle remote exceptions
|
||||
// Remove all data handlers and parse error data
|
||||
if (this.isError(response)) {
|
||||
req.removeAllListeners('data');
|
||||
req.on('data', (data) => {
|
||||
req.emit('error', this.parseError(response, data.toString()));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
else if (this.isRedirect(response)) {
|
||||
let download = request(params);
|
||||
download.on('complete', (response) => {
|
||||
req.emit('finish');
|
||||
});
|
||||
// Proxy data to original data handler
|
||||
// Not the nicest way but hey
|
||||
download.on('data', (dataChunk) => {
|
||||
req.emit('data', dataChunk);
|
||||
});
|
||||
// Handle subrequest
|
||||
download.on('response', (response) => {
|
||||
if (this.isError(response)) {
|
||||
download.removeAllListeners('data');
|
||||
download.on('data', (data) => {
|
||||
req.emit('error', this.parseError(response, data.toString()));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// No need to interrupt the request
|
||||
// data will be automatically sent to the data handler
|
||||
});
|
||||
return <fs.ReadStream><any>req;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create symbolic link to the destination path
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public symlink(src: string, destination: string, createParent: boolean = false, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('src', src);
|
||||
this.checkArgDefined('destination', destination);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('createsymlink', src, {
|
||||
createParent: createParent,
|
||||
destination: destination
|
||||
});
|
||||
|
||||
this.sendRequest('PUT', endpoint, undefined, (error) => {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink path
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public unlink(path: string, recursive: boolean = false, callback: (error: HdfsError) => void): void {
|
||||
this.checkArgDefined('path', path);
|
||||
|
||||
let endpoint = this.getOperationEndpoint('delete', path, { recursive: recursive });
|
||||
this.sendRequest('DELETE', endpoint, undefined, (error) => {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias WebHDFS.unlink
|
||||
* @returns void
|
||||
*/
|
||||
public rmdir(path: string, recursive: boolean = false, callback: (error: HdfsError) => void): void {
|
||||
this.unlink(path, recursive, callback);
|
||||
}
|
||||
|
||||
public static createClient(opts: IHdfsOptions): WebHDFS {
|
||||
return new WebHDFS(
|
||||
Object.assign(
|
||||
{
|
||||
host: 'localhost',
|
||||
port: '50070',
|
||||
path: '/webhdfs/v1'
|
||||
},
|
||||
opts || {}
|
||||
),
|
||||
opts.requestParams ?? { }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class HdfsError extends Error {
|
||||
constructor(
|
||||
errorMessage: string,
|
||||
public statusCode?: number,
|
||||
public statusMessage?: string,
|
||||
public remoteExceptionMessage?: string,
|
||||
public internalError?: any) {
|
||||
super(errorMessage);
|
||||
}
|
||||
}
|
||||