mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
[Port] Sync up arc and azdata extensions with main (#12810)
* Sync up arc and azdata extensions with main * capture 'this' to use retrieveVariable as callback (#12828) * capture 'this' to use retrieveVariable as callback * remove change not needed for #12082 Co-authored-by: Arvind Ranasaria <ranasaria@outlook.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Welcome to Microsoft Azure Arc Extension for Azure Data Studio!
|
Welcome to Microsoft Azure Arc Extension for Azure Data Studio!
|
||||||
|
|
||||||
**This extension is only applicable to customers in the Azure Arc data services private preview.**
|
**This extension is only applicable to customers in the Azure Arc data services public preview.**
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
not_numbered: true
|
not_numbered: true
|
||||||
expand_sections: true
|
expand_sections: true
|
||||||
sections:
|
sections:
|
||||||
- title: TSG100 - The Azure Arc Postgres troubleshooter
|
- title: TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter
|
||||||
url: postgres/tsg100-troubleshoot-postgres
|
url: postgres/tsg100-troubleshoot-postgres
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
- This chapter contains notebooks for troubleshooting Postgres on Azure Arc
|
- This chapter contains notebooks for troubleshooting Postgres on Azure Arc
|
||||||
|
|
||||||
## Notebooks in this Chapter
|
## Notebooks in this Chapter
|
||||||
- [TSG100 - The Azure Arc Postgres troubleshooter](tsg100-troubleshoot-postgres.ipynb)
|
- [TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter](tsg100-troubleshoot-postgres.ipynb)
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
not_numbered: true
|
not_numbered: true
|
||||||
expand_sections: true
|
expand_sections: true
|
||||||
sections:
|
sections:
|
||||||
- title: TSG100 - The Azure Arc Postgres troubleshooter
|
- title: TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter
|
||||||
url: postgres/tsg100-troubleshoot-postgres
|
url: postgres/tsg100-troubleshoot-postgres
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"TSG100 - The Azure Arc Postgres troubleshooter\n",
|
"TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter\n",
|
||||||
"==============================================\n",
|
"===================================================================\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Description\n",
|
"Description\n",
|
||||||
"-----------\n",
|
"-----------\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Follow these steps to troubleshoot an Azure Arc Postgres Server.\n",
|
"Follow these steps to troubleshoot an Azure Arc enabled PostgreSQL\n",
|
||||||
|
"Hyperscale Server.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Steps\n",
|
"Steps\n",
|
||||||
"-----\n",
|
"-----\n",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"# the user will be prompted to select a server.\n",
|
"# the user will be prompted to select a server.\n",
|
||||||
"namespace = os.environ.get('POSTGRES_SERVER_NAMESPACE')\n",
|
"namespace = os.environ.get('POSTGRES_SERVER_NAMESPACE')\n",
|
||||||
"name = os.environ.get('POSTGRES_SERVER_NAME')\n",
|
"name = os.environ.get('POSTGRES_SERVER_NAME')\n",
|
||||||
|
"version = os.environ.get('POSTGRES_SERVER_VERSION')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"tail_lines = 50"
|
"tail_lines = 50"
|
||||||
]
|
]
|
||||||
@@ -143,7 +145,7 @@
|
|||||||
" if cmd.startswith(\"kubectl \") and \"AZDATA_OPENSHIFT\" in os.environ:\n",
|
" if cmd.startswith(\"kubectl \") and \"AZDATA_OPENSHIFT\" in os.environ:\n",
|
||||||
" cmd_actual[0] = cmd_actual[0].replace(\"kubectl\", \"oc\")\n",
|
" cmd_actual[0] = cmd_actual[0].replace(\"kubectl\", \"oc\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # To aid supportabilty, determine which binary file will actually be executed on the machine\n",
|
" # To aid supportability, determine which binary file will actually be executed on the machine\n",
|
||||||
" #\n",
|
" #\n",
|
||||||
" which_binary = None\n",
|
" which_binary = None\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -400,11 +402,11 @@
|
|||||||
"import math\n",
|
"import math\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# If a server was provided, get it\n",
|
"# If a server was provided, get it\n",
|
||||||
"if namespace and name:\n",
|
"if namespace and name and version:\n",
|
||||||
" server = json.loads(run(f'kubectl get dbs -n {namespace} {name} -o json', return_output=True))\n",
|
" server = json.loads(run(f'kubectl get postgresql-{version} -n {namespace} {name} -o json', return_output=True))\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" # Otherwise prompt the user to select a server\n",
|
" # Otherwise prompt the user to select a server\n",
|
||||||
" servers = json.loads(run(f'kubectl get dbs --all-namespaces -o json', return_output=True))['items']\n",
|
" servers = json.loads(run(f'kubectl get postgresqls --all-namespaces -o json', return_output=True))['items']\n",
|
||||||
" if not servers:\n",
|
" if not servers:\n",
|
||||||
" raise Exception('No Postgres servers found')\n",
|
" raise Exception('No Postgres servers found')\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -425,6 +427,7 @@
|
|||||||
" server = servers[i-1]\n",
|
" server = servers[i-1]\n",
|
||||||
" namespace = server['metadata']['namespace']\n",
|
" namespace = server['metadata']['namespace']\n",
|
||||||
" name = server['metadata']['name']\n",
|
" name = server['metadata']['name']\n",
|
||||||
|
" version = server['kind'][len('postgresql-'):]\n",
|
||||||
" break\n",
|
" break\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Got server {namespace}.{name}'))"
|
"display(Markdown(f'#### Got server {namespace}.{name}'))"
|
||||||
@@ -446,10 +449,10 @@
|
|||||||
"uid = server['metadata']['uid']\n",
|
"uid = server['metadata']['uid']\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Server summary'))\n",
|
"display(Markdown(f'#### Server summary'))\n",
|
||||||
"run(f'kubectl get dbs -n {namespace} {name}')\n",
|
"run(f'kubectl get postgresql-{version} -n {namespace} {name}')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Resource summary'))\n",
|
"display(Markdown(f'#### Resource summary'))\n",
|
||||||
"run(f'kubectl get pods,pvc,svc,ep -n {namespace} -l dusky.microsoft.com/serviceId={uid}')"
|
"run(f'kubectl get sts,pods,pvc,svc,ep -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid}')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -466,7 +469,7 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"display(Markdown(f'#### Troubleshooting server {namespace}.{name}'))\n",
|
"display(Markdown(f'#### Troubleshooting server {namespace}.{name}'))\n",
|
||||||
"run(f'kubectl describe dbs -n {namespace} {name}')"
|
"run(f'kubectl describe postgresql-{version} -n {namespace} {name}')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -482,7 +485,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"pods = json.loads(run(f'kubectl get pods -n {namespace} -l dusky.microsoft.com/serviceId={uid} -o json', return_output=True))['items']\n",
|
"pods = json.loads(run(f'kubectl get pods -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid} -o json', return_output=True))['items']\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Summarize and describe each pod\n",
|
"# Summarize and describe each pod\n",
|
||||||
"for pod in pods:\n",
|
"for pod in pods:\n",
|
||||||
@@ -529,8 +532,7 @@
|
|||||||
" con_restarts = con_status.get('restartCount', 0)\n",
|
" con_restarts = con_status.get('restartCount', 0)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" display(Markdown(f'#### Troubleshooting container {namespace}.{pod_name}/{con_name} ({i+1}/{len(cons)})\\n'\n",
|
" display(Markdown(f'#### Troubleshooting container {namespace}.{pod_name}/{con_name} ({i+1}/{len(cons)})\\n'\n",
|
||||||
" f'#### {\"S\" if con_started else \"Not s\"}tarted and '\n",
|
" f'#### {\"R\" if con_ready else \"Not r\"}eady with {con_restarts} restarts'))\n",
|
||||||
" f'{\"\" if con_ready else \"not \"}ready with {con_restarts} restarts'))\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines}')\n",
|
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines}')\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -554,7 +556,7 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"display(Markdown(f'#### Troubleshooting PersistentVolumeClaims'))\n",
|
"display(Markdown(f'#### Troubleshooting PersistentVolumeClaims'))\n",
|
||||||
"run(f'kubectl describe pvc -n {namespace} -l dusky.microsoft.com/serviceId={uid}')"
|
"run(f'kubectl describe pvc -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid}')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"|Tools|Description|Installation|\n",
|
"|Tools|Description|Installation|\n",
|
||||||
"|---|---|---|\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",
|
"|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 resources in an Azure Arc cluster |[Installation](https://github.com/microsoft/Azure-data-services-on-Azure-Arc/blob/master/scenarios/001-install-client-tools.md) |"
|
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "714582b9-10ee-409e-ab12-15a4825c9471"
|
"azdata_cell_guid": "714582b9-10ee-409e-ab12-15a4825c9471"
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Set variables**\n",
|
"### **Set variables**\n",
|
||||||
"Generated by Azure Data Studio using the values collected in the Azure Arc Data controller create wizard"
|
"Generated by Azure Data Studio using the values collected in the 'Create Azure Arc data controller' wizard."
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
|
"azdata_cell_guid": "4b266b2d-bd1b-4565-92c9-3fc146cdce6d"
|
||||||
@@ -129,13 +129,11 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"if \"AZDATA_NB_VAR_ARC_DOCKER_PASSWORD\" in os.environ:\n",
|
|
||||||
" arc_docker_password = os.environ[\"AZDATA_NB_VAR_ARC_DOCKER_PASSWORD\"]\n",
|
|
||||||
"if \"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\" in os.environ:\n",
|
"if \"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\" in os.environ:\n",
|
||||||
" arc_admin_password = os.environ[\"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\"]\n",
|
" arc_admin_password = os.environ[\"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" if arc_admin_password == \"\":\n",
|
" if arc_admin_password == \"\":\n",
|
||||||
" arc_admin_password = getpass.getpass(prompt = 'Azure Arc Data controller password')\n",
|
" arc_admin_password = getpass.getpass(prompt = 'Azure Arc Data Controller password')\n",
|
||||||
" if arc_admin_password == \"\":\n",
|
" if arc_admin_password == \"\":\n",
|
||||||
" sys.exit(f'Password is required.')\n",
|
" sys.exit(f'Password is required.')\n",
|
||||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||||
@@ -175,7 +173,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Create Azure Arc Data controller**"
|
"### **Create Azure Arc Data Controller**"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "efe78cd3-ed73-4c9b-b586-fdd6c07dd37f"
|
"azdata_cell_guid": "efe78cd3-ed73-4c9b-b586-fdd6c07dd37f"
|
||||||
@@ -184,16 +182,14 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"print (f'Creating Azure Arc controller: {arc_data_controller_name} using configuration {arc_cluster_context}')\n",
|
"print (f'Creating Azure Arc Data Controller: {arc_data_controller_name} using configuration {arc_cluster_context}')\n",
|
||||||
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
|
||||||
"os.environ[\"AZDATA_USERNAME\"] = arc_admin_username\n",
|
"os.environ[\"AZDATA_USERNAME\"] = arc_admin_username\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
|
||||||
"os.environ[\"DOCKER_USERNAME\"] = arc_docker_username\n",
|
|
||||||
"os.environ[\"DOCKER_PASSWORD\"] = arc_docker_password\n",
|
|
||||||
"if os.name == 'nt':\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 {arc_data_controller_namespace}')\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 {arc_data_controller_namespace}')\n",
|
||||||
"run_command(f'azdata arc dc create --connectivity-mode {arc_data_controller_connectivity_mode} -n {arc_data_controller_name} -ns {arc_data_controller_namespace} -s {arc_subscription} -g {arc_resource_group} -l {arc_data_controller_location} -sc {arc_data_controller_storage_class} --profile-name {arc_profile}')\n",
|
"run_command(f'azdata arc dc create --connectivity-mode Indirect -n {arc_data_controller_name} -ns {arc_data_controller_namespace} -s {arc_subscription} -g {arc_resource_group} -l {arc_data_controller_location} -sc {arc_data_controller_storage_class} --profile-name {arc_profile}')\n",
|
||||||
"print(f'Azure Arc Data controller cluster: {arc_data_controller_name} created.') "
|
"print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') "
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
|
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
|
||||||
@@ -205,7 +201,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Setting context to created Azure Arc Data controller**"
|
"### **Setting context to created Azure Arc Data Controller**"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "a3ddc701-811d-4058-b3fb-b7295fcf50ae"
|
"azdata_cell_guid": "a3ddc701-811d-4058-b3fb-b7295fcf50ae"
|
||||||
@@ -214,7 +210,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Setting context to data controller.\n",
|
"# Setting context to Data Controller.\n",
|
||||||
"#\n",
|
"#\n",
|
||||||
"run_command(f'kubectl config set-context --current --namespace {arc_data_controller_namespace}')"
|
"run_command(f'kubectl config set-context --current --namespace {arc_data_controller_namespace}')"
|
||||||
],
|
],
|
||||||
@@ -227,7 +223,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Login to the data controller.**\n"
|
"### **Login to the Data Controller.**\n"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "9376b2ab-0edf-478f-9e3c-5ff46ae3501a"
|
"azdata_cell_guid": "9376b2ab-0edf-478f-9e3c-5ff46ae3501a"
|
||||||
@@ -236,9 +232,9 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Login to the data controller.\n",
|
"# Login to the Data Controller.\n",
|
||||||
"#\n",
|
"#\n",
|
||||||
"run_command(f'azdata login -n {arc_data_controller_namespace}')"
|
"run_command(f'azdata login --namespace {arc_data_controller_namespace}')"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "9aed0c5a-2c8a-4ad7-becb-60281923a196"
|
"azdata_cell_guid": "9aed0c5a-2c8a-4ad7-becb-60281923a196"
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"\n",
|
"\n",
|
||||||
" \n",
|
" \n",
|
||||||
"## Deploy a PostgreSQL server group on an existing Azure Arc data cluster\n",
|
"## Create a PostgreSQL Hyperscale - Azure Arc on an existing Azure Arc Data Controller\n",
|
||||||
" \n",
|
" \n",
|
||||||
"This notebook walks through the process of deploying a PostgreSQL server group on an existing Azure Arc data cluster.\n",
|
"This notebook walks through the process of creating a PostgreSQL Hyperscale - Azure Arc on an existing Azure Arc Data Controller.\n",
|
||||||
" \n",
|
" \n",
|
||||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||||
"* Make sure you have the target Azure Arc data cluster already created.\n",
|
"* Make sure you have the target Azure Arc Data Controller already created.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run All\" button to run the notebook</font></span>"
|
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run All\" button to run the notebook</font></span>"
|
||||||
],
|
],
|
||||||
@@ -41,7 +41,21 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Check prerequisites**"
|
"### **Prerequisites** \n",
|
||||||
|
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||||
|
" \n",
|
||||||
|
"|Tools|Description|Installation|\n",
|
||||||
|
"|---|---|---|\n",
|
||||||
|
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "20fe3985-a01e-461c-bce0-235f7606cc3c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### **Setup and Check Prerequisites**"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "68531b91-ddce-47d7-a1d8-2ddc3d17f3e7"
|
"azdata_cell_guid": "68531b91-ddce-47d7-a1d8-2ddc3d17f3e7"
|
||||||
@@ -75,100 +89,20 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"#### **Ensure Postgres Server Group name and password exist**"
|
"### **Set variables**\n",
|
||||||
|
"\n",
|
||||||
|
"#### \n",
|
||||||
|
"\n",
|
||||||
|
"Generated by Azure Data Studio using the values collected in the 'Deploy PostgreSQL Hyperscale - Azure Arc instance' wizard"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "68ec0760-27d1-4ded-9a9f-89077c40b8bb"
|
"azdata_cell_guid": "68ec0760-27d1-4ded-9a9f-89077c40b8bb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"# Required Values\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" server_group_name = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" postgres_password = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD was not defined. Exiting\\n') \n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" postgres_storage_class_data = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA was not defined. Exiting\\n') \n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_LOGS\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" postgres_storage_class_logs = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_LOGS\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_LOGS was not defined. Exiting\\n') \n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_BACKUPS\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" postgres_storage_class_backups = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_BACKUPS\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_BACKUPS was not defined. Exiting\\n') \n",
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "53769960-e1f8-4477-b4cf-3ab1ea34348b",
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"#### **Get optional parameters for the PostgreSQL server group**"
|
"### **Creating the PostgreSQL Hyperscale - Azure Arc instance**"
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "68ec0760-27d1-4ded-9a9f-89077c40b8bb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"server_group_workers = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_WORKERS\"]\n",
|
|
||||||
"server_group_port = os.environ.get(\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PORT\")\n",
|
|
||||||
"server_group_cores_request = os.environ.get(\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST\")\n",
|
|
||||||
"server_group_cores_limit = os.environ.get(\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT\")\n",
|
|
||||||
"server_group_memory_request = os.environ.get(\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST\")\n",
|
|
||||||
"server_group_memory_limit = os.environ.get(\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT\")"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "53769960-e1f8-4477-b4cf-3ab1ea34348b",
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"source": [
|
|
||||||
"### **Installing PostgreSQL server group**"
|
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
@@ -179,7 +113,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Login to the data controller.\n",
|
"# Login to the data controller.\n",
|
||||||
"#\n",
|
"#\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
"out=run_command()"
|
"out=run_command()"
|
||||||
],
|
],
|
||||||
@@ -192,17 +126,22 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"print (f'Creating a PostgreSQL server group on Azure Arc')\n",
|
"print (f'Creating the PostgreSQL Hyperscale - Azure Arc instance')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"workers_option = f' -w {server_group_workers}' if server_group_workers else \"\"\n",
|
"workers_option = f' -w {postgres_server_group_workers}' if postgres_server_group_workers else \"\"\n",
|
||||||
"port_option = f' --port \"{server_group_port}\"' if server_group_port else \"\"\n",
|
"port_option = f' --port \"{postgres_server_group_port}\"' if postgres_server_group_port else \"\"\n",
|
||||||
"cores_request_option = f' -cr \"{server_group_cores_request}\"' if server_group_cores_request else \"\"\n",
|
"engine_version_option = f' -ev {postgres_server_group_engine_version}' if postgres_server_group_engine_version else \"\"\n",
|
||||||
"cores_limit_option = f' -cl \"{server_group_cores_limit}\"' if server_group_cores_limit else \"\"\n",
|
"extensions_option = f' --extensions \"{postgres_server_group_extensions}\"' if postgres_server_group_extensions else \"\"\n",
|
||||||
"memory_request_option = f' -mr \"{server_group_memory_request}Mi\"' if server_group_memory_request else \"\"\n",
|
"volume_size_data_option = f' -vsd {postgres_server_group_volume_size_data}Gi' if postgres_server_group_volume_size_data else \"\"\n",
|
||||||
"memory_limit_option = f' -ml \"{server_group_memory_limit}Mi\"' if server_group_memory_limit else \"\"\n",
|
"volume_size_logs_option = f' -vsl {postgres_server_group_volume_size_logs}Gi' if postgres_server_group_volume_size_logs else \"\"\n",
|
||||||
|
"volume_size_backups_option = f' -vsb {postgres_server_group_volume_size_backups}Gi' if postgres_server_group_volume_size_backups else \"\"\n",
|
||||||
|
"cores_request_option = f' -cr \"{postgres_server_group_cores_request}\"' if postgres_server_group_cores_request else \"\"\n",
|
||||||
|
"cores_limit_option = f' -cl \"{postgres_server_group_cores_limit}\"' if postgres_server_group_cores_limit else \"\"\n",
|
||||||
|
"memory_request_option = f' -mr \"{postgres_server_group_memory_request}Gi\"' if postgres_server_group_memory_request else \"\"\n",
|
||||||
|
"memory_limit_option = f' -ml \"{postgres_server_group_memory_limit}Gi\"' if postgres_server_group_memory_limit else \"\"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = postgres_password\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
||||||
"cmd = f'azdata arc postgres server create -n {server_group_name} -scd {postgres_storage_class_data} -scl {postgres_storage_class_logs} -scb {postgres_storage_class_backups}{workers_option}{port_option}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
|
"cmd = f'azdata arc postgres server create -n {postgres_server_group_name} -scd {postgres_storage_class_data} -scl {postgres_storage_class_logs} -scb {postgres_storage_class_backups}{workers_option}{port_option}{engine_version_option}{extensions_option}{volume_size_data_option}{volume_size_logs_option}{volume_size_backups_option}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
|
||||||
"out=run_command()"
|
"out=run_command()"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -25,12 +25,12 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"\n",
|
"\n",
|
||||||
" \n",
|
" \n",
|
||||||
"## Deploy Azure SQL managed instance on an existing Azure Arc data cluster\n",
|
"## Create SQL managed instance - Azure Arc on an existing Azure Arc Data Controller\n",
|
||||||
" \n",
|
" \n",
|
||||||
"This notebook walks through the process of deploying a <a href=\"https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance\">Azure SQL managed instance</a> on an existing Azure Arc data cluster.\n",
|
"This notebook walks through the process of creating a <a href=\"https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance\">SQL managed instance - Azure Arc</a> on an existing Azure Arc Data Controller.\n",
|
||||||
" \n",
|
" \n",
|
||||||
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
"* Follow the instructions in the **Prerequisites** cell to install the tools if not already installed.\n",
|
||||||
"* Make sure you have the target Azure Arc data cluster already created.\n",
|
"* Make sure you have the target Azure Arc Data Controller already created.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run All\" button to run the notebook</font></span>"
|
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run All\" button to run the notebook</font></span>"
|
||||||
],
|
],
|
||||||
@@ -41,7 +41,21 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Check prerequisites**"
|
"### **Prerequisites** \n",
|
||||||
|
"Ensure the following tools are installed and added to PATH before proceeding.\n",
|
||||||
|
" \n",
|
||||||
|
"|Tools|Description|Installation|\n",
|
||||||
|
"|---|---|---|\n",
|
||||||
|
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "d1c8258e-9efd-4380-a48c-cd675423ed2f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### **Setup and Check Prerequisites**"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "68531b91-ddce-47d7-a1d8-2ddc3d17f3e7"
|
"azdata_cell_guid": "68531b91-ddce-47d7-a1d8-2ddc3d17f3e7"
|
||||||
@@ -75,70 +89,20 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"#### **Ensure SQL instance name, username and password exist**"
|
"### **Set variables**\n",
|
||||||
|
"\n",
|
||||||
|
"#### \n",
|
||||||
|
"\n",
|
||||||
|
"Generated by Azure Data Studio using the values collected in the 'Deploy Azure SQL managed instance - Azure Arc' wizard"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "68ec0760-27d1-4ded-9a9f-89077c40b8bb"
|
"azdata_cell_guid": "68ec0760-27d1-4ded-9a9f-89077c40b8bb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"# Required Values\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_INSTANCE_NAME\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" mssql_instance_name = os.environ[\"AZDATA_NB_VAR_SQL_INSTANCE_NAME\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_INSTANCE_NAME was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_PASSWORD\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_PASSWORD was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" mssql_storage_class_data = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA was not defined. Exiting\\n')\n",
|
|
||||||
"\n",
|
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\" in os.environ\n",
|
|
||||||
"if env_var:\n",
|
|
||||||
" mssql_storage_class_logs = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\"]\n",
|
|
||||||
"else:\n",
|
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS was not defined. Exiting\\n') \n",
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "53769960-e1f8-4477-b4cf-3ab1ea34348b",
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### **Installing Managed SQL Instance**"
|
"### **Creating the SQL managed instance - Azure Arc instance**"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
@@ -149,7 +113,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Login to the data controller.\n",
|
"# Login to the data controller.\n",
|
||||||
"#\n",
|
"#\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
"out=run_command()"
|
"out=run_command()"
|
||||||
],
|
],
|
||||||
@@ -162,10 +126,16 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"print (f'Creating Managed SQL Server instance on Azure Arc')\n",
|
"print (f'Creating the SQL managed instance - Azure Arc instance')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = mssql_password\n",
|
"cores_request_option = f' -cr \"{sql_cores_request}\"' if sql_cores_request else \"\"\n",
|
||||||
"cmd = f'azdata arc sql mi create -n {mssql_instance_name} -scd {mssql_storage_class_data} -scl {mssql_storage_class_logs}'\n",
|
"cores_limit_option = f' -cl \"{sql_cores_limit}\"' if sql_cores_limit else \"\"\n",
|
||||||
|
"memory_request_option = f' -mr \"{sql_memory_request}Gi\"' if sql_memory_request else \"\"\n",
|
||||||
|
"memory_limit_option = f' -ml \"{sql_memory_limit}Gi\"' if sql_memory_limit else \"\"\n",
|
||||||
|
"\n",
|
||||||
|
"os.environ[\"AZDATA_USERNAME\"] = sql_username\n",
|
||||||
|
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
||||||
|
"cmd = f'azdata arc sql mi create -n {sql_instance_name} -scd {sql_storage_class_data} -scl {sql_storage_class_logs}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
|
||||||
"out=run_command()"
|
"out=run_command()"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "arc",
|
"name": "arc",
|
||||||
"displayName": "%arc.displayName%",
|
"displayName": "%arc.displayName%",
|
||||||
"description": "%arc.description%",
|
"description": "%arc.description%",
|
||||||
"version": "0.3.5",
|
"version": "0.5.1",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCommand:arc.connectToController",
|
"onCommand:arc.connectToController",
|
||||||
"onCommand:arc.createController",
|
"onCommand:arc.createController",
|
||||||
|
"onCommand:azdata.resource.deploy",
|
||||||
"onView:azureArc"
|
"onView:azureArc"
|
||||||
],
|
],
|
||||||
"extensionDependencies": [
|
"extensionDependencies": [
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
"view/item/context": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "arc.openDashboard",
|
"command": "arc.openDashboard",
|
||||||
"when": "view == azureArc && viewItem != postgresInstances",
|
"when": "view == azureArc && viewItem",
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"notebookWizard": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.arc.control.plane.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.arc.data.controller.ipynb",
|
||||||
"type": "new-arc-control-plane",
|
"type": "new-arc-control-plane",
|
||||||
"doneAction": {
|
"doneAction": {
|
||||||
"label": "%deploy.done.action%"
|
"label": "%deploy.done.action%"
|
||||||
@@ -158,52 +159,30 @@
|
|||||||
"generateSummaryPage": false,
|
"generateSummaryPage": false,
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.select.cluster.title%",
|
"title": "%arc.data.controller.select.cluster.title%",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "kube_cluster_context_picker",
|
"type": "kube_cluster_context_picker",
|
||||||
"label": "%arc.control.plane.kube.cluster.context%",
|
"label": "%arc.data.controller.kube.cluster.context%",
|
||||||
"required": true,
|
"required": true,
|
||||||
"inputWidth": "350px",
|
"inputWidth": "350px",
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_CLUSTER_CONTEXT",
|
"variableName": "AZDATA_NB_VAR_ARC_CLUSTER_CONTEXT",
|
||||||
"configFileVariableName": "AZDATA_NB_VAR_ARC_CONFIG_FILE"
|
"configFileVariableName": "AZDATA_NB_VAR_ARC_CONFIG_FILE"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "%arc.control.plane.container.registry.title%",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"label": "%arc.control.plane.container.registry.name%",
|
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DOCKER_USERNAME",
|
|
||||||
"type": "text",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": "22cda7bb-2eb1-419e-a742-8710c313fe79",
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.control.plane.container.registry.password%",
|
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DOCKER_PASSWORD",
|
|
||||||
"type": "password",
|
|
||||||
"userName": "docker",
|
|
||||||
"confirmationRequired": false,
|
|
||||||
"defaultValue": "",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.cluster.config.profile.title%",
|
"title": "%arc.data.controller.cluster.config.profile.title%",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"label": "%arc.control.plane.cluster.config.profile%",
|
"label": "%arc.data.controller.cluster.config.profile%",
|
||||||
"required": true,
|
"required": true,
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_PROFILE",
|
"variableName": "AZDATA_NB_VAR_ARC_PROFILE",
|
||||||
"editable": false,
|
"editable": false,
|
||||||
@@ -220,14 +199,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.data.controller.create.title%",
|
"title": "%arc.data.controller.data.controller.create.title%",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.project.details.title%",
|
"title": "%arc.data.controller.project.details.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"label": "%arc.control.plane.project.details.description%",
|
"label": "%arc.data.controller.project.details.description%",
|
||||||
"labelWidth": "600px"
|
"labelWidth": "600px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -240,30 +219,30 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.data.controller.details.title%",
|
"title": "%arc.data.controller.data.controller.details.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"label": "%arc.control.plane.data.controller.details.description%",
|
"label": "%arc.data.controller.data.controller.details.description%",
|
||||||
"labelWidth": "600px"
|
"labelWidth": "600px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "%arc.control.plane.arc.data.controller.namespace%",
|
"label": "%arc.data.controller.arc.data.controller.namespace%",
|
||||||
"textValidationRequired": true,
|
"textValidationRequired": true,
|
||||||
"textValidationRegex": "^[a-z0-9]([-a-z0-9]{0,11}[a-z0-9])?$",
|
"textValidationRegex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$",
|
||||||
"textValidationDescription": "%arc.control.plane.arc.data.controller.namespace.validation.description%",
|
"textValidationDescription": "%arc.data.controller.arc.data.controller.namespace.validation.description%",
|
||||||
"defaultValue": "arc",
|
"defaultValue": "arc",
|
||||||
"required": true,
|
"required": true,
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE"
|
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "%arc.control.plane.arc.data.controller.name%",
|
"label": "%arc.data.controller.arc.data.controller.name%",
|
||||||
"textValidationRequired": true,
|
"textValidationRequired": true,
|
||||||
"textValidationRegex": "^[a-z0-9]([-a-z0-9]{0,11}[a-z0-9])?$",
|
"textValidationRegex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$",
|
||||||
"textValidationDescription": "%arc.control.plane.arc.data.controller.name.validation.description%",
|
"textValidationDescription": "%arc.data.controller.arc.data.controller.name.validation.description%",
|
||||||
"defaultValue": "arc-cp1",
|
"defaultValue": "arc-dc",
|
||||||
"required": true,
|
"required": true,
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME"
|
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME"
|
||||||
},
|
},
|
||||||
@@ -276,42 +255,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "azure_locations",
|
"type": "azure_locations",
|
||||||
"label": "%arc.control.plane.arc.data.controller.location%",
|
"label": "%arc.data.controller.arc.data.controller.location%",
|
||||||
"defaultValue": "eastus",
|
"defaultValue": "eastus",
|
||||||
"required": true,
|
"required": true,
|
||||||
"locationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION",
|
"locationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION",
|
||||||
"displayLocationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_DISPLAY_LOCATION",
|
"displayLocationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_DISPLAY_LOCATION",
|
||||||
"locations": [
|
"locations": [
|
||||||
|
"australiaeast",
|
||||||
|
"centralus",
|
||||||
"eastus",
|
"eastus",
|
||||||
"eastus2",
|
"eastus2",
|
||||||
"centralus",
|
"francecentral",
|
||||||
"westus2",
|
"japaneast",
|
||||||
|
"koreacentral",
|
||||||
|
"northeurope",
|
||||||
"southeastasia",
|
"southeastasia",
|
||||||
"westeurope"
|
"uksouth",
|
||||||
|
"westeurope",
|
||||||
|
"westus2"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "options",
|
|
||||||
"label": "%arc.control.plane.arc.data.controller.connectivity.mode%",
|
|
||||||
"options": {
|
|
||||||
"values": [
|
|
||||||
"Indirect",
|
|
||||||
"Direct"
|
|
||||||
],
|
|
||||||
"defaultValue": "Indirect",
|
|
||||||
"optionsType": "radio"
|
|
||||||
},
|
|
||||||
"enabled": false,
|
|
||||||
"required": true,
|
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.admin.account.title%",
|
"title": "%arc.data.controller.admin.account.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.admin.account.name%",
|
"label": "%arc.data.controller.admin.account.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_ADMIN_USERNAME",
|
"variableName": "AZDATA_NB_VAR_ARC_ADMIN_USERNAME",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -319,12 +289,12 @@
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.admin.account.password%",
|
"label": "%arc.data.controller.admin.account.password%",
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_ADMIN_PASSWORD",
|
"variableName": "AZDATA_NB_VAR_ARC_ADMIN_PASSWORD",
|
||||||
"type": "sql_password",
|
"type": "sql_password",
|
||||||
"userName": "arcadmin",
|
"userName": "arcadmin",
|
||||||
"confirmationRequired": true,
|
"confirmationRequired": true,
|
||||||
"confirmationLabel": "%arc.control.plane.admin.account.confirm.password%",
|
"confirmationLabel": "%arc.data.controller.admin.account.confirm.password%",
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
@@ -333,7 +303,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.data.controller.create.summary.title%",
|
"title": "%arc.data.controller.data.controller.create.summary.title%",
|
||||||
"isSummaryPage": true,
|
"isSummaryPage": true,
|
||||||
"fieldHeight": "16px",
|
"fieldHeight": "16px",
|
||||||
"sections": [
|
"sections": [
|
||||||
@@ -349,7 +319,7 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.arc.data.controller%",
|
"label": "%arc.data.controller.summary.arc.data.controller%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"labelWidth": "185px"
|
"labelWidth": "185px"
|
||||||
@@ -359,7 +329,7 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.estimated.cost.per.month%",
|
"label": "%arc.data.controller.summary.estimated.cost.per.month%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"labelWidth": "190px",
|
"labelWidth": "190px",
|
||||||
@@ -376,7 +346,7 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.arc.by.microsoft%",
|
"label": "%arc.data.controller.summary.arc.by.microsoft%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"labelWidth": "185px"
|
"labelWidth": "185px"
|
||||||
}
|
}
|
||||||
@@ -385,7 +355,7 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.free%",
|
"label": "%arc.data.controller.summary.free%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
@@ -403,10 +373,10 @@
|
|||||||
"label": "{0}",
|
"label": "{0}",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"labelWidth": "67px",
|
"labelWidth": "69px",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"text": "%arc.control.plane.summary.arc.terms.of.use%",
|
"text": "%arc.data.controller.summary.arc.terms.of.use%",
|
||||||
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -423,10 +393,10 @@
|
|||||||
"label": "{0}",
|
"label": "{0}",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"labelWidth": "102px",
|
"labelWidth": "100px",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"text": "%arc.control.plane.summary.arc.terms.privacy.policy%",
|
"text": "%arc.data.controller.summary.arc.terms.privacy.policy%",
|
||||||
"url": "https://go.microsoft.com/fwlink/?linkid=512132"
|
"url": "https://go.microsoft.com/fwlink/?linkid=512132"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -438,17 +408,17 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.summary.terms%",
|
"title": "%arc.data.controller.summary.terms%",
|
||||||
"fieldHeight": "88px",
|
"fieldHeight": "88px",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.terms.description%",
|
"label": "%arc.data.controller.summary.terms.description%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"labelWidth": "750px",
|
"labelWidth": "750px",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"text": "%arc.control.plane.summary.terms.link.text%",
|
"text": "%arc.data.controller.summary.terms.link.text%",
|
||||||
"url": "https://go.microsoft.com/fwlink/?linkid=2045624"
|
"url": "https://go.microsoft.com/fwlink/?linkid=2045624"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -456,76 +426,64 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.summary.kubernetes%",
|
"title": "%arc.data.controller.summary.kubernetes%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.kube.config.file.path%",
|
"label": "%arc.data.controller.summary.kube.config.file.path%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_CONFIG_FILE)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_CONFIG_FILE)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.cluster.context%",
|
"label": "%arc.data.controller.summary.cluster.context%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_CLUSTER_CONTEXT)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_CLUSTER_CONTEXT)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.profile%",
|
"label": "%arc.data.controller.summary.profile%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_PROFILE)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_PROFILE)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.username%",
|
"label": "%arc.data.controller.summary.username%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_ADMIN_USERNAME)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_ADMIN_USERNAME)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.control.plane.summary.docker.username%",
|
|
||||||
"type": "readonly_text",
|
|
||||||
"isEvaluated": true,
|
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DOCKER_USERNAME)"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.control.plane.summary.azure%",
|
"title": "%arc.data.controller.summary.azure%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.data.controller.namespace%",
|
"label": "%arc.data.controller.summary.data.controller.namespace%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.data.controller.name%",
|
"label": "%arc.data.controller.summary.data.controller.name%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.data.controller.connectivity.mode%",
|
"label": "%arc.data.controller.summary.subscription%",
|
||||||
"type": "readonly_text",
|
|
||||||
"isEvaluated": true,
|
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.control.plane.summary.subscription%",
|
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DISPLAY_SUBSCRIPTION)",
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_DISPLAY_SUBSCRIPTION)",
|
||||||
"inputWidth": "600"
|
"inputWidth": "600"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.resource.group%",
|
"label": "%arc.data.controller.summary.resource.group%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_RESOURCE_GROUP)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_RESOURCE_GROUP)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.control.plane.summary.location%",
|
"label": "%arc.data.controller.summary.location%",
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_DISPLAY_LOCATION)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_DISPLAY_LOCATION)"
|
||||||
@@ -542,7 +500,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.1.0"
|
"version": "20.2.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": true
|
"when": true
|
||||||
@@ -561,7 +519,7 @@
|
|||||||
"tags": ["Hybrid", "SQL Server"],
|
"tags": ["Hybrid", "SQL Server"],
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"dialog": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
|
||||||
"doneAction": {
|
"doneAction": {
|
||||||
"label": "%deploy.done.action%"
|
"label": "%deploy.done.action%"
|
||||||
@@ -576,14 +534,16 @@
|
|||||||
"generateSummaryPage": false,
|
"generateSummaryPage": false,
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"title": "",
|
"title": "%arc.sql.wizard.page1.title%",
|
||||||
|
"labelWidth": "175px",
|
||||||
|
"inputWidth": "280px",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"title": "%arc.sql.settings.section.title%",
|
"title": "%arc.sql.connection.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.controller%",
|
"label": "%arc.controller%",
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
"variableName": "",
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"editable": false,
|
"editable": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -597,23 +557,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionsType": "dropdown"
|
"optionsType": "dropdown"
|
||||||
},
|
}
|
||||||
"labelWidth": "100%"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.sql.instance.name%",
|
"label": "%arc.sql.instance.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"defaultValue": "sqlinstance1",
|
"defaultValue": "sqlinstance1",
|
||||||
|
"description": "%arc.sql.invalid.instance.name%",
|
||||||
"required": true,
|
"required": true,
|
||||||
"labelWidth": "100%"
|
"textValidationRequired": true,
|
||||||
|
"textValidationRegex": "^[a-z]([-a-z0-9]{0,11}[a-z0-9])?$",
|
||||||
|
"textValidationDescription": "%arc.sql.invalid.instance.name%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.sql.username%",
|
"label": "%arc.sql.username%",
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_USERNAME",
|
"variableName": "AZDATA_NB_VAR_SQL_USERNAME",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"defaultValue": "sa",
|
"description": "%arc.sql.invalid.username%",
|
||||||
"enabled": false
|
"required": true,
|
||||||
|
"textValidationRequired": true,
|
||||||
|
"textValidationRegex": "^(?!sa$)",
|
||||||
|
"textValidationDescription": "%arc.sql.invalid.username%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.password%",
|
"label": "%arc.password%",
|
||||||
@@ -624,7 +589,12 @@
|
|||||||
"confirmationLabel": "%arc.confirm.password%",
|
"confirmationLabel": "%arc.confirm.password%",
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
"required": true
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "%arc.sql.instance.settings.section.title%",
|
||||||
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.storage-class.data.label%",
|
"label": "%arc.storage-class.data.label%",
|
||||||
"description": "%arc.sql.storage-class.data.description%",
|
"description": "%arc.sql.storage-class.data.description%",
|
||||||
@@ -638,6 +608,38 @@
|
|||||||
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS",
|
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS",
|
||||||
"type": "kube_storage_class",
|
"type": "kube_storage_class",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.cores-request.label%",
|
||||||
|
"description": "%arc.sql.cores-request.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
|
||||||
|
"type": "number",
|
||||||
|
"min": 1,
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.cores-limit.label%",
|
||||||
|
"description": "%arc.sql.cores-limit.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_CORES_LIMIT",
|
||||||
|
"type": "number",
|
||||||
|
"min": 1,
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.memory-request.label%",
|
||||||
|
"description": "%arc.sql.memory-request.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
|
||||||
|
"type": "number",
|
||||||
|
"min": 2,
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.memory-limit.label%",
|
||||||
|
"description": "%arc.sql.memory-limit.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
|
||||||
|
"type": "number",
|
||||||
|
"min": 2,
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -651,7 +653,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.1.0"
|
"version": "20.2.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "true"
|
"when": "true"
|
||||||
@@ -683,7 +685,7 @@
|
|||||||
"tags": ["Hybrid", "PostgreSQL"],
|
"tags": ["Hybrid", "PostgreSQL"],
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"dialog": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb",
|
||||||
"doneAction": {
|
"doneAction": {
|
||||||
"label": "%deploy.done.action%"
|
"label": "%deploy.done.action%"
|
||||||
@@ -698,14 +700,16 @@
|
|||||||
"generateSummaryPage": false,
|
"generateSummaryPage": false,
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"title": "",
|
"title": "%arc.postgres.wizard.page1.title%",
|
||||||
|
"labelWidth": "205px",
|
||||||
|
"inputWidth": "280px",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"title": "%arc.postgres.settings.section.title%",
|
"title": "%arc.postgres.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.controller%",
|
"label": "%arc.controller%",
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
"variableName": "",
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"editable": false,
|
"editable": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -719,17 +723,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionsType": "dropdown"
|
"optionsType": "dropdown"
|
||||||
},
|
}
|
||||||
"labelWidth": "100%"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.name%",
|
"label": "%arc.postgres.server.group.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
"description": "%arc.postgres.server.group.name.validation.description%",
|
||||||
"textValidationRequired": true,
|
"textValidationRequired": true,
|
||||||
"textValidationRegex": "^[a-z]([-a-z0-9]{0,8}[a-z0-9])?$",
|
"textValidationRegex": "^[a-z]([-a-z0-9]{0,10}[a-z0-9])?$",
|
||||||
"textValidationDescription": "%arc.postgres.server.group.name.validation.description%",
|
"textValidationDescription": "%arc.postgres.server.group.name.validation.description%",
|
||||||
"defaultValue": "postgres1",
|
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -742,11 +745,12 @@
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.workers%",
|
"label": "%arc.postgres.server.group.workers.label%",
|
||||||
|
"description": "%arc.postgres.server.group.workers.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_WORKERS",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_WORKERS",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"defaultValue": "1",
|
"defaultValue": "0",
|
||||||
"min": 1
|
"min": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.port%",
|
"label": "%arc.postgres.server.group.port%",
|
||||||
@@ -756,6 +760,27 @@
|
|||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 65535
|
"max": 65535
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.postgres.server.group.engine.version%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_ENGINE_VERSION",
|
||||||
|
"type": "options",
|
||||||
|
"options": [
|
||||||
|
"11",
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"defaultValue": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.postgres.server.group.extensions.label%",
|
||||||
|
"description": "%arc.postgres.server.group.extensions.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_EXTENSIONS",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "%arc.postgres.settings.storage.title%",
|
||||||
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.storage-class.data.label%",
|
"label": "%arc.storage-class.data.label%",
|
||||||
"description": "%arc.postgres.storage-class.data.description%",
|
"description": "%arc.postgres.storage-class.data.description%",
|
||||||
@@ -763,6 +788,14 @@
|
|||||||
"type": "kube_storage_class",
|
"type": "kube_storage_class",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.postgres.server.group.volume.size.data.label%",
|
||||||
|
"description": "%arc.postgres.server.group.volume.size.data.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_VOLUME_SIZE_DATA",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": "5",
|
||||||
|
"min": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.storage-class.logs.label%",
|
"label": "%arc.storage-class.logs.label%",
|
||||||
"description": "%arc.postgres.storage-class.logs.description%",
|
"description": "%arc.postgres.storage-class.logs.description%",
|
||||||
@@ -770,12 +803,28 @@
|
|||||||
"type": "kube_storage_class",
|
"type": "kube_storage_class",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.postgres.server.group.volume.size.logs.label%",
|
||||||
|
"description": "%arc.postgres.server.group.volume.size.logs.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_VOLUME_SIZE_LOGS",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": "5",
|
||||||
|
"min": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.storage-class.backups.label%",
|
"label": "%arc.storage-class.backups.label%",
|
||||||
"description": "%arc.postgres.storage-class.backups.description%",
|
"description": "%arc.postgres.storage-class.backups.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_BACKUPS",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_BACKUPS",
|
||||||
"type": "kube_storage_class",
|
"type": "kube_storage_class",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.postgres.server.group.volume.size.backups.label%",
|
||||||
|
"description": "%arc.postgres.server.group.volume.size.backups.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_VOLUME_SIZE_BACKUPS",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": "5",
|
||||||
|
"min": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -783,28 +832,32 @@
|
|||||||
"title": "%arc.postgres.settings.resource.title%",
|
"title": "%arc.postgres.settings.resource.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.cores.request%",
|
"label": "%arc.postgres.server.group.cores.request.label%",
|
||||||
|
"description": "%arc.postgres.server.group.cores.request.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 0
|
"min": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.cores.limit%",
|
"label": "%arc.postgres.server.group.cores.limit.label%",
|
||||||
|
"description": "%arc.postgres.server.group.cores.limit.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 0
|
"min": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.memory.request%",
|
"label": "%arc.postgres.server.group.memory.request.label%",
|
||||||
|
"description": "%arc.postgres.server.group.memory.request.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 0
|
"min": 0.25
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.memory.limit%",
|
"label": "%arc.postgres.server.group.memory.limit.label%",
|
||||||
|
"description": "%arc.postgres.server.group.memory.limit.description%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 0
|
"min": 0.25
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -814,11 +867,11 @@
|
|||||||
},
|
},
|
||||||
"requiredTools": [
|
"requiredTools": [
|
||||||
{
|
{
|
||||||
"name": "azure-cli"
|
"name": "kubectl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.1.0"
|
"version": "20.2.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "true"
|
"when": "true"
|
||||||
@@ -858,5 +911,10 @@
|
|||||||
"sinon": "^9.0.2",
|
"sinon": "^9.0.2",
|
||||||
"typemoq": "2.1.0",
|
"typemoq": "2.1.0",
|
||||||
"vscodetestcover": "^1.1.0"
|
"vscodetestcover": "^1.1.0"
|
||||||
|
},
|
||||||
|
"__metadata": {
|
||||||
|
"id": "68",
|
||||||
|
"publisherDisplayName": "Microsoft",
|
||||||
|
"publisherId": "Microsoft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,15 +148,15 @@ async function promptInputBox(title: string, options: vscode.InputBoxOptions): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens an input box prompting the user to enter in the name of a resource to delete
|
* Opens an input box prompting the user to enter in the name of an instance to delete
|
||||||
* @param name The name of the resource to delete
|
* @param name The name of the instance to delete
|
||||||
* @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason
|
* @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason
|
||||||
*/
|
*/
|
||||||
export async function promptForResourceDeletion(name: string): Promise<boolean> {
|
export async function promptForInstanceDeletion(name: string): Promise<boolean> {
|
||||||
const title = loc.resourceDeletionWarning(name);
|
const title = loc.instanceDeletionWarning(name);
|
||||||
const options: vscode.InputBoxOptions = {
|
const options: vscode.InputBoxOptions = {
|
||||||
placeHolder: name,
|
placeHolder: name,
|
||||||
validateInput: input => input !== name ? loc.invalidResourceDeletionName(name) : ''
|
validateInput: input => input !== name ? loc.invalidInstanceDeletionName(name) : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
return await promptInputBox(title, options) !== undefined;
|
return await promptInputBox(title, options) !== undefined;
|
||||||
@@ -189,28 +189,15 @@ export async function promptAndConfirmPassword(validate: (input: string) => stri
|
|||||||
/**
|
/**
|
||||||
* Gets the message to display for a given error object that may be a variety of types.
|
* Gets the message to display for a given error object that may be a variety of types.
|
||||||
* @param error The error object
|
* @param error The error object
|
||||||
|
* @param useMessageWithLink Whether to use the messageWithLink - if available
|
||||||
*/
|
*/
|
||||||
export function getErrorMessage(error: any): string {
|
export function getErrorMessage(error: any, useMessageWithLink: boolean = false): string {
|
||||||
|
if (useMessageWithLink && error.messageWithLink) {
|
||||||
|
return error.messageWithLink;
|
||||||
|
}
|
||||||
return error.message ?? error;
|
return error.message ?? error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an instance name from the controller. An instance name will either be just its name
|
|
||||||
* e.g. myinstance or namespace_name e.g. mynamespace_my-instance.
|
|
||||||
* @param instanceName The instance name in one of the formats described
|
|
||||||
*/
|
|
||||||
export function parseInstanceName(instanceName: string | undefined): string {
|
|
||||||
instanceName = instanceName ?? '';
|
|
||||||
const parts: string[] = instanceName.split('_');
|
|
||||||
if (parts.length === 2) {
|
|
||||||
instanceName = parts[1];
|
|
||||||
}
|
|
||||||
else if (parts.length > 2) {
|
|
||||||
throw new Error(`Cannot parse resource '${instanceName}'. Acceptable formats are 'namespace_name' or 'name'.`);
|
|
||||||
}
|
|
||||||
return instanceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an address into its separate ip and port values. Address must be in the form <ip>:<port>
|
* Parses an address into its separate ip and port values. Address must be in the form <ip>:<port>
|
||||||
* @param address The address to parse
|
* @param address The address to parse
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import * as vscode from 'vscode';
|
|||||||
|
|
||||||
export const refreshActionId = 'arc.refresh';
|
export const refreshActionId = 'arc.refresh';
|
||||||
|
|
||||||
|
export const credentialNamespace = 'arcCredentials';
|
||||||
|
|
||||||
|
export const controllerTroubleshootDocsUrl = 'https://aka.ms/arc-data-tsg';
|
||||||
|
export const miaaTroubleshootDocsUrl = 'https://aka.ms/miaa-tsg';
|
||||||
|
|
||||||
export interface IconPath {
|
export interface IconPath {
|
||||||
dark: string;
|
dark: string;
|
||||||
light: string;
|
light: string;
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
|||||||
});
|
});
|
||||||
|
|
||||||
vscode.commands.registerCommand('arc.connectToController', async () => {
|
vscode.commands.registerCommand('arc.connectToController', async () => {
|
||||||
|
const nodes = await treeDataProvider.getChildren();
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
const response = await vscode.window.showErrorMessage(loc.onlyOneControllerSupported, loc.yes, loc.no);
|
||||||
|
if (response !== loc.yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await treeDataProvider.removeController(nodes[0] as ControllerTreeNode);
|
||||||
|
}
|
||||||
const dialog = new ConnectToControllerDialog(treeDataProvider);
|
const dialog = new ConnectToControllerDialog(treeDataProvider);
|
||||||
dialog.showDialog();
|
dialog.showDialog();
|
||||||
const model = await dialog.waitForClose();
|
const model = await dialog.waitForClose();
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import { getErrorMessage } from './common/utils';
|
|||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export const arcDeploymentDeprecation = localize('arc.arcDeploymentDeprecation', "The Arc Deployment extension has been replaced by the Arc extension and has been uninstalled.");
|
export const arcDeploymentDeprecation = localize('arc.arcDeploymentDeprecation', "The Arc Deployment extension has been replaced by the Arc extension and has been uninstalled.");
|
||||||
export function arcControllerDashboard(name: string): string { return localize('arc.controllerDashboard', "Azure Arc Controller Dashboard (Preview) - {0}", name); }
|
export function arcControllerDashboard(name: string): string { return localize('arc.controllerDashboard', "Azure Arc Data Controller Dashboard (Preview) - {0}", name); }
|
||||||
export function miaaDashboard(name: string): string { return localize('arc.miaaDashboard', "Managed Instance Dashboard (Preview) - {0}", name); }
|
export function miaaDashboard(name: string): string { return localize('arc.miaaDashboard', "SQL managed instance - Azure Arc Dashboard (Preview) - {0}", name); }
|
||||||
export function postgresDashboard(name: string): string { return localize('arc.postgresDashboard', "Postgres Dashboard (Preview) - {0}", name); }
|
export function postgresDashboard(name: string): string { return localize('arc.postgresDashboard', "PostgreSQL Hyperscale - Azure Arc Dashboard (Preview) - {0}", name); }
|
||||||
|
|
||||||
export const dataControllersType = localize('arc.dataControllersType', "Azure Arc Data Controller");
|
export const dataControllersType = localize('arc.dataControllersType', "Azure Arc Data Controller");
|
||||||
export const pgSqlType = localize('arc.pgSqlType', "PostgreSQL Server group - Azure Arc");
|
export const pgSqlType = localize('arc.pgSqlType', "PostgreSQL Hyperscale - Azure Arc");
|
||||||
export const miaaType = localize('arc.miaaType', "SQL instance - Azure Arc");
|
export const miaaType = localize('arc.miaaType', "SQL managed instance - Azure Arc");
|
||||||
|
|
||||||
export const overview = localize('arc.overview', "Overview");
|
export const overview = localize('arc.overview', "Overview");
|
||||||
export const connectionStrings = localize('arc.connectionStrings', "Connection Strings");
|
export const connectionStrings = localize('arc.connectionStrings', "Connection Strings");
|
||||||
@@ -72,9 +72,12 @@ export const direct = localize('arc.direct', "Direct");
|
|||||||
export const indirect = localize('arc.indirect', "Indirect");
|
export const indirect = localize('arc.indirect', "Indirect");
|
||||||
export const loading = localize('arc.loading', "Loading...");
|
export const loading = localize('arc.loading', "Loading...");
|
||||||
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
||||||
|
export const noInstancesAvailable = localize('arc.noInstancesAvailable', "No instances available");
|
||||||
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
||||||
|
export function connectToSql(name: string): string { return localize('arc.connectToSql', "Connect to SQL managed instance - Azure Arc ({0})", name); }
|
||||||
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
||||||
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
||||||
|
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
|
||||||
export const controllerName = localize('arc.controllerName', "Name");
|
export const controllerName = localize('arc.controllerName', "Name");
|
||||||
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
||||||
export const username = localize('arc.username', "Username");
|
export const username = localize('arc.username', "Username");
|
||||||
@@ -123,9 +126,11 @@ export const condition = localize('arc.condition', "Condition");
|
|||||||
export const details = localize('arc.details', "Details");
|
export const details = localize('arc.details', "Details");
|
||||||
export const lastUpdated = localize('arc.lastUpdated', "Last updated");
|
export const lastUpdated = localize('arc.lastUpdated', "Last updated");
|
||||||
export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available.");
|
export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available.");
|
||||||
|
export const podsReady = localize('arc.podsReady', "pods ready");
|
||||||
|
|
||||||
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
|
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
|
||||||
export function resourceDeleted(name: string): string { return localize('arc.resourceDeleted', "Resource '{0}' deleted", name); }
|
export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); }
|
||||||
|
export function instanceDeleted(name: string): string { return localize('arc.instanceDeleted', "Instance '{0}' deleted", name); }
|
||||||
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); }
|
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); }
|
||||||
export function clickTheTroubleshootButton(resourceType: string): string { return localize('arc.clickTheTroubleshootButton', "Click the troubleshoot button to open the Azure Arc {0} troubleshooting notebook.", resourceType); }
|
export function clickTheTroubleshootButton(resourceType: string): string { return localize('arc.clickTheTroubleshootButton', "Click the troubleshoot button to open the Azure Arc {0} troubleshooting notebook.", resourceType); }
|
||||||
export function numVCores(vCores: string | undefined): string {
|
export function numVCores(vCores: string | undefined): string {
|
||||||
@@ -146,18 +151,19 @@ export const connectionRequired = localize('arc.connectionRequired', "A connecti
|
|||||||
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
|
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
|
||||||
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
|
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
|
||||||
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
|
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
|
||||||
export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, getErrorMessage(error)); }
|
export function instanceDeletionFailed(name: string, error: any): string { return localize('arc.instanceDeletionFailed', "Failed to delete instance {0}. {1}", name, getErrorMessage(error)); }
|
||||||
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
|
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
|
||||||
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
|
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
|
||||||
|
export function connectToSqlFailed(serverName: string, error: any): string { return localize('arc.connectToSqlFailed', "Could not connect to SQL managed instance - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); }
|
||||||
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function resourceDeletionWarning(name: string): string { return localize('arc.resourceDeletionWarning', "Warning! Deleting a resource is permanent and cannot be undone. To delete the resource '{0}' type the name '{0}' below to proceed.", name); }
|
export function instanceDeletionWarning(name: string): string { return localize('arc.instanceDeletionWarning', "Warning! Deleting an instance is permanent and cannot be undone. To delete the instance '{0}' type the name '{0}' below to proceed.", name); }
|
||||||
export function invalidResourceDeletionName(name: string): string { return localize('arc.invalidResourceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
|
export function invalidInstanceDeletionName(name: string): string { return localize('arc.invalidInstanceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
|
||||||
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
||||||
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
||||||
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error)); }
|
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error, true)); }
|
||||||
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
|
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
|
||||||
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again.");
|
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again.");
|
||||||
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
|
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { ControllerInfo, ResourceType } from 'arc';
|
import { ControllerInfo, ResourceType } from 'arc';
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { parseInstanceName, UserCancelledError } from '../common/utils';
|
import { UserCancelledError } from '../common/utils';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
|
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
|
||||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||||
@@ -20,7 +20,6 @@ export type Registration = {
|
|||||||
export class ControllerModel {
|
export class ControllerModel {
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
private _endpoints: azdataExt.DcEndpointListResult[] = [];
|
private _endpoints: azdataExt.DcEndpointListResult[] = [];
|
||||||
private _namespace: string = '';
|
|
||||||
private _registrations: Registration[] = [];
|
private _registrations: Registration[] = [];
|
||||||
private _controllerConfig: azdataExt.DcConfigShowResult | undefined = undefined;
|
private _controllerConfig: azdataExt.DcConfigShowResult | undefined = undefined;
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ export class ControllerModel {
|
|||||||
}
|
}
|
||||||
public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise<void> {
|
public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise<void> {
|
||||||
await this.azdataLogin(promptReconnect);
|
await this.azdataLogin(promptReconnect);
|
||||||
this._registrations = [];
|
const newRegistrations: Registration[] = [];
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this._azdataApi.azdata.arc.dc.config.show().then(result => {
|
this._azdataApi.azdata.arc.dc.config.show().then(result => {
|
||||||
this._controllerConfig = result.result;
|
this._controllerConfig = result.result;
|
||||||
@@ -125,7 +124,7 @@ export class ControllerModel {
|
|||||||
}),
|
}),
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this._azdataApi.azdata.arc.postgres.server.list().then(result => {
|
this._azdataApi.azdata.arc.postgres.server.list().then(result => {
|
||||||
this._registrations.push(...result.result.map(r => {
|
newRegistrations.push(...result.result.map(r => {
|
||||||
return {
|
return {
|
||||||
instanceName: r.name,
|
instanceName: r.name,
|
||||||
state: r.state,
|
state: r.state,
|
||||||
@@ -134,7 +133,7 @@ export class ControllerModel {
|
|||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
this._azdataApi.azdata.arc.sql.mi.list().then(result => {
|
this._azdataApi.azdata.arc.sql.mi.list().then(result => {
|
||||||
this._registrations.push(...result.result.map(r => {
|
newRegistrations.push(...result.result.map(r => {
|
||||||
return {
|
return {
|
||||||
instanceName: r.name,
|
instanceName: r.name,
|
||||||
state: r.state,
|
state: r.state,
|
||||||
@@ -143,6 +142,7 @@ export class ControllerModel {
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
|
this._registrations = newRegistrations;
|
||||||
this.registrationsLastUpdated = new Date();
|
this.registrationsLastUpdated = new Date();
|
||||||
this._onRegistrationsUpdated.fire(this._registrations);
|
this._onRegistrationsUpdated.fire(this._registrations);
|
||||||
})
|
})
|
||||||
@@ -157,10 +157,6 @@ export class ControllerModel {
|
|||||||
return this._endpoints.find(e => e.name === name);
|
return this._endpoints.find(e => e.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get namespace(): string {
|
|
||||||
return this._namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get registrations(): Registration[] {
|
public get registrations(): Registration[] {
|
||||||
return this._registrations;
|
return this._registrations;
|
||||||
}
|
}
|
||||||
@@ -171,19 +167,10 @@ export class ControllerModel {
|
|||||||
|
|
||||||
public getRegistration(type: ResourceType, name: string): Registration | undefined {
|
public getRegistration(type: ResourceType, name: string): Registration | undefined {
|
||||||
return this._registrations.find(r => {
|
return this._registrations.find(r => {
|
||||||
return r.instanceType === type && parseInstanceName(r.instanceName) === name;
|
return r.instanceType === type && r.instanceName === name;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteRegistration(_type: ResourceType, _name: string) {
|
|
||||||
/* TODO chgagnon
|
|
||||||
if (r && !r.isDeleted && r.customObjectName) {
|
|
||||||
const r = this.getRegistration(type, name);
|
|
||||||
await this._registrationRouter.apiV1RegistrationNsNameIsDeletedDelete(this._namespace, r.customObjectName, true);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* property to for use a display label for this controller
|
* property to for use a display label for this controller
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ResourceInfo } from 'arc';
|
import { MiaaResourceInfo } from 'arc';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { Deferred } from '../common/promise';
|
import { Deferred } from '../common/promise';
|
||||||
import { UserCancelledError } from '../common/utils';
|
import { createCredentialId, parseIpAndPort, UserCancelledError } from '../common/utils';
|
||||||
|
import { credentialNamespace } from '../constants';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
import { ConnectToSqlDialog } from '../ui/dialogs/connectSqlDialog';
|
||||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerModel, Registration } from './controllerModel';
|
import { ControllerModel, Registration } from './controllerModel';
|
||||||
import { ResourceModel } from './resourceModel';
|
import { ResourceModel } from './resourceModel';
|
||||||
@@ -35,8 +37,8 @@ export class MiaaModel extends ResourceModel {
|
|||||||
|
|
||||||
private _refreshPromise: Deferred<void> | undefined = undefined;
|
private _refreshPromise: Deferred<void> | undefined = undefined;
|
||||||
|
|
||||||
constructor(private _controllerModel: ControllerModel, info: ResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
|
constructor(private _controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
super(info, registration);
|
super(_miaaInfo, registration);
|
||||||
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,53 +157,15 @@ export class MiaaModel extends ResourceModel {
|
|||||||
if (this._connectionProfile) {
|
if (this._connectionProfile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let connection: azdata.connection.ConnectionProfile | azdata.connection.Connection | undefined;
|
|
||||||
|
|
||||||
if (this.info.connectionId) {
|
const ipAndPort = parseIpAndPort(this.config?.status.externalEndpoint || '');
|
||||||
try {
|
let connectionProfile: azdata.IConnectionProfile | undefined = {
|
||||||
const connections = await azdata.connection.getConnections();
|
serverName: `${ipAndPort.ip},${ipAndPort.port}`,
|
||||||
const existingConnection = connections.find(conn => conn.connectionId === this.info.connectionId);
|
|
||||||
if (existingConnection) {
|
|
||||||
const credentials = await azdata.connection.getCredentials(this.info.connectionId);
|
|
||||||
if (credentials) {
|
|
||||||
existingConnection.options['password'] = credentials.password;
|
|
||||||
connection = existingConnection;
|
|
||||||
} else {
|
|
||||||
// We need the password so prompt the user for it
|
|
||||||
const connectionProfile: azdata.IConnectionProfile = {
|
|
||||||
serverName: existingConnection.options['serverName'],
|
|
||||||
databaseName: existingConnection.options['databaseName'],
|
|
||||||
authenticationType: existingConnection.options['authenticationType'],
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
connectionName: '',
|
|
||||||
userName: existingConnection.options['user'],
|
|
||||||
password: '',
|
|
||||||
savePassword: false,
|
|
||||||
groupFullName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
id: '',
|
|
||||||
groupId: undefined,
|
|
||||||
options: existingConnection.options
|
|
||||||
};
|
|
||||||
connection = await azdata.connection.openConnectionDialog(['MSSQL'], connectionProfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// ignore - the connection may not necessarily exist anymore and in that case we'll just reprompt for a connection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection) {
|
|
||||||
// We need the password so prompt the user for it
|
|
||||||
const connectionProfile: azdata.IConnectionProfile = {
|
|
||||||
// TODO chgagnon fill in external IP and port
|
|
||||||
// serverName: (this.registration.externalIp && this.registration.externalPort) ? `${this.registration.externalIp},${this.registration.externalPort}` : '',
|
|
||||||
serverName: '',
|
|
||||||
databaseName: '',
|
databaseName: '',
|
||||||
authenticationType: 'SqlLogin',
|
authenticationType: 'SqlLogin',
|
||||||
providerName: 'MSSQL',
|
providerName: 'MSSQL',
|
||||||
connectionName: '',
|
connectionName: '',
|
||||||
userName: 'sa',
|
userName: this._miaaInfo.userName || '',
|
||||||
password: '',
|
password: '',
|
||||||
savePassword: true,
|
savePassword: true,
|
||||||
groupFullName: undefined,
|
groupFullName: undefined,
|
||||||
@@ -210,28 +174,41 @@ export class MiaaModel extends ResourceModel {
|
|||||||
groupId: undefined,
|
groupId: undefined,
|
||||||
options: {}
|
options: {}
|
||||||
};
|
};
|
||||||
// Weren't able to load the existing connection so prompt user for new one
|
|
||||||
connection = await azdata.connection.openConnectionDialog(['MSSQL'], connectionProfile);
|
// If we have the ID stored then try to retrieve the password from previous connections
|
||||||
|
if (this.info.connectionId) {
|
||||||
|
try {
|
||||||
|
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
|
||||||
|
const credentials = await credentialProvider.readCredential(createCredentialId(this._controllerModel.info.id, this.info.resourceType, this.info.name));
|
||||||
|
if (credentials.password) {
|
||||||
|
// Try to connect to verify credentials are still valid
|
||||||
|
connectionProfile.password = credentials.password;
|
||||||
|
// If we don't have a username for some reason then just continue on and we'll prompt for the username below
|
||||||
|
if (connectionProfile.userName) {
|
||||||
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
|
if (!result.connected) {
|
||||||
|
vscode.window.showErrorMessage(loc.connectToSqlFailed(connectionProfile.serverName, result.errorMessage));
|
||||||
|
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
|
||||||
|
connectToSqlDialog.showDialog(connectionProfile);
|
||||||
|
connectionProfile = await connectToSqlDialog.waitForClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Unexpected error fetching password for MIAA instance ${err}`);
|
||||||
|
// ignore - something happened fetching the password so just reprompt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection) {
|
if (!connectionProfile?.userName || !connectionProfile?.password) {
|
||||||
const profile = {
|
// Need to prompt user for password since we don't have one stored
|
||||||
// The option name might be different here based on where it came from
|
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
|
||||||
serverName: connection.options['serverName'] || connection.options['server'],
|
connectToSqlDialog.showDialog(connectionProfile);
|
||||||
databaseName: connection.options['databaseName'] || connection.options['database'],
|
connectionProfile = await connectToSqlDialog.waitForClose();
|
||||||
authenticationType: connection.options['authenticationType'],
|
}
|
||||||
providerName: 'MSSQL',
|
|
||||||
connectionName: '',
|
if (connectionProfile) {
|
||||||
userName: connection.options['user'],
|
this.updateConnectionProfile(connectionProfile);
|
||||||
password: connection.options['password'],
|
|
||||||
savePassword: false,
|
|
||||||
groupFullName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
id: connection.connectionId,
|
|
||||||
groupId: undefined,
|
|
||||||
options: connection.options
|
|
||||||
};
|
|
||||||
this.updateConnectionProfile(profile);
|
|
||||||
} else {
|
} else {
|
||||||
throw new UserCancelledError();
|
throw new UserCancelledError();
|
||||||
}
|
}
|
||||||
@@ -240,6 +217,7 @@ export class MiaaModel extends ResourceModel {
|
|||||||
private async updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void> {
|
private async updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void> {
|
||||||
this._connectionProfile = connectionProfile;
|
this._connectionProfile = connectionProfile;
|
||||||
this.info.connectionId = connectionProfile.id;
|
this.info.connectionId = connectionProfile.id;
|
||||||
|
this._miaaInfo.userName = connectionProfile.userName;
|
||||||
await this._treeDataProvider.saveControllers();
|
await this._treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,278 +4,83 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ResourceInfo } from 'arc';
|
import { ResourceInfo } from 'arc';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
import { Registration } from './controllerModel';
|
import { ControllerModel, Registration } from './controllerModel';
|
||||||
import { ResourceModel } from './resourceModel';
|
import { ResourceModel } from './resourceModel';
|
||||||
|
import { parseIpAndPort } from '../common/utils';
|
||||||
export enum PodRole {
|
|
||||||
Monitor,
|
|
||||||
Router,
|
|
||||||
Shard
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface V1Pod {
|
|
||||||
'apiVersion'?: string;
|
|
||||||
'kind'?: string;
|
|
||||||
'metadata'?: any; // V1ObjectMeta;
|
|
||||||
'spec'?: any; // V1PodSpec;
|
|
||||||
'status'?: V1PodStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface V1PodStatus {
|
|
||||||
'conditions'?: any[]; // Array<V1PodCondition>;
|
|
||||||
'containerStatuses'?: Array<V1ContainerStatus>;
|
|
||||||
'ephemeralContainerStatuses'?: any[]; // Array<V1ContainerStatus>;
|
|
||||||
'hostIP'?: string;
|
|
||||||
'initContainerStatuses'?: any[]; // Array<V1ContainerStatus>;
|
|
||||||
'message'?: string;
|
|
||||||
'nominatedNodeName'?: string;
|
|
||||||
'phase'?: string;
|
|
||||||
'podIP'?: string;
|
|
||||||
'podIPs'?: any[]; // Array<V1PodIP>;
|
|
||||||
'qosClass'?: string;
|
|
||||||
'reason'?: string;
|
|
||||||
'startTime'?: Date | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface V1ContainerStatus {
|
|
||||||
'containerID'?: string;
|
|
||||||
'image'?: string;
|
|
||||||
'imageID'?: string;
|
|
||||||
'lastState'?: any; // V1ContainerState;
|
|
||||||
'name'?: string;
|
|
||||||
'ready'?: boolean;
|
|
||||||
'restartCount'?: number;
|
|
||||||
'started'?: boolean | null;
|
|
||||||
'state'?: any; // V1ContainerState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DuskyObjectModelsDatabaseService {
|
|
||||||
'apiVersion'?: string;
|
|
||||||
'kind'?: string;
|
|
||||||
'metadata'?: any; // V1ObjectMeta;
|
|
||||||
'spec'?: any; // DuskyObjectModelsDatabaseServiceSpec;
|
|
||||||
'status'?: any; // DuskyObjectModelsDatabaseServiceStatus;
|
|
||||||
'arc'?: any; // DuskyObjectModelsDatabaseServiceArcPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface V1Status {
|
|
||||||
'apiVersion'?: string;
|
|
||||||
'code'?: number | null;
|
|
||||||
'details'?: any; // V1StatusDetails;
|
|
||||||
'kind'?: string;
|
|
||||||
'message'?: string;
|
|
||||||
'metadata'?: any; // V1ListMeta;
|
|
||||||
'reason'?: string;
|
|
||||||
'status'?: string;
|
|
||||||
'hasObject'?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DuskyObjectModelsDatabase {
|
|
||||||
'name'?: string;
|
|
||||||
'owner'?: string;
|
|
||||||
'sharded'?: boolean | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PostgresModel extends ResourceModel {
|
export class PostgresModel extends ResourceModel {
|
||||||
private _service?: DuskyObjectModelsDatabaseService;
|
private _config?: azdataExt.PostgresServerShowResult;
|
||||||
private _pods?: V1Pod[];
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
private readonly _onServiceUpdated = new vscode.EventEmitter<DuskyObjectModelsDatabaseService>();
|
|
||||||
private readonly _onPodsUpdated = new vscode.EventEmitter<V1Pod[]>();
|
|
||||||
public onServiceUpdated = this._onServiceUpdated.event;
|
|
||||||
public onPodsUpdated = this._onPodsUpdated.event;
|
|
||||||
public serviceLastUpdated?: Date;
|
|
||||||
public podsLastUpdated?: Date;
|
|
||||||
|
|
||||||
constructor(info: ResourceInfo, registration: Registration) {
|
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
|
||||||
|
public onConfigUpdated = this._onConfigUpdated.event;
|
||||||
|
public configLastUpdated?: Date;
|
||||||
|
|
||||||
|
constructor(private _controllerModel: ControllerModel, info: ResourceInfo, registration: Registration) {
|
||||||
super(info, registration);
|
super(info, registration);
|
||||||
|
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's Kubernetes namespace */
|
/** Returns the configuration of Postgres */
|
||||||
public get namespace(): string | undefined {
|
public get config(): azdataExt.PostgresServerShowResult | undefined {
|
||||||
return ''; // TODO chgagnon return this.info.namespace;
|
return this._config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's name */
|
/** Returns the major version of Postgres */
|
||||||
public get name(): string {
|
public get engineVersion(): string | undefined {
|
||||||
return this.info.name;
|
const kind = this._config?.kind;
|
||||||
|
return kind
|
||||||
|
? kind.substring(kind.lastIndexOf('-') + 1)
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's fully qualified name in the format namespace.name */
|
/** Returns the IP address and port of Postgres */
|
||||||
public get fullName(): string {
|
public get endpoint(): { ip: string, port: string } | undefined {
|
||||||
return `${this.namespace}.${this.name}`;
|
return this._config?.status.externalEndpoint
|
||||||
|
? parseIpAndPort(this._config.status.externalEndpoint)
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's spec */
|
/** Returns the scale configuration of Postgres e.g. '3 nodes, 1.5 vCores, 1Gi RAM, 2Gi storage per node' */
|
||||||
public get service(): DuskyObjectModelsDatabaseService | undefined {
|
public get scaleConfiguration(): string | undefined {
|
||||||
return this._service;
|
if (!this._config) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the service's pods */
|
const cpuLimit = this._config.spec.scheduling?.default?.resources?.limits?.cpu;
|
||||||
public get pods(): V1Pod[] | undefined {
|
const ramLimit = this._config.spec.scheduling?.default?.resources?.limits?.memory;
|
||||||
return this._pods;
|
const cpuRequest = this._config.spec.scheduling?.default?.resources?.requests?.cpu;
|
||||||
}
|
const ramRequest = this._config.spec.scheduling?.default?.resources?.requests?.memory;
|
||||||
|
const storage = this._config.spec.storage?.data?.size;
|
||||||
/** Refreshes the model */
|
const nodes = (this._config.spec.scale?.shards ?? 0) + 1; // An extra node for the coordinator
|
||||||
public async refresh() {
|
|
||||||
await Promise.all([
|
|
||||||
/* TODO enable
|
|
||||||
this._databaseRouter.getDuskyDatabaseService(this.info.namespace || 'test', this.info.name).then(response => {
|
|
||||||
this._service = response.body;
|
|
||||||
this.serviceLastUpdated = new Date();
|
|
||||||
this._onServiceUpdated.fire(this._service);
|
|
||||||
}),
|
|
||||||
this._databaseRouter.getDuskyPods(this.info.namespace || 'test', this.info.name).then(response => {
|
|
||||||
this._pods = response.body;
|
|
||||||
this.podsLastUpdated = new Date();
|
|
||||||
this._onPodsUpdated.fire(this._pods!);
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the service
|
|
||||||
* @param func A function of modifications to apply to the service
|
|
||||||
*/
|
|
||||||
public async update(_func: (service: DuskyObjectModelsDatabaseService) => void): Promise<DuskyObjectModelsDatabaseService> {
|
|
||||||
return <any>undefined;
|
|
||||||
/*
|
|
||||||
// Get the latest spec of the service in case it has changed
|
|
||||||
const service = (await this._databaseRouter.getDuskyDatabaseService(this.info.namespace || 'test', this.info.name)).body;
|
|
||||||
service.status = undefined; // can't update the status
|
|
||||||
func(service);
|
|
||||||
|
|
||||||
return await this._databaseRouter.updateDuskyDatabaseService(this.namespace || 'test', this.name, service).then(r => {
|
|
||||||
this._service = r.body;
|
|
||||||
return this._service;
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deletes the service */
|
|
||||||
public async delete(): Promise<V1Status> {
|
|
||||||
return <any>undefined;
|
|
||||||
// return (await this._databaseRouter.deleteDuskyDatabaseService(this.info.namespace || 'test', this.info.name)).body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a SQL database in the service */
|
|
||||||
public async createDatabase(_db: DuskyObjectModelsDatabase): Promise<DuskyObjectModelsDatabase> {
|
|
||||||
return <any>undefined;
|
|
||||||
// return (await this._databaseRouter.createDuskyDatabase(this.namespace || 'test', this.name, db)).body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IP address and port of the service, preferring external IP over
|
|
||||||
* internal IP. If either field is not available it will be set to undefined.
|
|
||||||
*/
|
|
||||||
public get endpoint(): { ip?: string, port?: number } {
|
|
||||||
const externalIp = this._service?.status?.externalIP;
|
|
||||||
const internalIp = this._service?.status?.internalIP;
|
|
||||||
const externalPort = this._service?.status?.externalPort;
|
|
||||||
const internalPort = this._service?.status?.internalPort;
|
|
||||||
|
|
||||||
return externalIp ? { ip: externalIp, port: externalPort ?? undefined }
|
|
||||||
: internalIp ? { ip: internalIp, port: internalPort ?? undefined }
|
|
||||||
: { ip: undefined, port: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the service's configuration e.g. '3 nodes, 1.5 vCores, 1GiB RAM, 2GiB storage per node' */
|
|
||||||
public get configuration(): string {
|
|
||||||
|
|
||||||
// TODO: Resource requests and limits can be configured per role. Figure out how
|
|
||||||
// to display that in the UI. For now, only show the default configuration.
|
|
||||||
const cpuLimit = this._service?.spec?.scheduling?._default?.resources?.limits?.['cpu'];
|
|
||||||
const ramLimit = this._service?.spec?.scheduling?._default?.resources?.limits?.['memory'];
|
|
||||||
const cpuRequest = this._service?.spec?.scheduling?._default?.resources?.requests?.['cpu'];
|
|
||||||
const ramRequest = this._service?.spec?.scheduling?._default?.resources?.requests?.['memory'];
|
|
||||||
const storage = this._service?.spec?.storage?.volumeSize;
|
|
||||||
const nodes = this.pods?.length;
|
|
||||||
|
|
||||||
let configuration: string[] = [];
|
let configuration: string[] = [];
|
||||||
|
|
||||||
if (nodes) {
|
|
||||||
configuration.push(`${nodes} ${nodes > 1 ? loc.nodes : loc.node}`);
|
configuration.push(`${nodes} ${nodes > 1 ? loc.nodes : loc.node}`);
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer limits if they're provided, otherwise use requests if they're provided
|
// Prefer limits if they're provided, otherwise use requests if they're provided
|
||||||
if (cpuLimit || cpuRequest) {
|
if (cpuLimit || cpuRequest) {
|
||||||
configuration.push(`${this.formatCores(cpuLimit ?? cpuRequest!)} ${loc.vCores}`);
|
configuration.push(`${cpuLimit ?? cpuRequest!} ${loc.vCores}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ramLimit || ramRequest) {
|
if (ramLimit || ramRequest) {
|
||||||
configuration.push(`${this.formatMemory(ramLimit ?? ramRequest!)} ${loc.ram}`);
|
configuration.push(`${ramLimit ?? ramRequest!} ${loc.ram}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storage) {
|
if (storage) {
|
||||||
configuration.push(`${this.formatMemory(storage)} ${loc.storagePerNode}`);
|
configuration.push(`${storage} ${loc.storagePerNode}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration.join(', ');
|
return configuration.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Given a V1Pod, returns its PodRole or undefined if the role isn't known */
|
/** Refreshes the model */
|
||||||
public static getPodRole(pod: V1Pod): PodRole | undefined {
|
public async refresh() {
|
||||||
const name = pod.metadata?.name;
|
await this._controllerModel.azdataLogin();
|
||||||
const role = name?.substring(name.lastIndexOf('-'))[1];
|
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name)).result;
|
||||||
switch (role) {
|
this.configLastUpdated = new Date();
|
||||||
case 'm': return PodRole.Monitor;
|
this._onConfigUpdated.fire(this._config);
|
||||||
case 'r': return PodRole.Router;
|
|
||||||
case 's': return PodRole.Shard;
|
|
||||||
default: return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Given a PodRole, returns its localized name */
|
|
||||||
public static getPodRoleName(role?: PodRole): string {
|
|
||||||
switch (role) {
|
|
||||||
case PodRole.Monitor: return loc.monitor;
|
|
||||||
case PodRole.Router: return loc.coordinator;
|
|
||||||
case PodRole.Shard: return loc.worker;
|
|
||||||
default: return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Given a V1Pod returns its status */
|
|
||||||
public static getPodStatus(pod: V1Pod): string {
|
|
||||||
const phase = pod.status?.phase;
|
|
||||||
if (phase !== 'Running') {
|
|
||||||
return phase ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pods can be in the running phase while some
|
|
||||||
// containers are crashing, so check those too.
|
|
||||||
for (let c of pod.status?.containerStatuses?.filter(c => !c.ready) ?? []) {
|
|
||||||
const wReason = c.state?.waiting?.reason;
|
|
||||||
const tReason = c.state?.terminated?.reason;
|
|
||||||
if (wReason) { return wReason; }
|
|
||||||
if (tReason) { return tReason; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return loc.running;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts millicores to cores (600m -> 0.6 cores)
|
|
||||||
* https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
|
|
||||||
* @param cores The millicores to format e.g. 600m
|
|
||||||
*/
|
|
||||||
private formatCores(cores: string): number {
|
|
||||||
return cores?.endsWith('m') ? +cores.slice(0, -1) / 1000 : +cores;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the memory to end with 'B' e.g:
|
|
||||||
* 1 -> 1B
|
|
||||||
* 1K -> 1KB, 1Ki -> 1KiB
|
|
||||||
* 1M -> 1MB, 1Mi -> 1MiB
|
|
||||||
* 1G -> 1GB, 1Gi -> 1GiB
|
|
||||||
* 1T -> 1TB, 1Ti -> 1TiB
|
|
||||||
* https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
|
|
||||||
* @param memory The amount + unit of memory to format e.g. 1K
|
|
||||||
*/
|
|
||||||
private formatMemory(memory: string): string {
|
|
||||||
return memory && !memory.endsWith('B') ? `${memory}B` : memory;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
||||||
return this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), this.retrieveVariable);
|
// capture 'this' in an arrow function object
|
||||||
|
const retrieveVariable = (key: string) => this.retrieveVariable(key);
|
||||||
|
return this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), retrieveVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getPassword(controller: arc.DataController): Promise<string> {
|
private async getPassword(controller: arc.DataController): Promise<string> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ResourceType } from 'arc';
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getAzurecoreApi, getConnectionModeDisplayText, getDatabaseStateDisplayText, getErrorMessage, getResourceTypeIcon, parseEndpoint, parseInstanceName, parseIpAndPort, promptAndConfirmPassword, promptForResourceDeletion, resourceTypeToDisplayName } from '../../common/utils';
|
import { getAzurecoreApi, getConnectionModeDisplayText, getDatabaseStateDisplayText, getErrorMessage, getResourceTypeIcon, parseEndpoint, parseIpAndPort, promptAndConfirmPassword, promptForInstanceDeletion, resourceTypeToDisplayName } from '../../common/utils';
|
||||||
import { ConnectionMode as ConnectionMode, IconPathHelper } from '../../constants';
|
import { ConnectionMode as ConnectionMode, IconPathHelper } from '../../constants';
|
||||||
import * as loc from '../../localizedConstants';
|
import * as loc from '../../localizedConstants';
|
||||||
import { MockInputBox } from '../stubs';
|
import { MockInputBox } from '../stubs';
|
||||||
@@ -47,24 +47,6 @@ describe('parseEndpoint Method Tests', function (): void {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseInstanceName Method Tests', () => {
|
|
||||||
it('Should parse valid instanceName with namespace correctly', function (): void {
|
|
||||||
should(parseInstanceName('mynamespace_myinstance')).equal('myinstance');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should parse valid instanceName without namespace correctly', function (): void {
|
|
||||||
should(parseInstanceName('myinstance')).equal('myinstance');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should return empty string when undefined value passed in', function (): void {
|
|
||||||
should(parseInstanceName(undefined)).equal('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should return empty string when empty string value passed in', function (): void {
|
|
||||||
should(parseInstanceName('')).equal('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getAzurecoreApi Method Tests', function () {
|
describe('getAzurecoreApi Method Tests', function () {
|
||||||
it('Should get azurecore API correctly', function (): void {
|
it('Should get azurecore API correctly', function (): void {
|
||||||
should(getAzurecoreApi()).not.be.undefined();
|
should(getAzurecoreApi()).not.be.undefined();
|
||||||
@@ -140,7 +122,7 @@ describe('promptForResourceDeletion Method Tests', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Resolves as true when value entered is correct', function (done): void {
|
it('Resolves as true when value entered is correct', function (done): void {
|
||||||
promptForResourceDeletion('myname').then((value: boolean) => {
|
promptForInstanceDeletion('myname').then((value: boolean) => {
|
||||||
value ? done() : done(new Error('Expected return value to be true'));
|
value ? done() : done(new Error('Expected return value to be true'));
|
||||||
});
|
});
|
||||||
mockInputBox.value = 'myname';
|
mockInputBox.value = 'myname';
|
||||||
@@ -148,14 +130,14 @@ describe('promptForResourceDeletion Method Tests', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Resolves as false when input box is closed early', function (done): void {
|
it('Resolves as false when input box is closed early', function (done): void {
|
||||||
promptForResourceDeletion('myname').then((value: boolean) => {
|
promptForInstanceDeletion('myname').then((value: boolean) => {
|
||||||
!value ? done() : done(new Error('Expected return value to be false'));
|
!value ? done() : done(new Error('Expected return value to be false'));
|
||||||
});
|
});
|
||||||
mockInputBox.hide();
|
mockInputBox.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Validation message is set when value entered is incorrect', async function (): Promise<void> {
|
it('Validation message is set when value entered is incorrect', async function (): Promise<void> {
|
||||||
promptForResourceDeletion('myname');
|
promptForInstanceDeletion('myname');
|
||||||
mockInputBox.value = 'wrong value';
|
mockInputBox.value = 'wrong value';
|
||||||
await mockInputBox.triggerAccept();
|
await mockInputBox.triggerAccept();
|
||||||
should(mockInputBox.validationMessage).not.be.equal('', 'Validation message should not be empty after incorrect value entered');
|
should(mockInputBox.validationMessage).not.be.equal('', 'Validation message should not be empty after incorrect value entered');
|
||||||
@@ -260,22 +242,6 @@ describe('getErrorMessage Method Tests', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseInstanceName Method Tests', function () {
|
|
||||||
it('2 part name', function (): void {
|
|
||||||
const name = 'MyName';
|
|
||||||
should(parseInstanceName(`MyNamespace_${name}`)).equal(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('1 part name', function (): void {
|
|
||||||
const name = 'MyName';
|
|
||||||
should(parseInstanceName(name)).equal(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Invalid name', function (): void {
|
|
||||||
should(() => parseInstanceName('Some_Invalid_Name')).throwError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parseIpAndPort', function (): void {
|
describe('parseIpAndPort', function (): void {
|
||||||
it('Valid address', function (): void {
|
it('Valid address', function (): void {
|
||||||
const ip = '127.0.0.1';
|
const ip = '127.0.0.1';
|
||||||
|
|||||||
77
extensions/arc/src/test/mocks/fakeAzdataApi.ts
Normal file
77
extensions/arc/src/test/mocks/fakeAzdataApi.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple fake Azdata Api used to mock the API during tests
|
||||||
|
*/
|
||||||
|
export class FakeAzdataApi implements azdataExt.IAzdataApi {
|
||||||
|
|
||||||
|
public postgresInstances: azdataExt.PostgresServerListResult[] = [];
|
||||||
|
public miaaInstances: azdataExt.SqlMiListResult[] = [];
|
||||||
|
|
||||||
|
//
|
||||||
|
// API Implementation
|
||||||
|
//
|
||||||
|
public get arc() {
|
||||||
|
const self = this;
|
||||||
|
return {
|
||||||
|
dc: {
|
||||||
|
create(_namespace: string, _name: string, _connectivityMode: string, _resourceGroup: string, _location: string, _subscription: string, _profileName?: string, _storageClass?: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
||||||
|
endpoint: {
|
||||||
|
async list(): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> { return <any>{ result: [] }; }
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
list(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> { throw new Error('Method not implemented.'); },
|
||||||
|
async show(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> { return <any>{ result: undefined! }; }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
server: {
|
||||||
|
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
||||||
|
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return <any>{ result: self.postgresInstances }; },
|
||||||
|
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
|
||||||
|
edit(
|
||||||
|
_name: string,
|
||||||
|
_args: {
|
||||||
|
adminPassword?: boolean,
|
||||||
|
coresLimit?: string,
|
||||||
|
coresRequest?: string,
|
||||||
|
engineSettings?: string,
|
||||||
|
extensions?: string,
|
||||||
|
memoryLimit?: string,
|
||||||
|
memoryRequest?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
port?: number,
|
||||||
|
replaceEngineSettings?: boolean,
|
||||||
|
workers?: number
|
||||||
|
},
|
||||||
|
_additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sql: {
|
||||||
|
mi: {
|
||||||
|
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
||||||
|
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return <any>{ result: self.miaaInstances }; },
|
||||||
|
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getPath(): Promise<string> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<any>> {
|
||||||
|
return <any>undefined;
|
||||||
|
}
|
||||||
|
version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
getSemVersion(): any {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,16 +3,21 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ControllerInfo } from 'arc';
|
import { ControllerInfo, ResourceType } from 'arc';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
|
import { MiaaModel } from '../../../models/miaaModel';
|
||||||
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
|
import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
|
||||||
|
import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
|
||||||
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||||
|
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||||
|
|
||||||
describe('AzureArcTreeDataProvider tests', function (): void {
|
describe('AzureArcTreeDataProvider tests', function (): void {
|
||||||
let treeDataProvider: AzureArcTreeDataProvider;
|
let treeDataProvider: AzureArcTreeDataProvider;
|
||||||
@@ -84,6 +89,27 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
let children = await treeDataProvider.getChildren();
|
let children = await treeDataProvider.getChildren();
|
||||||
should(children.length).equal(0, 'After loading we should have 0 children');
|
should(children.length).equal(0, 'After loading we should have 0 children');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return all children of controller after loading', async function (): Promise<void> {
|
||||||
|
const mockArcExtension = TypeMoq.Mock.ofType<vscode.Extension<any>>();
|
||||||
|
const mockArcApi = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
|
mockArcExtension.setup(x => x.exports).returns(() => {
|
||||||
|
return mockArcApi.object;
|
||||||
|
});
|
||||||
|
const fakeAzdataApi = new FakeAzdataApi();
|
||||||
|
fakeAzdataApi.postgresInstances = [{ name: 'pg1', state: '', workers: 0 }];
|
||||||
|
fakeAzdataApi.miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
|
||||||
|
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
|
||||||
|
|
||||||
|
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
|
||||||
|
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
|
||||||
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
|
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
|
||||||
|
const children = await treeDataProvider.getChildren(controllerNode);
|
||||||
|
should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child');
|
||||||
|
should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
|
||||||
|
should(children.length).equal(2, 'Should have excatly 2 children');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removeController', function (): void {
|
describe('removeController', function (): void {
|
||||||
@@ -104,4 +130,31 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
|
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('openResourceDashboard', function (): void {
|
||||||
|
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
|
||||||
|
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
||||||
|
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||||
|
await should(openDashboardPromise).be.rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
|
||||||
|
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
||||||
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
|
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||||
|
await should(openDashboardPromise).be.rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
|
||||||
|
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
||||||
|
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
|
||||||
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
|
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
|
||||||
|
const resourceNode = new MiaaTreeNode(miaaModel, controllerModel);
|
||||||
|
sinon.stub(controllerNode, 'getResourceNode').returns(resourceNode);
|
||||||
|
const showDashboardStub = sinon.stub(resourceNode, 'openDashboard');
|
||||||
|
await treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||||
|
should(showDashboardStub.calledOnce).be.true('showDashboard should have been called exactly once');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
5
extensions/arc/src/typings/arc.d.ts
vendored
5
extensions/arc/src/typings/arc.d.ts
vendored
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
declare module 'arc' {
|
declare module 'arc' {
|
||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Covers defining what the arc extension exports to other extensions
|
* Covers defining what the arc extension exports to other extensions
|
||||||
@@ -20,6 +19,10 @@ declare module 'arc' {
|
|||||||
sqlManagedInstances = 'sqlManagedInstances'
|
sqlManagedInstances = 'sqlManagedInstances'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MiaaResourceInfo = ResourceInfo & {
|
||||||
|
userName?: string
|
||||||
|
};
|
||||||
|
|
||||||
export type ResourceInfo = {
|
export type ResourceInfo = {
|
||||||
name: string,
|
name: string,
|
||||||
resourceType: ResourceType | string,
|
resourceType: ResourceType | string,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { ResourceType } from 'arc';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getConnectionModeDisplayText, getResourceTypeIcon, parseInstanceName, resourceTypeToDisplayName } from '../../../common/utils';
|
import { getConnectionModeDisplayText, getResourceTypeIcon, resourceTypeToDisplayName } from '../../../common/utils';
|
||||||
import { cssStyles, Endpoints, IconPathHelper, iconSize } from '../../../constants';
|
import { cssStyles, Endpoints, IconPathHelper, controllerTroubleshootDocsUrl, iconSize } from '../../../constants';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
@@ -93,7 +93,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
|||||||
headerCssStyles: cssStyles.tableHeader,
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
rowCssStyles: cssStyles.tableRow
|
rowCssStyles: cssStyles.tableRow
|
||||||
}, {
|
}, {
|
||||||
displayName: loc.compute,
|
displayName: loc.state,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '34%',
|
width: '34%',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
@@ -178,18 +178,30 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
|||||||
this._openInAzurePortalButton.onDidClick(async () => {
|
this._openInAzurePortalButton.onDidClick(async () => {
|
||||||
const config = this._controllerModel.controllerConfig;
|
const config = this._controllerModel.controllerConfig;
|
||||||
if (config) {
|
if (config) {
|
||||||
vscode.env.openExternal(vscode.Uri.parse(
|
await vscode.env.openExternal(vscode.Uri.parse(
|
||||||
`https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.dataControllers}/${config.metadata.name}`));
|
`https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.dataControllers}/${config.metadata.name}`));
|
||||||
} else {
|
} else {
|
||||||
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
|
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
label: loc.troubleshoot,
|
||||||
|
iconPath: IconPathHelper.wrench
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
troubleshootButton.onDidClick(async () => {
|
||||||
|
await vscode.env.openExternal(vscode.Uri.parse(controllerTroubleshootDocsUrl));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
||||||
[
|
[
|
||||||
{ component: newInstance },
|
{ component: newInstance },
|
||||||
{ component: refreshButton, toolbarSeparatorAfter: true },
|
{ component: refreshButton, toolbarSeparatorAfter: true },
|
||||||
{ component: this._openInAzurePortalButton }
|
{ component: this._openInAzurePortalButton, toolbarSeparatorAfter: true },
|
||||||
|
{ component: troubleshootButton }
|
||||||
]
|
]
|
||||||
).component();
|
).component();
|
||||||
}
|
}
|
||||||
@@ -219,26 +231,18 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
|||||||
iconHeight: iconSize,
|
iconHeight: iconSize,
|
||||||
iconWidth: iconSize
|
iconWidth: iconSize
|
||||||
}).component();
|
}).component();
|
||||||
let nameComponent: azdata.Component;
|
|
||||||
if (r.instanceType === ResourceType.postgresInstances) {
|
const nameComponent = this.modelView.modelBuilder.hyperlink()
|
||||||
nameComponent = this.modelView.modelBuilder.text()
|
|
||||||
.withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: r.instanceName || '',
|
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
||||||
}).component();
|
|
||||||
} else {
|
|
||||||
nameComponent = this.modelView.modelBuilder.hyperlink()
|
|
||||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||||
label: r.instanceName || '',
|
label: r.instanceName || '',
|
||||||
url: ''
|
url: ''
|
||||||
}).component();
|
}).component();
|
||||||
(<azdata.HyperlinkComponent>nameComponent).onDidClick(async () => {
|
|
||||||
await this._controllerModel.treeDataProvider.openResourceDashboard(this._controllerModel, r.instanceType || '', parseInstanceName(r.instanceName));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO chgagnon
|
this.disposables.push(nameComponent.onDidClick(async () => {
|
||||||
return [imageComponent, nameComponent, resourceTypeToDisplayName(r.instanceType), '-'/* loc.numVCores(r.vCores) */];
|
await this._controllerModel.treeDataProvider.openResourceDashboard(this._controllerModel, r.instanceType || '', r.instanceName);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [imageComponent, nameComponent, resourceTypeToDisplayName(r.instanceType), r.state];
|
||||||
});
|
});
|
||||||
this._arcResourcesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated;
|
this._arcResourcesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import * as azdata from 'azdata';
|
|||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getDatabaseStateDisplayText, promptForResourceDeletion } from '../../../common/utils';
|
import { getDatabaseStateDisplayText, promptForInstanceDeletion } from '../../../common/utils';
|
||||||
import { cssStyles, Endpoints, IconPathHelper } from '../../../constants';
|
import { cssStyles, IconPathHelper, miaaTroubleshootDocsUrl } from '../../../constants';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
import { MiaaModel } from '../../../models/miaaModel';
|
import { MiaaModel } from '../../../models/miaaModel';
|
||||||
@@ -198,13 +198,22 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
deleteButton.onDidClick(async () => {
|
deleteButton.onDidClick(async () => {
|
||||||
deleteButton.enabled = false;
|
deleteButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
if (await promptForResourceDeletion(this._miaaModel.info.name)) {
|
if (await promptForInstanceDeletion(this._miaaModel.info.name)) {
|
||||||
await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name);
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: loc.deletingInstance(this._miaaModel.info.name),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
(_progress, _token) => {
|
||||||
|
return this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name);
|
||||||
|
}
|
||||||
|
);
|
||||||
await this._controllerModel.refreshTreeNode();
|
await this._controllerModel.refreshTreeNode();
|
||||||
vscode.window.showInformationMessage(loc.resourceDeleted(this._miaaModel.info.name));
|
vscode.window.showInformationMessage(loc.instanceDeleted(this._miaaModel.info.name));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.resourceDeletionFailed(this._miaaModel.info.name, error));
|
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._miaaModel.info.name, error));
|
||||||
} finally {
|
} finally {
|
||||||
deleteButton.enabled = true;
|
deleteButton.enabled = true;
|
||||||
}
|
}
|
||||||
@@ -248,6 +257,17 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
label: loc.troubleshoot,
|
||||||
|
iconPath: IconPathHelper.wrench
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
troubleshootButton.onDidClick(async () => {
|
||||||
|
await vscode.env.openExternal(vscode.Uri.parse(miaaTroubleshootDocsUrl));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
||||||
[
|
[
|
||||||
{ component: deleteButton },
|
{ component: deleteButton },
|
||||||
@@ -332,19 +352,13 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private refreshDashboardLinks(): void {
|
private refreshDashboardLinks(): void {
|
||||||
const kibanaEndpoint = this._controllerModel.getEndpoint(Endpoints.logsui);
|
if (this._miaaModel.config) {
|
||||||
if (kibanaEndpoint && this._miaaModel.config) {
|
const kibanaUrl = this._miaaModel.config.status.logSearchDashboard ?? '';
|
||||||
const kibanaQuery = `kubernetes_namespace:"${this._miaaModel.config.metadata.namespace}" and custom_resource_name :"${this._miaaModel.config.metadata.name}"`;
|
|
||||||
const kibanaUrl = `${kibanaEndpoint.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
|
|
||||||
this._kibanaLink.label = kibanaUrl;
|
this._kibanaLink.label = kibanaUrl;
|
||||||
this._kibanaLink.url = kibanaUrl;
|
this._kibanaLink.url = kibanaUrl;
|
||||||
this._kibanaLoading!.loading = false;
|
this._kibanaLoading!.loading = false;
|
||||||
}
|
|
||||||
|
|
||||||
const grafanaEndpoint = this._controllerModel.getEndpoint(Endpoints.metricsui);
|
const grafanaUrl = this._miaaModel.config.status.metricsDashboard ?? '';
|
||||||
if (grafanaEndpoint && this._miaaModel.config) {
|
|
||||||
const grafanaQuery = `var-hostname=${this._miaaModel.info.name}-0`;
|
|
||||||
const grafanaUrl = grafanaEndpoint ? `${grafanaEndpoint.endpoint}/d/40q72HnGk/sql-managed-instance-metrics?${grafanaQuery}` : '';
|
|
||||||
this._grafanaLink.label = grafanaUrl;
|
this._grafanaLink.label = grafanaUrl;
|
||||||
this._grafanaLink.url = grafanaUrl;
|
this._grafanaLink.url = grafanaUrl;
|
||||||
this._grafanaLoading!.loading = false;
|
this._grafanaLoading!.loading = false;
|
||||||
|
|||||||
@@ -1,31 +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 loc from '../../../localizedConstants';
|
|
||||||
import { IconPathHelper } from '../../../constants';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
|
|
||||||
export class PostgresBackupPage extends DashboardPage {
|
|
||||||
protected get title(): string {
|
|
||||||
return loc.backup;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get id(): string {
|
|
||||||
return 'postgres-backup';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get icon(): { dark: string; light: string; } {
|
|
||||||
return IconPathHelper.backup;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get container(): azdata.Component {
|
|
||||||
return this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.backup }).component();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().component();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +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 loc from '../../../localizedConstants';
|
|
||||||
import { IconPathHelper } from '../../../constants';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
|
|
||||||
export class PostgresComputeStoragePage extends DashboardPage {
|
|
||||||
protected get title(): string {
|
|
||||||
return loc.computeAndStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get id(): string {
|
|
||||||
return 'postgres-compute-storage';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get icon(): { dark: string; light: string; } {
|
|
||||||
return IconPathHelper.computeStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get container(): azdata.Component {
|
|
||||||
return this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.computeAndStorage }).component();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().component();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
@@ -12,13 +11,12 @@ import { DashboardPage } from '../../components/dashboardPage';
|
|||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
export class PostgresConnectionStringsPage extends DashboardPage {
|
export class PostgresConnectionStringsPage extends DashboardPage {
|
||||||
private loading?: azdata.LoadingComponent;
|
|
||||||
private keyValueContainer?: KeyValueContainer;
|
private keyValueContainer?: KeyValueContainer;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView);
|
||||||
|
|
||||||
this.disposables.push(this._postgresModel.onServiceUpdated(
|
this.disposables.push(this._postgresModel.onConfigUpdated(
|
||||||
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,44 +59,20 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
|||||||
|
|
||||||
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
|
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
|
||||||
this.disposables.push(this.keyValueContainer);
|
this.disposables.push(this.keyValueContainer);
|
||||||
|
content.addItem(this.keyValueContainer.container);
|
||||||
this.loading = this.modelView.modelBuilder.loadingComponent()
|
|
||||||
.withItem(this.keyValueContainer.container)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
|
||||||
loading: !this._postgresModel.serviceLastUpdated
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
content.addItem(this.loading);
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
return this.modelView.modelBuilder.toolbarContainer().component();
|
||||||
label: loc.refresh,
|
|
||||||
iconPath: IconPathHelper.refresh
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
refreshButton.onDidClick(async () => {
|
|
||||||
refreshButton.enabled = false;
|
|
||||||
try {
|
|
||||||
this.loading!.loading = true;
|
|
||||||
await this._postgresModel.refresh();
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
|
||||||
} finally {
|
|
||||||
refreshButton.enabled = true;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
|
||||||
{ component: refreshButton }
|
|
||||||
]).component();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getConnectionStrings(): KeyValue[] {
|
private getConnectionStrings(): KeyValue[] {
|
||||||
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
|
const endpoint = this._postgresModel.endpoint;
|
||||||
|
if (!endpoint) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new InputKeyValue(this.modelView.modelBuilder, 'ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password={your_password_here};Ssl Mode=Require;`),
|
new InputKeyValue(this.modelView.modelBuilder, 'ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password={your_password_here};Ssl Mode=Require;`),
|
||||||
@@ -115,6 +89,5 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
|||||||
|
|
||||||
private handleServiceUpdated() {
|
private handleServiceUpdated() {
|
||||||
this.keyValueContainer?.refresh(this.getConnectionStrings());
|
this.keyValueContainer?.refresh(this.getConnectionStrings());
|
||||||
this.loading!.loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import { ControllerModel } from '../../../models/controllerModel';
|
|||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
import { PostgresOverviewPage } from './postgresOverviewPage';
|
import { PostgresOverviewPage } from './postgresOverviewPage';
|
||||||
import { PostgresConnectionStringsPage } from './postgresConnectionStringsPage';
|
import { PostgresConnectionStringsPage } from './postgresConnectionStringsPage';
|
||||||
import { PostgresPropertiesPage } from './postgresPropertiesPage';
|
|
||||||
import { Dashboard } from '../../components/dashboard';
|
import { Dashboard } from '../../components/dashboard';
|
||||||
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
|
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
|
||||||
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
|
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
|
||||||
import { PostgresResourceHealthPage } from './postgresResourceHealthPage';
|
|
||||||
|
|
||||||
export class PostgresDashboard extends Dashboard {
|
export class PostgresDashboard extends Dashboard {
|
||||||
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(loc.postgresDashboard(_postgresModel.name), 'ArcPgDashboard');
|
super(loc.postgresDashboard(_postgresModel.info.name), 'ArcPgDashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async showDashboard(): Promise<void> {
|
public async showDashboard(): Promise<void> {
|
||||||
@@ -32,24 +30,22 @@ export class PostgresDashboard extends Dashboard {
|
|||||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||||
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
|
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
|
||||||
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
|
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
|
||||||
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
|
// TODO: Removed properties page while investigating bug where refreshed values don't appear in UI
|
||||||
const resourceHealthPage = new PostgresResourceHealthPage(modelView, this._postgresModel);
|
// const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
|
||||||
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
|
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
|
||||||
const supportRequestPage = new PostgresSupportRequestPage(modelView);
|
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
overviewPage.tab,
|
overviewPage.tab,
|
||||||
{
|
{
|
||||||
title: loc.settings,
|
title: loc.settings,
|
||||||
tabs: [
|
tabs: [
|
||||||
connectionStringsPage.tab,
|
connectionStringsPage.tab
|
||||||
propertiesPage.tab
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: loc.supportAndTroubleshooting,
|
title: loc.supportAndTroubleshooting,
|
||||||
tabs: [
|
tabs: [
|
||||||
resourceHealthPage.tab,
|
|
||||||
diagnoseAndSolveProblemsPage.tab,
|
diagnoseAndSolveProblemsPage.tab,
|
||||||
supportRequestPage.tab
|
supportRequestPage.tab
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
troubleshootButton.onDidClick(() => {
|
troubleshootButton.onDidClick(() => {
|
||||||
process.env['POSTGRES_SERVER_NAMESPACE'] = this._postgresModel.namespace;
|
process.env['POSTGRES_SERVER_NAMESPACE'] = this._postgresModel.config?.metadata.namespace;
|
||||||
process.env['POSTGRES_SERVER_NAME'] = this._postgresModel.name;
|
process.env['POSTGRES_SERVER_NAME'] = this._postgresModel.info.name;
|
||||||
|
process.env['POSTGRES_SERVER_VERSION'] = this._postgresModel.engineVersion;
|
||||||
vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arcDataServices'), true, 'postgres/tsg100-troubleshoot-postgres');
|
vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arcDataServices'), true, 'postgres/tsg100-troubleshoot-postgres');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +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 loc from '../../../localizedConstants';
|
|
||||||
import { IconPathHelper } from '../../../constants';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
|
|
||||||
export class PostgresNetworkingPage extends DashboardPage {
|
|
||||||
protected get title(): string {
|
|
||||||
return loc.networking;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get id(): string {
|
|
||||||
return 'postgres-networking';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get icon(): { dark: string; light: string; } {
|
|
||||||
return IconPathHelper.networking;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get container(): azdata.Component {
|
|
||||||
return this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.networking }).component();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().component();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,33 +5,34 @@
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles, Endpoints } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
import { promptAndConfirmPassword } from '../../../common/utils';
|
import { promptAndConfirmPassword, promptForInstanceDeletion } from '../../../common/utils';
|
||||||
|
import { ResourceType } from 'arc';
|
||||||
|
|
||||||
export class PostgresOverviewPage extends DashboardPage {
|
export class PostgresOverviewPage extends DashboardPage {
|
||||||
|
|
||||||
private propertiesLoading?: azdata.LoadingComponent;
|
private propertiesLoading!: azdata.LoadingComponent;
|
||||||
private kibanaLoading?: azdata.LoadingComponent;
|
private kibanaLoading!: azdata.LoadingComponent;
|
||||||
private grafanaLoading?: azdata.LoadingComponent;
|
private grafanaLoading!: azdata.LoadingComponent;
|
||||||
private nodesTableLoading?: azdata.LoadingComponent;
|
|
||||||
|
|
||||||
private properties?: azdata.PropertiesContainerComponent;
|
private properties!: azdata.PropertiesContainerComponent;
|
||||||
private kibanaLink?: azdata.HyperlinkComponent;
|
private kibanaLink!: azdata.HyperlinkComponent;
|
||||||
private grafanaLink?: azdata.HyperlinkComponent;
|
private grafanaLink!: azdata.HyperlinkComponent;
|
||||||
private nodesTable?: azdata.DeclarativeTableComponent;
|
|
||||||
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView);
|
||||||
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdated())),
|
|
||||||
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
|
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
|
||||||
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
|
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated())));
|
||||||
this._postgresModel.onPodsUpdated(() => this.eventuallyRunOnInitialized(() => this.handlePodsUpdated())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
@@ -60,7 +61,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent()
|
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withItem(this.properties)
|
.withItem(this.properties)
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
loading: !this._controllerModel.registrationsLastUpdated && !this._postgresModel.serviceLastUpdated && !this._postgresModel.podsLastUpdated
|
loading: !this._controllerModel.registrationsLastUpdated && !this._postgresModel.configLastUpdated
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
content.addItem(this.propertiesLoading, { CSSStyles: cssStyles.text });
|
content.addItem(this.propertiesLoading, { CSSStyles: cssStyles.text });
|
||||||
@@ -72,29 +73,26 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
CSSStyles: titleCSS
|
CSSStyles: titleCSS
|
||||||
}).component());
|
}).component());
|
||||||
|
|
||||||
this.kibanaLink = this.modelView.modelBuilder.hyperlink()
|
this.kibanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
|
||||||
label: this.getKibanaLink(),
|
|
||||||
url: this.getKibanaLink()
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.grafanaLink = this.modelView.modelBuilder.hyperlink()
|
this.grafanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
|
||||||
label: this.getGrafanaLink(),
|
|
||||||
url: this.getGrafanaLink()
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent()
|
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withItem(this.kibanaLink)
|
.withProperties<azdata.LoadingComponentProperties>(
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
{ loading: !this._postgresModel?.configLastUpdated }
|
||||||
loading: !this._controllerModel.endpointsLastUpdated
|
)
|
||||||
}).component();
|
.component();
|
||||||
|
|
||||||
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent()
|
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withItem(this.grafanaLink)
|
.withProperties<azdata.LoadingComponentProperties>(
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
{ loading: !this._postgresModel?.configLastUpdated }
|
||||||
loading: !this._controllerModel.endpointsLastUpdated
|
)
|
||||||
}).component();
|
.component();
|
||||||
|
|
||||||
|
this.refreshDashboardLinks();
|
||||||
|
|
||||||
|
this.kibanaLoading.component = this.kibanaLink;
|
||||||
|
this.grafanaLoading.component = this.grafanaLink;
|
||||||
|
|
||||||
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -134,60 +132,8 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
||||||
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
content.addItem(endpointsTable);
|
content.addItem(endpointsTable);
|
||||||
|
|
||||||
// Server group nodes
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: loc.serverGroupNodes,
|
|
||||||
CSSStyles: titleCSS
|
|
||||||
}).component());
|
|
||||||
|
|
||||||
this.nodesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
|
||||||
width: '100%',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
displayName: loc.name,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '30%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.type,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '15%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.status,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '20%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.fullyQualifiedDomain,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '35%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
}
|
|
||||||
],
|
|
||||||
data: this.getNodes()
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.nodesTableLoading = this.modelView.modelBuilder.loadingComponent()
|
|
||||||
.withItem(this.nodesTable)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
|
||||||
loading: !this._postgresModel.serviceLastUpdated && !this._postgresModel.podsLastUpdated
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
content.addItem(this.nodesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -205,11 +151,13 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
try {
|
try {
|
||||||
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
|
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
|
||||||
if (password) {
|
if (password) {
|
||||||
await this._postgresModel.update(s => {
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
// TODO chgagnon
|
this._postgresModel.info.name,
|
||||||
// s.arc = s.arc ?? new DuskyObjectModelsDatabaseServiceArcPayload();
|
{
|
||||||
s.arc.servicePassword = password;
|
adminPassword: true,
|
||||||
});
|
noWait: true
|
||||||
|
},
|
||||||
|
{ 'AZDATA_PASSWORD': password });
|
||||||
vscode.window.showInformationMessage(loc.passwordReset);
|
vscode.window.showInformationMessage(loc.passwordReset);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -229,15 +177,22 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
deleteButton.onDidClick(async () => {
|
deleteButton.onDidClick(async () => {
|
||||||
deleteButton.enabled = false;
|
deleteButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
/*
|
if (await promptForInstanceDeletion(this._postgresModel.info.name)) {
|
||||||
if (await promptForResourceDeletion(this._postgresModel.namespace, this._postgresModel.name)) {
|
await vscode.window.withProgress(
|
||||||
await this._postgresModel.delete();
|
{
|
||||||
await this._controllerModel.deleteRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
location: vscode.ProgressLocation.Notification,
|
||||||
vscode.window.showInformationMessage(loc.resourceDeleted(this._postgresModel.fullName));
|
title: loc.deletingInstance(this._postgresModel.info.name),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
(_progress, _token) => {
|
||||||
|
return this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await this._controllerModel.refreshTreeNode();
|
||||||
|
vscode.window.showInformationMessage(loc.instanceDeleted(this._postgresModel.info.name));
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.resourceDeletionFailed(this._postgresModel.fullName, error));
|
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._postgresModel.info.name, error));
|
||||||
} finally {
|
} finally {
|
||||||
deleteButton.enabled = true;
|
deleteButton.enabled = true;
|
||||||
}
|
}
|
||||||
@@ -256,7 +211,6 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
this.propertiesLoading!.loading = true;
|
this.propertiesLoading!.loading = true;
|
||||||
this.kibanaLoading!.loading = true;
|
this.kibanaLoading!.loading = true;
|
||||||
this.grafanaLoading!.loading = true;
|
this.grafanaLoading!.loading = true;
|
||||||
this.nodesTableLoading!.loading = true;
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this._postgresModel.refresh(),
|
this._postgresModel.refresh(),
|
||||||
@@ -278,15 +232,13 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
openInAzurePortalButton.onDidClick(async () => {
|
openInAzurePortalButton.onDidClick(async () => {
|
||||||
/*
|
const azure = this._controllerModel.controllerConfig?.spec.settings.azure;
|
||||||
const r = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
if (azure) {
|
||||||
if (!r) {
|
|
||||||
vscode.window.showErrorMessage(loc.couldNotFindAzureResource(this._postgresModel.fullName));
|
|
||||||
} else {
|
|
||||||
vscode.env.openExternal(vscode.Uri.parse(
|
vscode.env.openExternal(vscode.Uri.parse(
|
||||||
`https://portal.azure.com/#resource/subscriptions/${r.subscriptionId}/resourceGroups/${r.resourceGroupName}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${r.instanceName}`));
|
`https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}`));
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
@@ -298,63 +250,35 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getProperties(): azdata.PropertiesContainerItem[] {
|
private getProperties(): azdata.PropertiesContainerItem[] {
|
||||||
/*
|
const status = this._postgresModel.config?.status;
|
||||||
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
const azure = this._controllerModel.controllerConfig?.spec.settings.azure;
|
||||||
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ displayName: loc.name, value: this._postgresModel.name },
|
{ displayName: loc.resourceGroup, value: azure?.resourceGroup || '-' },
|
||||||
{ displayName: loc.coordinatorEndpoint, value: `postgresql://postgres@${endpoint.ip}:${endpoint.port}` },
|
{ displayName: loc.dataController, value: this._controllerModel.controllerConfig?.metadata.name || '-' },
|
||||||
{ displayName: loc.status, value: this._postgresModel.service?.status?.state ?? '' },
|
{ displayName: loc.region, value: azure?.location || '-' },
|
||||||
|
{ displayName: loc.namespace, value: this._postgresModel.config?.metadata.namespace || '-' },
|
||||||
|
{ displayName: loc.subscriptionId, value: azure?.subscription || '-' },
|
||||||
|
{ displayName: loc.externalEndpoint, value: this._postgresModel.config?.status.externalEndpoint || '-' },
|
||||||
|
{ displayName: loc.status, value: status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : '-' },
|
||||||
{ displayName: loc.postgresAdminUsername, value: 'postgres' },
|
{ displayName: loc.postgresAdminUsername, value: 'postgres' },
|
||||||
{ displayName: loc.dataController, value: this._controllerModel?.namespace ?? '' },
|
{ displayName: loc.postgresVersion, value: this._postgresModel.engineVersion ?? '-' },
|
||||||
{ displayName: loc.nodeConfiguration, value: this._postgresModel.configuration },
|
{ displayName: loc.nodeConfiguration, value: this._postgresModel.scaleConfiguration || '-' }
|
||||||
{ displayName: loc.subscriptionId, value: registration?.subscriptionId ?? '' },
|
|
||||||
{ displayName: loc.postgresVersion, value: this._postgresModel.service?.spec?.engine?.version?.toString() ?? '' }
|
|
||||||
];
|
];
|
||||||
*/
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getKibanaLink(): string {
|
private refreshDashboardLinks(): void {
|
||||||
const kibanaQuery = `kubernetes_namespace:"${this._postgresModel.namespace}" and custom_resource_name:"${this._postgresModel.name}"`;
|
if (this._postgresModel.config) {
|
||||||
return `${this._controllerModel.getEndpoint(Endpoints.logsui)?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
|
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
|
||||||
|
this.kibanaLink.label = kibanaUrl;
|
||||||
|
this.kibanaLink.url = kibanaUrl;
|
||||||
|
this.kibanaLoading.loading = false;
|
||||||
|
|
||||||
|
const grafanaUrl = this._postgresModel.config.status.metricsDashboard ?? '';
|
||||||
|
this.grafanaLink.label = grafanaUrl;
|
||||||
|
this.grafanaLink.url = grafanaUrl;
|
||||||
|
this.grafanaLoading.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getGrafanaLink(): string {
|
|
||||||
const grafanaQuery = `var-Namespace=${this._postgresModel.namespace}&var-Name=${this._postgresModel.name}`;
|
|
||||||
return `${this._controllerModel.getEndpoint(Endpoints.metricsui)?.endpoint}/d/postgres-metrics?${grafanaQuery}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getNodes(): string[][] {
|
|
||||||
/* TODO chgagnon
|
|
||||||
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
|
|
||||||
return this._postgresModel.pods?.map((pod: V1Pod) => {
|
|
||||||
const name = pod.metadata?.name ?? '';
|
|
||||||
const role: PodRole | undefined = PostgresModel.getPodRole(pod);
|
|
||||||
const service = pod.metadata?.annotations?.['arcdata.microsoft.com/serviceHost'];
|
|
||||||
const internalDns = service ? `${name}.${service}` : '';
|
|
||||||
|
|
||||||
return [
|
|
||||||
name,
|
|
||||||
PostgresModel.getPodRoleName(role),
|
|
||||||
PostgresModel.getPodStatus(pod),
|
|
||||||
role === PodRole.Router ? `${endpoint.ip}:${endpoint.port}` : internalDns
|
|
||||||
];
|
|
||||||
}) ?? [];
|
|
||||||
*/
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleEndpointsUpdated() {
|
|
||||||
this.kibanaLink!.label = this.getKibanaLink();
|
|
||||||
this.kibanaLink!.url = this.getKibanaLink();
|
|
||||||
this.kibanaLoading!.loading = false;
|
|
||||||
|
|
||||||
this.grafanaLink!.label = this.getGrafanaLink();
|
|
||||||
this.grafanaLink!.url = this.getGrafanaLink();
|
|
||||||
this.grafanaLoading!.loading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRegistrationsUpdated() {
|
private handleRegistrationsUpdated() {
|
||||||
@@ -362,19 +286,9 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
this.propertiesLoading!.loading = false;
|
this.propertiesLoading!.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleServiceUpdated() {
|
private handleConfigUpdated() {
|
||||||
this.properties!.propertyItems = this.getProperties();
|
this.properties!.propertyItems = this.getProperties();
|
||||||
this.propertiesLoading!.loading = false;
|
this.propertiesLoading!.loading = false;
|
||||||
|
this.refreshDashboardLinks();
|
||||||
this.nodesTable!.data = this.getNodes();
|
|
||||||
this.nodesTableLoading!.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handlePodsUpdated() {
|
|
||||||
this.properties!.propertyItems = this.getProperties();
|
|
||||||
this.propertiesLoading!.loading = false;
|
|
||||||
|
|
||||||
this.nodesTable!.data = this.getNodes();
|
|
||||||
this.nodesTableLoading!.loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { KeyValueContainer, KeyValue } from '../../components/keyValueContainer';
|
import { KeyValueContainer, KeyValue, InputKeyValue, TextKeyValue } from '../../components/keyValueContainer';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
@@ -19,7 +19,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
|||||||
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView);
|
||||||
|
|
||||||
this.disposables.push(this._postgresModel.onServiceUpdated(
|
this.disposables.push(this._postgresModel.onConfigUpdated(
|
||||||
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
||||||
|
|
||||||
this.disposables.push(this._controllerModel.onRegistrationsUpdated(
|
this.disposables.push(this._controllerModel.onRegistrationsUpdated(
|
||||||
@@ -54,7 +54,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
|||||||
this.loading = this.modelView.modelBuilder.loadingComponent()
|
this.loading = this.modelView.modelBuilder.loadingComponent()
|
||||||
.withItem(this.keyValueContainer.container)
|
.withItem(this.keyValueContainer.container)
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
loading: !this._postgresModel.serviceLastUpdated && !this._controllerModel.registrationsLastUpdated
|
loading: !this._postgresModel.configLastUpdated && !this._controllerModel.registrationsLastUpdated
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
content.addItem(this.loading);
|
content.addItem(this.loading);
|
||||||
@@ -91,24 +91,20 @@ export class PostgresPropertiesPage extends DashboardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getProperties(): KeyValue[] {
|
private getProperties(): KeyValue[] {
|
||||||
/*
|
const endpoint = this._postgresModel.endpoint;
|
||||||
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
|
const status = this._postgresModel.config?.status;
|
||||||
const connectionString = `postgresql://postgres@${endpoint.ip}:${endpoint.port}`;
|
|
||||||
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, connectionString),
|
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, endpoint ? `postgresql://postgres@${endpoint.ip}:${endpoint.port}` : ''),
|
||||||
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
|
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.status, this._postgresModel.service?.status?.state ?? 'Unknown'),
|
new TextKeyValue(this.modelView.modelBuilder, loc.status, status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : loc.unknown),
|
||||||
// TODO: Make this a LinkKeyValue that opens the controller dashboard
|
// TODO: Make this a LinkKeyValue that opens the controller dashboard
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.namespace ?? ''),
|
new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? ''),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.configuration),
|
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.scaleConfiguration ?? ''),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.service?.spec?.engine?.version?.toString() ?? ''),
|
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.engineVersion ?? ''),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.resourceGroup, registration?.resourceGroupName ?? ''),
|
new TextKeyValue(this.modelView.modelBuilder, loc.resourceGroup, this._controllerModel.controllerConfig?.spec.settings.azure.resourceGroup ?? ''),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.subscriptionId, registration?.subscriptionId ?? '')
|
new TextKeyValue(this.modelView.modelBuilder, loc.subscriptionId, this._controllerModel.controllerConfig?.spec.settings.azure.subscription ?? '')
|
||||||
];
|
];
|
||||||
*/
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRegistrationsUpdated() {
|
private handleRegistrationsUpdated() {
|
||||||
|
|||||||
@@ -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 loc from '../../../localizedConstants';
|
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
|
||||||
import { fromNow } from '../../../common/date';
|
|
||||||
|
|
||||||
export class PostgresResourceHealthPage extends DashboardPage {
|
|
||||||
private interval: NodeJS.Timeout;
|
|
||||||
private podsUpdated?: azdata.TextComponent;
|
|
||||||
private podsLoading?: azdata.LoadingComponent;
|
|
||||||
private conditionsLoading?: azdata.LoadingComponent;
|
|
||||||
private podsTable?: azdata.DeclarativeTableComponent;
|
|
||||||
private conditionsTable?: azdata.DeclarativeTableComponent;
|
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
|
||||||
super(modelView);
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
modelView.onClosed(() => {
|
|
||||||
try { clearInterval(this.interval); }
|
|
||||||
catch { }
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(this._postgresModel.onServiceUpdated(
|
|
||||||
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
|
||||||
|
|
||||||
// Keep the last updated timestamps up to date with the current time
|
|
||||||
this.interval = setInterval(() => this.handleServiceUpdated(), 60 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get title(): string {
|
|
||||||
return loc.resourceHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get id(): string {
|
|
||||||
return 'postgres-resource-health';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get icon(): { dark: string; light: string; } {
|
|
||||||
return IconPathHelper.health;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get container(): azdata.Component {
|
|
||||||
const root = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
const content = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: loc.resourceHealth,
|
|
||||||
CSSStyles: { ...cssStyles.title, 'margin-bottom': '30px' }
|
|
||||||
}).component());
|
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: loc.podOverview,
|
|
||||||
CSSStyles: { ...cssStyles.title, 'margin-block-end': '0' }
|
|
||||||
}).component());
|
|
||||||
|
|
||||||
this.podsUpdated = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
|
||||||
value: this.getPodsLastUpdated(),
|
|
||||||
CSSStyles: { ...cssStyles.text, 'font-size': '12px', 'margin-block-start': '0' }
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
content.addItem(this.podsUpdated);
|
|
||||||
|
|
||||||
// Pod overview
|
|
||||||
this.podsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
displayName: '',
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '35%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: { ...cssStyles.tableRow, 'font-size': '20px', 'font-weight': 'bold', 'padding': '7px' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: '',
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '65%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: { ...cssStyles.tableRow, 'padding': '7px' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
data: this.getPodsTable()
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.podsLoading = this.modelView.modelBuilder.loadingComponent()
|
|
||||||
.withItem(this.podsTable)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
|
||||||
loading: !this._postgresModel.serviceLastUpdated
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
content.addItem(this.podsLoading, { CSSStyles: { 'margin-bottom': '30px' } });
|
|
||||||
|
|
||||||
// Conditions table
|
|
||||||
this.conditionsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
|
||||||
width: '100%',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
displayName: loc.condition,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '15%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: '',
|
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
|
||||||
width: '1%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.details,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '64%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.lastUpdated,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
width: '20%',
|
|
||||||
isReadOnly: true,
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: { ...cssStyles.tableRow, 'white-space': 'nowrap' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
data: this.getConditionsTable()
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.conditionsLoading = this.modelView.modelBuilder.loadingComponent()
|
|
||||||
.withItem(this.conditionsTable)
|
|
||||||
.withProperties<azdata.LoadingComponentProperties>({
|
|
||||||
loading: !this._postgresModel.serviceLastUpdated
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
content.addItem(this.conditionsLoading);
|
|
||||||
this.initialized = true;
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
|
||||||
label: loc.refresh,
|
|
||||||
iconPath: IconPathHelper.refresh
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
refreshButton.onDidClick(async () => {
|
|
||||||
refreshButton.enabled = false;
|
|
||||||
try {
|
|
||||||
this.podsLoading!.loading = true;
|
|
||||||
this.conditionsLoading!.loading = true;
|
|
||||||
await this._postgresModel.refresh();
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
|
||||||
} finally {
|
|
||||||
refreshButton.enabled = true;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
|
||||||
{ component: refreshButton }
|
|
||||||
]).component();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPodsLastUpdated(): string {
|
|
||||||
return this._postgresModel.serviceLastUpdated
|
|
||||||
? loc.updated(fromNow(this._postgresModel.serviceLastUpdated!, true)) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPodsTable(): (string | number)[][] {
|
|
||||||
return [
|
|
||||||
[this._postgresModel.service?.status?.podsRunning ?? 0, loc.running],
|
|
||||||
[this._postgresModel.service?.status?.podsPending ?? 0, loc.pending],
|
|
||||||
[this._postgresModel.service?.status?.podsFailed ?? 0, loc.failed],
|
|
||||||
[this._postgresModel.service?.status?.podsUnknown ?? 0, loc.unknown]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConditionsTable(): (string | azdata.ImageComponent)[][] {
|
|
||||||
/* TODO chgagnon
|
|
||||||
return this._postgresModel.service?.status?.conditions?.map(c => {
|
|
||||||
const healthy = c.type === 'Ready' ? c.status === 'True' : c.status === 'False';
|
|
||||||
|
|
||||||
const image = this.modelView.modelBuilder.image().withProperties<azdata.ImageComponentProperties>({
|
|
||||||
iconPath: healthy ? IconPathHelper.success : IconPathHelper.fail,
|
|
||||||
iconHeight: '20px',
|
|
||||||
iconWidth: '20px',
|
|
||||||
width: '20px',
|
|
||||||
height: '20px'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
return [
|
|
||||||
c.type ?? '',
|
|
||||||
image,
|
|
||||||
c.message ?? '',
|
|
||||||
c.lastTransitionTime ? fromNow(c.lastTransitionTime!, true) : ''
|
|
||||||
];
|
|
||||||
}) ?? [];
|
|
||||||
*/
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleServiceUpdated() {
|
|
||||||
this.podsUpdated!.value = this.getPodsLastUpdated();
|
|
||||||
this.podsTable!.data = this.getPodsTable();
|
|
||||||
this.podsLoading!.loading = false;
|
|
||||||
|
|
||||||
this.conditionsTable!.data = this.getConditionsTable();
|
|
||||||
this.conditionsLoading!.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,17 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
|
import { ResourceType } from 'arc';
|
||||||
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
export class PostgresSupportRequestPage extends DashboardPage {
|
export class PostgresSupportRequestPage extends DashboardPage {
|
||||||
constructor(protected modelView: azdata.ModelView) {
|
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,15 +52,13 @@ export class PostgresSupportRequestPage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
supportRequestButton.onDidClick(() => {
|
supportRequestButton.onDidClick(() => {
|
||||||
/*
|
const azure = this._controllerModel.controllerConfig?.spec.settings.azure;
|
||||||
const r = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
if (azure) {
|
||||||
if (!r) {
|
|
||||||
vscode.window.showErrorMessage(loc.couldNotFindAzureResource(this._postgresModel.fullName));
|
|
||||||
} else {
|
|
||||||
vscode.env.openExternal(vscode.Uri.parse(
|
vscode.env.openExternal(vscode.Uri.parse(
|
||||||
`https://portal.azure.com/#resource/subscriptions/${r.subscriptionId}/resourceGroups/${r.resourceGroupName}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${r.instanceName}/supportrequest`));
|
`https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}/supportrequest`));
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
content.addItem(supportRequestButton);
|
content.addItem(supportRequestButton);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ControllerInfo } from 'arc';
|
import { ControllerInfo, ResourceInfo } from 'arc';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@@ -74,6 +74,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
|
|
||||||
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
||||||
protected id!: string;
|
protected id!: string;
|
||||||
|
protected resources: ResourceInfo[] = [];
|
||||||
|
|
||||||
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
||||||
super();
|
super();
|
||||||
@@ -82,6 +83,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
|
|
||||||
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||||
this.id = controllerInfo?.id ?? uuid();
|
this.id = controllerInfo?.id ?? uuid();
|
||||||
|
this.resources = controllerInfo?.resources ?? [];
|
||||||
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
this.dialog.registerContent(async (view) => {
|
this.dialog.registerContent(async (view) => {
|
||||||
this.modelBuilder = view.modelBuilder;
|
this.modelBuilder = view.modelBuilder;
|
||||||
@@ -168,7 +170,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
|||||||
name: this.nameInputBox.value ?? '',
|
name: this.nameInputBox.value ?? '',
|
||||||
username: this.usernameInputBox.value,
|
username: this.usernameInputBox.value,
|
||||||
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
||||||
resources: []
|
resources: this.resources
|
||||||
};
|
};
|
||||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
try {
|
try {
|
||||||
|
|||||||
135
extensions/arc/src/ui/dialogs/connectSqlDialog.ts
Normal file
135
extensions/arc/src/ui/dialogs/connectSqlDialog.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { Deferred } from '../../common/promise';
|
||||||
|
import { createCredentialId } from '../../common/utils';
|
||||||
|
import { credentialNamespace } from '../../constants';
|
||||||
|
import * as loc from '../../localizedConstants';
|
||||||
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
|
import { MiaaModel } from '../../models/miaaModel';
|
||||||
|
import { InitializingComponent } from '../components/initializingComponent';
|
||||||
|
|
||||||
|
export class ConnectToSqlDialog extends InitializingComponent {
|
||||||
|
private modelBuilder!: azdata.ModelBuilder;
|
||||||
|
|
||||||
|
private serverNameInputBox!: azdata.InputBoxComponent;
|
||||||
|
private usernameInputBox!: azdata.InputBoxComponent;
|
||||||
|
private passwordInputBox!: azdata.InputBoxComponent;
|
||||||
|
private rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
|
||||||
|
private _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
|
||||||
|
|
||||||
|
constructor(private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDialog(connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
|
||||||
|
const dialog = azdata.window.createModelViewDialog(loc.connectToSql(this._miaaModel.info.name));
|
||||||
|
dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
|
dialog.registerContent(async view => {
|
||||||
|
this.modelBuilder = view.modelBuilder;
|
||||||
|
|
||||||
|
this.serverNameInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
value: connectionProfile?.serverName,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
value: connectionProfile?.userName
|
||||||
|
}).component();
|
||||||
|
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
inputType: 'password',
|
||||||
|
value: connectionProfile?.password
|
||||||
|
})
|
||||||
|
.component();
|
||||||
|
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||||
|
.withProperties<azdata.CheckBoxProperties>({
|
||||||
|
label: loc.rememberPassword,
|
||||||
|
checked: connectionProfile?.savePassword
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let formModel = this.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
component: this.serverNameInputBox,
|
||||||
|
title: loc.serverEndpoint,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.usernameInputBox,
|
||||||
|
title: loc.username,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.passwordInputBox,
|
||||||
|
title: loc.password,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.rememberPwCheckBox,
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: ''
|
||||||
|
}]).withLayout({ width: '100%' }).component();
|
||||||
|
await view.initializeModel(formModel);
|
||||||
|
this.serverNameInputBox.focus();
|
||||||
|
this.initialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.registerCloseValidator(async () => await this.validate());
|
||||||
|
dialog.okButton.label = loc.connect;
|
||||||
|
dialog.cancelButton.label = loc.cancel;
|
||||||
|
azdata.window.openDialog(dialog);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(): Promise<boolean> {
|
||||||
|
if (!this.serverNameInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const connectionProfile: azdata.IConnectionProfile = {
|
||||||
|
serverName: this.serverNameInputBox.value,
|
||||||
|
databaseName: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
connectionName: '',
|
||||||
|
userName: this.usernameInputBox.value,
|
||||||
|
password: this.passwordInputBox.value,
|
||||||
|
savePassword: !!this.rememberPwCheckBox.checked,
|
||||||
|
groupFullName: undefined,
|
||||||
|
saveProfile: true,
|
||||||
|
id: '',
|
||||||
|
groupId: undefined,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
|
if (result.connected) {
|
||||||
|
connectionProfile.id = result.connectionId;
|
||||||
|
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
|
||||||
|
if (connectionProfile.savePassword) {
|
||||||
|
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name), connectionProfile.password);
|
||||||
|
} else {
|
||||||
|
await credentialProvider.deleteCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name));
|
||||||
|
}
|
||||||
|
this._completionPromise.resolve(connectionProfile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vscode.window.showErrorMessage(loc.connectToSqlFailed(this.serverNameInputBox.value, result.errorMessage));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCancel(): void {
|
||||||
|
this._completionPromise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public waitForClose(): Promise<azdata.IConnectionProfile | undefined> {
|
||||||
|
return this._completionPromise.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,10 +135,14 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
|
|||||||
if (resourceNode) {
|
if (resourceNode) {
|
||||||
await resourceNode.openDashboard();
|
await resourceNode.openDashboard();
|
||||||
} else {
|
} else {
|
||||||
console.log(`Couldn't find resource node for ${name} (${resourceType})`);
|
const errMsg = `Couldn't find resource node for ${name} (${resourceType})`;
|
||||||
|
console.log(errMsg);
|
||||||
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Couldn\'t find controller node for opening dashboard');
|
const errMsg = 'Couldn\'t find controller node for opening dashboard';
|
||||||
|
console.log(errMsg);
|
||||||
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ResourceInfo, ResourceType } from 'arc';
|
import { MiaaResourceInfo, ResourceInfo, ResourceType } from 'arc';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { parseInstanceName, UserCancelledError } from '../../common/utils';
|
import { UserCancelledError } from '../../common/utils';
|
||||||
import * as loc from '../../localizedConstants';
|
import * as loc from '../../localizedConstants';
|
||||||
import { ControllerModel, Registration } from '../../models/controllerModel';
|
import { ControllerModel, Registration } from '../../models/controllerModel';
|
||||||
import { MiaaModel } from '../../models/miaaModel';
|
import { MiaaModel } from '../../models/miaaModel';
|
||||||
import { PostgresModel } from '../../models/postgresModel';
|
import { PostgresModel } from '../../models/postgresModel';
|
||||||
|
import { ResourceModel } from '../../models/resourceModel';
|
||||||
import { ControllerDashboard } from '../dashboards/controller/controllerDashboard';
|
import { ControllerDashboard } from '../dashboards/controller/controllerDashboard';
|
||||||
import { AzureArcTreeDataProvider } from './azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from './azureArcTreeDataProvider';
|
||||||
import { MiaaTreeNode } from './miaaTreeNode';
|
import { MiaaTreeNode } from './miaaTreeNode';
|
||||||
|
import { NoInstancesTreeNode } from './noInstancesTreeNode';
|
||||||
import { PostgresTreeNode } from './postgresTreeNode';
|
import { PostgresTreeNode } from './postgresTreeNode';
|
||||||
import { RefreshTreeNode } from './refreshTreeNode';
|
import { RefreshTreeNode } from './refreshTreeNode';
|
||||||
import { ResourceTreeNode } from './resourceTreeNode';
|
import { ResourceTreeNode } from './resourceTreeNode';
|
||||||
@@ -23,7 +25,7 @@ import { TreeNode } from './treeNode';
|
|||||||
*/
|
*/
|
||||||
export class ControllerTreeNode extends TreeNode {
|
export class ControllerTreeNode extends TreeNode {
|
||||||
|
|
||||||
private _children: ResourceTreeNode[] = [];
|
private _children: ResourceTreeNode<ResourceModel>[] = [];
|
||||||
|
|
||||||
constructor(public model: ControllerModel, private _context: vscode.ExtensionContext, private _treeDataProvider: AzureArcTreeDataProvider) {
|
constructor(public model: ControllerModel, private _context: vscode.ExtensionContext, private _treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
super(model.label, vscode.TreeItemCollapsibleState.Collapsed, ResourceType.dataControllers);
|
super(model.label, vscode.TreeItemCollapsibleState.Collapsed, ResourceType.dataControllers);
|
||||||
@@ -55,7 +57,7 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._children;
|
return this._children.length > 0 ? this._children : [new NoInstancesTreeNode()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openDashboard(): Promise<void> {
|
public async openDashboard(): Promise<void> {
|
||||||
@@ -68,14 +70,14 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
* @param resourceType The resourceType of the node
|
* @param resourceType The resourceType of the node
|
||||||
* @param name The name of the node
|
* @param name The name of the node
|
||||||
*/
|
*/
|
||||||
public getResourceNode(resourceType: string, name: string): ResourceTreeNode | undefined {
|
public getResourceNode(resourceType: string, name: string): ResourceTreeNode<ResourceModel> | undefined {
|
||||||
return this._children.find(c =>
|
return this._children.find(c =>
|
||||||
c.model?.info.resourceType === resourceType &&
|
c.model?.info.resourceType === resourceType &&
|
||||||
c.model.info.name === name);
|
c.model.info.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateChildren(registrations: Registration[]): void {
|
private updateChildren(registrations: Registration[]): void {
|
||||||
const newChildren: ResourceTreeNode[] = [];
|
const newChildren: ResourceTreeNode<ResourceModel>[] = [];
|
||||||
registrations.forEach(registration => {
|
registrations.forEach(registration => {
|
||||||
if (!registration.instanceName) {
|
if (!registration.instanceName) {
|
||||||
console.warn('Registration is missing required name value, skipping');
|
console.warn('Registration is missing required name value, skipping');
|
||||||
@@ -83,7 +85,7 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resourceInfo: ResourceInfo = {
|
const resourceInfo: ResourceInfo = {
|
||||||
name: parseInstanceName(registration.instanceName),
|
name: registration.instanceName,
|
||||||
resourceType: registration.instanceType ?? ''
|
resourceType: registration.instanceType ?? ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,10 +102,14 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
|
|
||||||
switch (registration.instanceType) {
|
switch (registration.instanceType) {
|
||||||
case ResourceType.postgresInstances:
|
case ResourceType.postgresInstances:
|
||||||
const postgresModel = new PostgresModel(resourceInfo, registration);
|
const postgresModel = new PostgresModel(this.model, resourceInfo, registration);
|
||||||
node = new PostgresTreeNode(postgresModel, this.model, this._context);
|
node = new PostgresTreeNode(postgresModel, this.model, this._context);
|
||||||
break;
|
break;
|
||||||
case ResourceType.sqlManagedInstances:
|
case ResourceType.sqlManagedInstances:
|
||||||
|
// Fill in the username too if we already have it
|
||||||
|
(resourceInfo as MiaaResourceInfo).userName = (this.model.info.resources.find(info =>
|
||||||
|
info.name === resourceInfo.name &&
|
||||||
|
info.resourceType === resourceInfo.resourceType) as MiaaResourceInfo)?.userName;
|
||||||
const miaaModel = new MiaaModel(this.model, resourceInfo, registration, this._treeDataProvider);
|
const miaaModel = new MiaaModel(this.model, resourceInfo, registration, this._treeDataProvider);
|
||||||
node = new MiaaTreeNode(miaaModel, this.model);
|
node = new MiaaTreeNode(miaaModel, this.model);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ import * as vscode from 'vscode';
|
|||||||
import { ControllerModel } from '../../models/controllerModel';
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
import { MiaaModel } from '../../models/miaaModel';
|
import { MiaaModel } from '../../models/miaaModel';
|
||||||
import { MiaaDashboard } from '../dashboards/miaa/miaaDashboard';
|
import { MiaaDashboard } from '../dashboards/miaa/miaaDashboard';
|
||||||
import { TreeNode } from './treeNode';
|
import { ResourceTreeNode } from './resourceTreeNode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TreeNode for displaying a SQL Managed Instance on Azure Arc
|
* The TreeNode for displaying a SQL Managed Instance on Azure Arc
|
||||||
*/
|
*/
|
||||||
export class MiaaTreeNode extends TreeNode {
|
export class MiaaTreeNode extends ResourceTreeNode<MiaaModel> {
|
||||||
|
|
||||||
constructor(public model: MiaaModel, private _controllerModel: ControllerModel) {
|
constructor(model: MiaaModel, private _controllerModel: ControllerModel) {
|
||||||
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.sqlManagedInstances);
|
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.sqlManagedInstances, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openDashboard(): Promise<void> {
|
public async openDashboard(): Promise<void> {
|
||||||
|
|||||||
18
extensions/arc/src/ui/tree/noInstancesTreeNode.ts
Normal file
18
extensions/arc/src/ui/tree/noInstancesTreeNode.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 loc from '../../localizedConstants';
|
||||||
|
import { TreeNode } from './treeNode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A placeholder TreeNode to display when there aren't any child instances available
|
||||||
|
*/
|
||||||
|
export class NoInstancesTreeNode extends TreeNode {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(loc.noInstancesAvailable, vscode.TreeItemCollapsibleState.None, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,14 +13,14 @@ import { ResourceTreeNode } from './resourceTreeNode';
|
|||||||
/**
|
/**
|
||||||
* The TreeNode for displaying an Postgres Server group
|
* The TreeNode for displaying an Postgres Server group
|
||||||
*/
|
*/
|
||||||
export class PostgresTreeNode extends ResourceTreeNode {
|
export class PostgresTreeNode extends ResourceTreeNode<PostgresModel> {
|
||||||
|
|
||||||
constructor(private _model: PostgresModel, private _controllerModel: ControllerModel, private _context: vscode.ExtensionContext) {
|
constructor(model: PostgresModel, private _controllerModel: ControllerModel, private _context: vscode.ExtensionContext) {
|
||||||
super(_model.name, vscode.TreeItemCollapsibleState.None, ResourceType.postgresInstances, _model);
|
super(model.info.name, vscode.TreeItemCollapsibleState.None, ResourceType.postgresInstances, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openDashboard(): Promise<void> {
|
public async openDashboard(): Promise<void> {
|
||||||
const postgresDashboard = new PostgresDashboard(this._context, this._controllerModel, this._model);
|
const postgresDashboard = new PostgresDashboard(this._context, this._controllerModel, this.model);
|
||||||
await postgresDashboard.showDashboard();
|
await postgresDashboard.showDashboard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { refreshActionId } from '../../constants';
|
|||||||
export class RefreshTreeNode extends TreeNode {
|
export class RefreshTreeNode extends TreeNode {
|
||||||
|
|
||||||
constructor(private _parent: TreeNode) {
|
constructor(private _parent: TreeNode) {
|
||||||
super(loc.refreshToEnterCredentials, vscode.TreeItemCollapsibleState.None, 'refresh');
|
super(loc.refreshToEnterCredentials, vscode.TreeItemCollapsibleState.None, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
public command: vscode.Command = {
|
public command: vscode.Command = {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { TreeNode } from './treeNode';
|
|||||||
/**
|
/**
|
||||||
* A TreeNode belonging to a child of a Controller
|
* A TreeNode belonging to a child of a Controller
|
||||||
*/
|
*/
|
||||||
export abstract class ResourceTreeNode extends TreeNode {
|
export abstract class ResourceTreeNode<M extends ResourceModel> extends TreeNode {
|
||||||
constructor(label: string, collapsibleState: vscode.TreeItemCollapsibleState, resourceType?: string, public model?: ResourceModel) {
|
constructor(label: string, collapsibleState: vscode.TreeItemCollapsibleState, resourceType: string, public model: M) {
|
||||||
super(label, collapsibleState, resourceType);
|
super(label, collapsibleState, resourceType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Welcome to Microsoft Azure Data CLI Extension for Azure Data Studio!
|
Welcome to Microsoft Azure Data CLI Extension for Azure Data Studio!
|
||||||
|
|
||||||
**This extension is only applicable to customers in the Azure Arc data services private preview. Other usage is not supported at this time.**
|
**This extension is only applicable to customers in the Azure Arc data services public preview. Other usage is not supported at this time.**
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -2,14 +2,14 @@
|
|||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"displayName": "%azdata.displayName%",
|
"displayName": "%azdata.displayName%",
|
||||||
"description": "%azdata.description%",
|
"description": "%azdata.description%",
|
||||||
"version": "0.1.2",
|
"version": "0.3.1",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
"icon": "images/extension.png",
|
"icon": "images/extension.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "*",
|
"vscode": "*",
|
||||||
"azdata": ">=1.20.0"
|
"azdata": ">=1.22.0"
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"*"
|
"*"
|
||||||
@@ -132,5 +132,10 @@
|
|||||||
"sinon": "^9.0.2",
|
"sinon": "^9.0.2",
|
||||||
"typemoq": "^2.1.0",
|
"typemoq": "^2.1.0",
|
||||||
"vscodetestcover": "^1.1.0"
|
"vscodetestcover": "^1.1.0"
|
||||||
|
},
|
||||||
|
"__metadata": {
|
||||||
|
"id": "73",
|
||||||
|
"publisherDisplayName": "Microsoft",
|
||||||
|
"publisherId": "Microsoft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
|
delete: async (name: string) => {
|
||||||
|
await localAzdataDiscovered;
|
||||||
|
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
|
return azdataToolService.localAzdata.arc.postgres.server.delete(name);
|
||||||
|
},
|
||||||
list: async () => {
|
list: async () => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
@@ -81,6 +86,26 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.postgres.server.show(name);
|
return azdataToolService.localAzdata.arc.postgres.server.show(name);
|
||||||
|
},
|
||||||
|
edit: async (
|
||||||
|
name: string,
|
||||||
|
args: {
|
||||||
|
adminPassword?: boolean;
|
||||||
|
coresLimit?: string;
|
||||||
|
coresRequest?: string;
|
||||||
|
engineSettings?: string;
|
||||||
|
extensions?: string;
|
||||||
|
memoryLimit?: string;
|
||||||
|
memoryRequest?: string;
|
||||||
|
noWait?: boolean;
|
||||||
|
port?: number;
|
||||||
|
replaceEngineSettings?: boolean;
|
||||||
|
workers?: number;
|
||||||
|
},
|
||||||
|
additionalEnvVars?: { [key: string]: string; }) => {
|
||||||
|
await localAzdataDiscovered;
|
||||||
|
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
|
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, additionalEnvVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,13 +6,15 @@
|
|||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
import { SemVer } from 'semver';
|
import { SemVer } from 'semver';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataReleaseInfo';
|
||||||
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
||||||
import { HttpClient } from './common/httpClient';
|
import { HttpClient } from './common/httpClient';
|
||||||
import Logger from './common/logger';
|
import Logger from './common/logger';
|
||||||
import { getErrorMessage, searchForCmd } from './common/utils';
|
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
|
||||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataHostname, azdataInstallKey, azdataReleaseJson, azdataUpdateKey, azdataUri, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
|
|
||||||
const enum AzdataDeployOption {
|
const enum AzdataDeployOption {
|
||||||
@@ -92,11 +94,44 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
|
delete: async (name: string) => {
|
||||||
|
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name]);
|
||||||
|
},
|
||||||
list: async () => {
|
list: async () => {
|
||||||
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list']);
|
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list']);
|
||||||
},
|
},
|
||||||
show: async (name: string) => {
|
show: async (name: string) => {
|
||||||
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name]);
|
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name]);
|
||||||
|
},
|
||||||
|
edit: async (
|
||||||
|
name: string,
|
||||||
|
args: {
|
||||||
|
adminPassword?: boolean,
|
||||||
|
coresLimit?: string,
|
||||||
|
coresRequest?: string,
|
||||||
|
engineSettings?: string,
|
||||||
|
extensions?: string,
|
||||||
|
memoryLimit?: string,
|
||||||
|
memoryRequest?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
port?: number,
|
||||||
|
replaceEngineSettings?: boolean,
|
||||||
|
workers?: number
|
||||||
|
},
|
||||||
|
additionalEnvVars?: { [key: string]: string }) => {
|
||||||
|
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
|
||||||
|
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
||||||
|
if (args.coresLimit !== undefined) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||||
|
if (args.coresRequest !== undefined) { argsArray.push('--cores-request', args.coresRequest); }
|
||||||
|
if (args.engineSettings !== undefined) { argsArray.push('--engine-settings', args.engineSettings); }
|
||||||
|
if (args.extensions !== undefined) { argsArray.push('--extensions', args.extensions); }
|
||||||
|
if (args.memoryLimit !== undefined) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||||
|
if (args.memoryRequest !== undefined) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||||
|
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||||
|
if (args.port !== undefined) { argsArray.push('--port', args.port.toString()); }
|
||||||
|
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
|
||||||
|
if (args.workers !== undefined) { argsArray.push('--workers', args.workers.toString()); }
|
||||||
|
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -151,18 +186,18 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
// ERROR: { stderr: '...' }
|
// ERROR: { stderr: '...' }
|
||||||
// so we also need to trim off the start that isn't a valid JSON blob
|
// so we also need to trim off the start that isn't a valid JSON blob
|
||||||
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'), err.stderr.indexOf('}') + 1)).stderr;
|
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'), err.stderr.indexOf('}') + 1)).stderr;
|
||||||
} catch (err) {
|
} catch {
|
||||||
// it means this was probably some other generic error (such as command not being found)
|
// it means this was probably some other generic error (such as command not being found)
|
||||||
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(this._path);
|
await fs.promises.access(this._path);
|
||||||
//this.path exists
|
//this.path exists
|
||||||
throw err; // rethrow the error
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// this.path does not exist
|
// this.path does not exist
|
||||||
await vscode.commands.executeCommand('setContext', azdataFound, false);
|
await vscode.commands.executeCommand('setContext', azdataFound, false);
|
||||||
throw (loc.noAzdata);
|
throw new NoAzdataError();
|
||||||
}
|
}
|
||||||
|
throw err; // rethrow the original error
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -311,6 +346,7 @@ async function promptToInstallAzdata(userRequested: boolean = false): Promise<bo
|
|||||||
? [loc.yes, loc.no]
|
? [loc.yes, loc.no]
|
||||||
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
||||||
if (config === AzdataDeployOption.prompt) {
|
if (config === AzdataDeployOption.prompt) {
|
||||||
|
Logger.log(loc.promptForAzdataInstallLog);
|
||||||
response = await vscode.window.showErrorMessage(loc.promptForAzdataInstall, ...responses);
|
response = await vscode.window.showErrorMessage(loc.promptForAzdataInstall, ...responses);
|
||||||
Logger.log(loc.userResponseToInstallPrompt(response));
|
Logger.log(loc.userResponseToInstallPrompt(response));
|
||||||
}
|
}
|
||||||
@@ -354,6 +390,7 @@ async function promptToUpdateAzdata(newVersion: string, userRequested: boolean =
|
|||||||
? [loc.yes, loc.no]
|
? [loc.yes, loc.no]
|
||||||
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
||||||
if (config === AzdataDeployOption.prompt) {
|
if (config === AzdataDeployOption.prompt) {
|
||||||
|
Logger.log(loc.promptForAzdataUpdateLog(newVersion));
|
||||||
response = await vscode.window.showInformationMessage(loc.promptForAzdataUpdate(newVersion), ...responses);
|
response = await vscode.window.showInformationMessage(loc.promptForAzdataUpdate(newVersion), ...responses);
|
||||||
Logger.log(loc.userResponseToUpdatePrompt(response));
|
Logger.log(loc.userResponseToUpdatePrompt(response));
|
||||||
}
|
}
|
||||||
@@ -427,9 +464,16 @@ export async function promptForEula(memento: vscode.Memento, userRequested: bool
|
|||||||
* Downloads the Windows installer and runs it
|
* Downloads the Windows installer and runs it
|
||||||
*/
|
*/
|
||||||
async function downloadAndInstallAzdataWin32(): Promise<void> {
|
async function downloadAndInstallAzdataWin32(): Promise<void> {
|
||||||
|
const downLoadLink = await getPlatformDownloadLink();
|
||||||
const downloadFolder = os.tmpdir();
|
const downloadFolder = os.tmpdir();
|
||||||
const downloadedFile = await HttpClient.downloadFile(`${azdataHostname}/${azdataUri}`, downloadFolder);
|
const downloadLogs = path.join(downloadFolder, 'ads_azdata_install_logs.log');
|
||||||
await executeCommand('msiexec', ['/qn', '/i', downloadedFile]);
|
const downloadedFile = await HttpClient.downloadFile(downLoadLink, downloadFolder);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeSudoCommand(`msiexec /qn /i "${downloadedFile}" /lvx "${downloadLogs}"`);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`${err.message}. See logs at ${downloadLogs} for more details.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -502,27 +546,10 @@ export async function discoverLatestAvailableAzdataVersion(): Promise<SemVer> {
|
|||||||
// However, doing discovery that way required apt update to be performed which requires sudo privileges. At least currently this code path
|
// However, doing discovery that way required apt update to be performed which requires sudo privileges. At least currently this code path
|
||||||
// gets invoked on extension start up and prompt user for sudo privileges is annoying at best. So for now basing linux discovery also on a releaseJson file.
|
// gets invoked on extension start up and prompt user for sudo privileges is annoying at best. So for now basing linux discovery also on a releaseJson file.
|
||||||
default:
|
default:
|
||||||
return await discoverLatestAzdataVersionFromJson();
|
return await getPlatformReleaseVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the latest azdata version from a json document published by azdata release
|
|
||||||
*/
|
|
||||||
async function discoverLatestAzdataVersionFromJson(): Promise<SemVer> {
|
|
||||||
// get version information for current platform from http://aka.ms/azdata/release.json
|
|
||||||
const fileContents = await HttpClient.getTextContent(`${azdataHostname}/${azdataReleaseJson}`);
|
|
||||||
let azdataReleaseInfo;
|
|
||||||
try {
|
|
||||||
azdataReleaseInfo = JSON.parse(fileContents);
|
|
||||||
} catch (e) {
|
|
||||||
throw Error(`failed to parse the JSON of contents at: ${azdataHostname}/${azdataReleaseJson}, text being parsed: '${fileContents}', error:${getErrorMessage(e)}`);
|
|
||||||
}
|
|
||||||
const version = azdataReleaseInfo[process.platform]['version'];
|
|
||||||
Logger.log(loc.latestAzdataVersionAvailable(version));
|
|
||||||
return new SemVer(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses out the azdata version from the raw azdata version output
|
* Parses out the azdata version from the raw azdata version output
|
||||||
* @param raw The raw version output from azdata --version
|
* @param raw The raw version output from azdata --version
|
||||||
|
|||||||
76
extensions/azdata/src/azdataReleaseInfo.ts
Normal file
76
extensions/azdata/src/azdataReleaseInfo.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as loc from './localizedConstants';
|
||||||
|
import { SemVer } from 'semver';
|
||||||
|
import { HttpClient } from './common/httpClient';
|
||||||
|
import Logger from './common/logger';
|
||||||
|
import { getErrorMessage } from './common/utils';
|
||||||
|
import { azdataHostname, azdataReleaseJson } from './constants';
|
||||||
|
|
||||||
|
interface PlatformReleaseInfo {
|
||||||
|
version: string; // "20.0.1"
|
||||||
|
link?: string; // "https://aka.ms/azdata-msi"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AzdataReleaseInfo {
|
||||||
|
win32: PlatformReleaseInfo,
|
||||||
|
darwin: PlatformReleaseInfo,
|
||||||
|
linux: PlatformReleaseInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformAzdataReleaseInfo(releaseInfo: AzdataReleaseInfo): PlatformReleaseInfo {
|
||||||
|
switch (os.platform()) {
|
||||||
|
case 'win32':
|
||||||
|
return releaseInfo.win32;
|
||||||
|
case 'linux':
|
||||||
|
return releaseInfo.linux;
|
||||||
|
case 'darwin':
|
||||||
|
return releaseInfo.darwin;
|
||||||
|
default:
|
||||||
|
Logger.log(loc.platformUnsupported(os.platform()));
|
||||||
|
throw new Error(`Unsupported AzdataReleaseInfo platform '${os.platform()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the release version for the current platform from the release info - throwing an error if it doesn't exist.
|
||||||
|
* @param releaseInfo The AzdataReleaseInfo object
|
||||||
|
*/
|
||||||
|
export async function getPlatformReleaseVersion(): Promise<SemVer> {
|
||||||
|
const releaseInfo = await getAzdataReleaseInfo();
|
||||||
|
const platformReleaseInfo = getPlatformAzdataReleaseInfo(releaseInfo);
|
||||||
|
if (!platformReleaseInfo.version) {
|
||||||
|
Logger.log(loc.noReleaseVersion(os.platform(), JSON.stringify(releaseInfo)));
|
||||||
|
throw new Error(`No release version available for platform ${os.platform()}`);
|
||||||
|
}
|
||||||
|
Logger.log(loc.latestAzdataVersionAvailable(platformReleaseInfo.version));
|
||||||
|
return new SemVer(platformReleaseInfo.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the download link for the current platform from the release info - throwing an error if it doesn't exist.
|
||||||
|
* @param releaseInfo The AzdataReleaseInfo object
|
||||||
|
*/
|
||||||
|
export async function getPlatformDownloadLink(): Promise<string> {
|
||||||
|
const releaseInfo = await getAzdataReleaseInfo();
|
||||||
|
const platformReleaseInfo = getPlatformAzdataReleaseInfo(releaseInfo);
|
||||||
|
if (!platformReleaseInfo.link) {
|
||||||
|
Logger.log(loc.noDownloadLink(os.platform(), JSON.stringify(releaseInfo)));
|
||||||
|
throw new Error(`No download link available for platform ${os.platform()}`);
|
||||||
|
}
|
||||||
|
return platformReleaseInfo.link;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAzdataReleaseInfo(): Promise<AzdataReleaseInfo> {
|
||||||
|
const fileContents = await HttpClient.getTextContent(`${azdataHostname}/${azdataReleaseJson}`);
|
||||||
|
try {
|
||||||
|
return JSON.parse(fileContents);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.log(loc.failedToParseReleaseInfo(`${azdataHostname}/${azdataReleaseJson}`, fileContents, e));
|
||||||
|
throw Error(`Failed to parse the JSON of contents at: ${azdataHostname}/${azdataReleaseJson}. Error: ${getErrorMessage(e)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ export async function executeCommand(command: string, args: string[], additional
|
|||||||
Logger.log(loc.stdoutOutput(stdout));
|
Logger.log(loc.stdoutOutput(stdout));
|
||||||
}
|
}
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
Logger.log(loc.stdoutOutput(stderr));
|
Logger.log(loc.stderrOutput(stderr));
|
||||||
}
|
}
|
||||||
if (code) {
|
if (code) {
|
||||||
const err = new ExitCodeError(code, stderr);
|
const err = new ExitCodeError(code, stderr);
|
||||||
@@ -94,7 +94,7 @@ export async function executeSudoCommand(command: string): Promise<ProcessOutput
|
|||||||
Logger.log(loc.stdoutOutput(stdout));
|
Logger.log(loc.stdoutOutput(stdout));
|
||||||
}
|
}
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
Logger.log(loc.stdoutOutput(stderr));
|
Logger.log(loc.stderrOutput(stderr));
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
Logger.log(loc.unexpectedCommandError(error.message));
|
Logger.log(loc.unexpectedCommandError(error.message));
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export namespace HttpClient {
|
|||||||
if (targetFolder !== undefined) {
|
if (targetFolder !== undefined) {
|
||||||
const filename = path.basename(response.request.path);
|
const filename = path.basename(response.request.path);
|
||||||
const targetPath = path.join(targetFolder, filename);
|
const targetPath = path.join(targetFolder, filename);
|
||||||
Logger.log(loc.downloadingTo(filename, targetPath));
|
Logger.log(loc.downloadingTo(filename, downloadUrl, targetPath));
|
||||||
// Wait to create the WriteStream until here so we can use the actual
|
// Wait to create the WriteStream until here so we can use the actual
|
||||||
// filename based off of the URI.
|
// filename based off of the URI.
|
||||||
downloadRequest.pipe(fs.createWriteStream(targetPath))
|
downloadRequest.pipe(fs.createWriteStream(targetPath))
|
||||||
@@ -73,6 +73,11 @@ export namespace HttpClient {
|
|||||||
reject(downloadError);
|
reject(downloadError);
|
||||||
downloadRequest.abort();
|
downloadRequest.abort();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
response.on('end', () => {
|
||||||
|
Logger.log(loc.downloadFinished);
|
||||||
|
resolve(strings.join(''));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let contentLength = response.headers['content-length'];
|
let contentLength = response.headers['content-length'];
|
||||||
let totalBytes = parseInt(contentLength || '0');
|
let totalBytes = parseInt(contentLength || '0');
|
||||||
@@ -92,13 +97,6 @@ export namespace HttpClient {
|
|||||||
printThreshold += 0.1;
|
printThreshold += 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.on('close', async () => {
|
|
||||||
if (targetFolder === undefined) {
|
|
||||||
|
|
||||||
Logger.log(loc.downloadFinished);
|
|
||||||
resolve(strings.join(''));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
export class Log {
|
export class Log {
|
||||||
private _output: vscode.OutputChannel;
|
private _output: vscode.OutputChannel;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._output = vscode.window.createOutputChannel('azdata');
|
this._output = vscode.window.createOutputChannel(loc.azdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(msg: string): void {
|
log(msg: string): void {
|
||||||
this._output.appendLine(msg);
|
this._output.appendLine(`[${new Date().toISOString()}] ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
show(): void {
|
show(): void {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import * as azdataExt from 'azdata-ext';
|
|||||||
import * as which from 'which';
|
import * as which from 'which';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
|
|
||||||
export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
|
export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(loc.noAzdata);
|
super(loc.noAzdata);
|
||||||
@@ -17,7 +16,6 @@ export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
|
|||||||
return loc.noAzdataWithLink;
|
return loc.noAzdataWithLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for the first instance of the specified executable in the PATH environment variable
|
* Searches for the first instance of the specified executable in the PATH environment variable
|
||||||
* @param exe The executable to search for
|
* @param exe The executable to search for
|
||||||
|
|||||||
@@ -34,15 +34,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
|
|||||||
eulaAccepted = isEulaAccepted(context.globalState); // fetch eula acceptance state from memento
|
eulaAccepted = isEulaAccepted(context.globalState); // fetch eula acceptance state from memento
|
||||||
await vscode.commands.executeCommand('setContext', constants.eulaAccepted, eulaAccepted); // set a context key for current value of eulaAccepted state retrieved from memento so that command for accepting eula is available/unavailable in commandPalette appropriately.
|
await vscode.commands.executeCommand('setContext', constants.eulaAccepted, eulaAccepted); // set a context key for current value of eulaAccepted state retrieved from memento so that command for accepting eula is available/unavailable in commandPalette appropriately.
|
||||||
Logger.log(loc.eulaAcceptedStateOnStartup(eulaAccepted));
|
Logger.log(loc.eulaAcceptedStateOnStartup(eulaAccepted));
|
||||||
if (!eulaAccepted) {
|
|
||||||
// Don't block on this since we want extension to finish activating without requiring user actions.
|
|
||||||
// If EULA has not been accepted then we will check again while executing azdata commands.
|
|
||||||
promptForEula(context.globalState)
|
|
||||||
.then(async (userResponse: boolean) => {
|
|
||||||
eulaAccepted = userResponse;
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't block on this since we want the extension to finish activating without needing user input
|
// Don't block on this since we want the extension to finish activating without needing user input
|
||||||
const localAzdataDiscovered = checkAndInstallAzdata() // install if not installed and user wants it.
|
const localAzdataDiscovered = checkAndInstallAzdata() // install if not installed and user wants it.
|
||||||
|
|||||||
@@ -4,16 +4,18 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
|
import { getErrorMessage } from './common/utils';
|
||||||
import { azdataConfigSection, azdataInstallKey, azdataUpdateKey } from './constants';
|
import { azdataConfigSection, azdataInstallKey, azdataUpdateKey } from './constants';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export const azdata = localize('azdata.azdata', "Azure Data CLI");
|
||||||
export const searchingForAzdata = localize('azdata.searchingForAzdata', "Searching for existing Azure Data CLI installation...");
|
export const searchingForAzdata = localize('azdata.searchingForAzdata', "Searching for existing Azure Data CLI installation...");
|
||||||
export const foundExistingAzdata = (path: string, version: string): string => localize('azdata.foundExistingAzdata', "Found existing Azure Data CLI installation of version (v{0}) at path:{1}", version, path);
|
export const foundExistingAzdata = (path: string, version: string): string => localize('azdata.foundExistingAzdata', "Found existing Azure Data CLI installation of version (v{0}) at path:{1}", version, path);
|
||||||
|
|
||||||
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('azdata.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
|
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('azdata.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
|
||||||
export const downloadFinished = localize('azdata.downloadFinished', "Download finished");
|
export const downloadFinished = localize('azdata.downloadFinished', "Download finished");
|
||||||
export const installingAzdata = localize('azdata.installingAzdata', "Installing azdata...");
|
export const installingAzdata = localize('azdata.installingAzdata', "Installing Azure Data CLI...");
|
||||||
export const updatingAzdata = localize('azdata.updatingAzdata', "updating azdata...");
|
export const updatingAzdata = localize('azdata.updatingAzdata', "Updating Azure Data CLI...");
|
||||||
export const azdataInstalled = localize('azdata.azdataInstalled', "Azure Data CLI was successfully installed. Restarting Azure Data Studio is required to complete configuration - features will not be activated until this is done.");
|
export const azdataInstalled = localize('azdata.azdataInstalled', "Azure Data CLI was successfully installed. Restarting Azure Data Studio is required to complete configuration - features will not be activated until this is done.");
|
||||||
export const azdataUpdated = (version: string) => localize('azdata.azdataUpdated', "Azure Data CLI was successfully updated to version: {0}.", version);
|
export const azdataUpdated = (version: string) => localize('azdata.azdataUpdated', "Azure Data CLI was successfully updated to version: {0}.", version);
|
||||||
export const yes = localize('azdata.yes', "Yes");
|
export const yes = localize('azdata.yes', "Yes");
|
||||||
@@ -22,46 +24,45 @@ export const accept = localize('azdata.accept', "Accept");
|
|||||||
export const decline = localize('azdata.decline', "Decline");
|
export const decline = localize('azdata.decline', "Decline");
|
||||||
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
||||||
export const askLater = localize('azdata.askLater', "Ask Later");
|
export const askLater = localize('azdata.askLater', "Ask Later");
|
||||||
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
export const downloadingTo = (name: string, url: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} from {1} to {2}", name, url, location);
|
||||||
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
|
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
|
||||||
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
||||||
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
||||||
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of azdata");
|
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of Azure Data CLI");
|
||||||
export const gettingTextContentsOfUrl = (url: string): string => localize('azdata.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
|
export const gettingTextContentsOfUrl = (url: string): string => localize('azdata.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
|
||||||
export const foundAzdataVersionToUpdateTo = (newVersion: string, currentVersion: string): string => localize('azdata.versionForUpdate', "Found version: {0} that Azure Data CLI can be updated to from current version: {1}.", newVersion, currentVersion);
|
export const foundAzdataVersionToUpdateTo = (newVersion: string, currentVersion: string): string => localize('azdata.versionForUpdate', "Found version: {0} that Azure Data CLI can be updated to from current version: {1}.", newVersion, currentVersion);
|
||||||
export const latestAzdataVersionAvailable = (version: string): string => localize('azdata.latestAzdataVersionAvailable', "Latest available Azure Data CLI version: {0}.", version);
|
export const latestAzdataVersionAvailable = (version: string): string => localize('azdata.latestAzdataVersionAvailable', "Latest available Azure Data CLI version: {0}.", version);
|
||||||
export const couldNotFindAzdata = (err: any): string => localize('azdata.couldNotFindAzdata', "Could not find azdata. Error: {0}", err.message ?? err);
|
export const couldNotFindAzdata = (err: any): string => localize('azdata.couldNotFindAzdata', "Could not find Azure Data CLI. Error: {0}", err.message ?? err);
|
||||||
export const currentlyInstalledVersionIsLatest = (currentVersion: string): string => localize('azdata.currentlyInstalledVersionIsLatest', "Currently installed version of azdata: {0} is same or newer than any other version available", currentVersion);
|
export const currentlyInstalledVersionIsLatest = (currentVersion: string): string => localize('azdata.currentlyInstalledVersionIsLatest', "Currently installed version of Azure Data CLI: {0} is same or newer than any other version available", currentVersion);
|
||||||
export const promptLog = (logEntry: string) => localize('azdata.promptLog', "Prompting the user to accept the following: {0}", logEntry);
|
export const promptLog = (logEntry: string) => localize('azdata.promptLog', "Prompting the user to accept the following: {0}", logEntry);
|
||||||
export const promptForAzdataInstall = localize('azdata.couldNotFindAzdataWithPrompt', "Could not find azdata, install it now? If not then some features will not be able to function.");
|
export const promptForAzdataInstall = localize('azdata.couldNotFindAzdataWithPrompt', "Could not find Azure Data CLI, install it now? If not then some features will not be able to function.");
|
||||||
export const promptForAzdataInstallLog = promptLog(promptForAzdataInstall);
|
export const promptForAzdataInstallLog = promptLog(promptForAzdataInstall);
|
||||||
export const promptForAzdataUpdate = (version: string): string => localize('azdata.promptForAzdataUpdate', "A new version of Azure Data CLI ( {0} ) is available, do you wish to update to it now?", version);
|
export const promptForAzdataUpdate = (version: string): string => localize('azdata.promptForAzdataUpdate', "A new version of Azure Data CLI ( {0} ) is available, do you wish to update to it now?", version);
|
||||||
export const promptForAzdataUpdateLog = (version: string): string => promptLog(promptForAzdataUpdate(version));
|
export const promptForAzdataUpdateLog = (version: string): string => promptLog(promptForAzdataUpdate(version));
|
||||||
|
|
||||||
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
||||||
export const installError = (err: any): string => localize('azdata.installError', "Error installing azdata: {0}", err.message ?? err);
|
export const installError = (err: any): string => localize('azdata.installError', "Error installing Azure Data CLI: {0}", err.message ?? err);
|
||||||
export const updateError = (err: any): string => localize('azdata.updateError', "Error updating azdata: {0}", err.message ?? err);
|
export const updateError = (err: any): string => localize('azdata.updateError', "Error updating Azure Data CLI: {0}", err.message ?? err);
|
||||||
export const platformUnsupported = (platform: string): string => localize('azdata.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
|
export const platformUnsupported = (platform: string): string => localize('azdata.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
|
||||||
export const unexpectedCommandError = (errMsg: string): string => localize('azdata.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
|
export const unexpectedCommandError = (errMsg: string): string => localize('azdata.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
|
||||||
export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
|
export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
|
||||||
export const noAzdata = localize('azdata.NoAzdata', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
|
export const noAzdata = localize('azdata.noAzdata', "No Azure Data CLI is available, run the command 'Azure Data CLI: Install' to enable the features that require it.");
|
||||||
export const noAzdataWithLink = localize('azdata.noAzdataWithLink', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
|
export const noAzdataWithLink = localize('azdata.noAzdataWithLink', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
|
||||||
export const skipInstall = (config: string): string => localize('azdata.skipInstall', "Skipping installation of azdata, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataInstallKey, config);
|
export const skipInstall = (config: string): string => localize('azdata.skipInstall', "Skipping installation of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataInstallKey, config);
|
||||||
export const skipUpdate = (config: string): string => localize('azdata.skipUpdate', "Skipping update of azdata, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataUpdateKey, config);
|
export const skipUpdate = (config: string): string => localize('azdata.skipUpdate', "Skipping update of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataUpdateKey, config);
|
||||||
|
export const noReleaseVersion = (platform: string, releaseInfo: string): string => localize('azdata.noReleaseVersion', "No release version available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
|
||||||
|
export const noDownloadLink = (platform: string, releaseInfo: string): string => localize('azdata.noDownloadLink', "No download link available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
|
||||||
|
export const failedToParseReleaseInfo = (url: string, fileContents: string, err: any): string => localize('azdata.failedToParseReleaseInfo', "Failed to parse the JSON of contents at: {0}.\nFile contents:\n{1}\nError: {2}", url, fileContents, getErrorMessage(err));
|
||||||
export const azdataUserSettingRead = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingReadLog', "Azure Data CLI user setting: {0}.{1} read, value: {2}", azdataConfigSection, configName, configValue);
|
export const azdataUserSettingRead = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingReadLog', "Azure Data CLI user setting: {0}.{1} read, value: {2}", azdataConfigSection, configName, configValue);
|
||||||
export const azdataUserSettingUpdated = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingUpdatedLog', "Azure Data CLI user setting: {0}.{1} updated, newValue: {2}", azdataConfigSection, configName, configValue);
|
export const azdataUserSettingUpdated = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingUpdatedLog', "Azure Data CLI user setting: {0}.{1} updated, newValue: {2}", azdataConfigSection, configName, configValue);
|
||||||
export const userResponseToInstallPrompt = (response: string | undefined): string => localize('azdata.userResponseInstall', "User Response on prompt to install azdata: {0}", response);
|
export const userResponseToInstallPrompt = (response: string | undefined): string => localize('azdata.userResponseInstall', "User Response on prompt to install Azure Data CLI: {0}", response);
|
||||||
export const userResponseToUpdatePrompt = (response: string | undefined): string => localize('azdata.userResponseUpdate', "User Response on prompt to update azdata: {0}", response);
|
export const userResponseToUpdatePrompt = (response: string | undefined): string => localize('azdata.userResponseUpdate', "User Response on prompt to update Azure Data CLI: {0}", response);
|
||||||
export const userRequestedInstall = localize('azdata.userRequestedInstall', "User requested to install Azure Data CLI using 'Azure Data CLI: Install' command");
|
export const userRequestedInstall = localize('azdata.userRequestedInstall', "User requested to install Azure Data CLI using 'Azure Data CLI: Install' command");
|
||||||
export const userRequestedUpdate = localize('azdata.userRequestedUpdate', "User requested to update Azure Data CLI using 'Azure Data CLI: Check for Update' command");
|
export const userRequestedUpdate = localize('azdata.userRequestedUpdate', "User requested to update Azure Data CLI using 'Azure Data CLI: Check for Update' command");
|
||||||
export const userRequestedAcceptEula = localize('azdata.acceptEula', "User requested to be prompted for accepting EULA by invoking 'Azure Data CLI: Accept EULA' command");
|
export const userRequestedAcceptEula = localize('azdata.acceptEula', "User requested to be prompted for accepting EULA by invoking 'Azure Data CLI: Accept EULA' command");
|
||||||
export const updateCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new Azure Data CLI version availability performed as Azure Data CLI was not found to be installed");
|
export const updateCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new Azure Data CLI version availability performed as Azure Data CLI was not found to be installed");
|
||||||
export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA](command:azdata.acceptEula) to accept EULA to enable the features that requires Azure Data CLI.");
|
export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA](command:azdata.acceptEula) to accept EULA to enable the features that requires Azure Data CLI.");
|
||||||
export const installManually = (expectedVersion: string, instructionsUrl: string) => localize('azdata.installManually', "Azure Data CLI is not installed. Version: {0} needs to be installed or some features may not work. Please install it manually using these [instructions]({1}). Restart ADS when installation is done.", expectedVersion, instructionsUrl);
|
|
||||||
export const installCorrectVersionManually = (currentVersion: string, expectedVersion: string, instructionsUrl: string) => localize('azdata.installCorrectVersionManually', "Azure Data CLI version: {0} is installed, version: {1} needs to be installed or some features may not work. Please uninstall the current version and then install the correct version manually using these [instructions]({2}). Restart ADS when installation is done.", currentVersion, expectedVersion, instructionsUrl);
|
|
||||||
export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl);
|
export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl);
|
||||||
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
||||||
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
|
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
|
||||||
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
|
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
|
||||||
export const eulaAcceptedStateUpdated = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateUpdated', "Updated 'EULA Accepted' state to: {0}", eulaAccepted);
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
@@ -11,10 +10,14 @@ import * as azdata from '../azdata';
|
|||||||
import * as childProcess from '../common/childProcess';
|
import * as childProcess from '../common/childProcess';
|
||||||
import { HttpClient } from '../common/httpClient';
|
import { HttpClient } from '../common/httpClient';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import * as constants from '../constants';
|
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0');
|
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This matches the schema of the JSON file used to determine the current version of
|
||||||
|
* azdata - do not modify unless also updating the corresponding JSON file
|
||||||
|
*/
|
||||||
const releaseJson = {
|
const releaseJson = {
|
||||||
win32: {
|
win32: {
|
||||||
'version': '9999.999.999',
|
'version': '9999.999.999',
|
||||||
@@ -27,7 +30,7 @@ const releaseJson = {
|
|||||||
'version': '9999.999.999'
|
'version': '9999.999.999'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let executeSudoCommandStub: sinon.SinonStub;
|
||||||
|
|
||||||
describe('azdata', function () {
|
describe('azdata', function () {
|
||||||
afterEach(function (): void {
|
afterEach(function (): void {
|
||||||
@@ -55,9 +58,11 @@ describe('azdata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('installAzdata', function (): void {
|
describe('installAzdata', function (): void {
|
||||||
|
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(<any>loc.yes));
|
sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(<any>loc.yes));
|
||||||
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
|
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
|
||||||
|
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('successful install', async function (): Promise<void> {
|
it('successful install', async function (): Promise<void> {
|
||||||
@@ -77,8 +82,12 @@ describe('azdata', function () {
|
|||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
it('unsuccessful download - win32', async function (): Promise<void> {
|
it('unsuccessful download - win32', async function (): Promise<void> {
|
||||||
sinon.stub(HttpClient, 'downloadFile').rejects();
|
sinon.stub(HttpClient, 'downloadFile').rejects();
|
||||||
const downloadPromise = azdata.checkAndInstallAzdata();
|
sinon.stub(childProcess, 'executeCommand')
|
||||||
await should(downloadPromise).be.rejected();
|
.onFirstCall()
|
||||||
|
.rejects(new Error('not Found')) // First call mock the tool not being found
|
||||||
|
.resolves({ stdout: '1.0.0', stderr: '' });
|
||||||
|
const azdataTool = await azdata.checkAndInstallAzdata();
|
||||||
|
should(azdataTool).be.undefined();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +109,7 @@ describe('azdata', function () {
|
|||||||
describe('updateAzdata', function (): void {
|
describe('updateAzdata', function (): void {
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
||||||
|
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('successful update', async function (): Promise<void> {
|
it('successful update', async function (): Promise<void> {
|
||||||
@@ -107,7 +117,6 @@ describe('azdata', function () {
|
|||||||
case 'win32':
|
case 'win32':
|
||||||
await testWin32SuccessfulUpdate();
|
await testWin32SuccessfulUpdate();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
await testDarwinSuccessfulUpdate();
|
await testDarwinSuccessfulUpdate();
|
||||||
break;
|
break;
|
||||||
@@ -132,9 +141,8 @@ describe('azdata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
||||||
this.timeout(20000);
|
it('finds latest available version of azdata successfully', async function (): Promise<void> {
|
||||||
it(`finds latest available version of azdata successfully`, async function (): Promise<void> {
|
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(releaseJson));
|
||||||
// if the latest version is not discovered then the following call throws failing the test
|
|
||||||
await azdata.discoverLatestAvailableAzdataVersion();
|
await azdata.discoverLatestAvailableAzdataVersion();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -142,7 +150,7 @@ describe('azdata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function testLinuxUnsuccessfulUpdate() {
|
async function testLinuxUnsuccessfulUpdate() {
|
||||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').rejects();
|
executeSudoCommandStub.rejects();
|
||||||
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(updateDone).be.false();
|
should(updateDone).be.false();
|
||||||
should(executeSudoCommandStub.calledOnce).be.true();
|
should(executeSudoCommandStub.calledOnce).be.true();
|
||||||
@@ -181,16 +189,16 @@ async function testDarwinUnsuccessfulUpdate() {
|
|||||||
|
|
||||||
async function testWin32UnsuccessfulUpdate() {
|
async function testWin32UnsuccessfulUpdate() {
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
|
executeSudoCommandStub.rejects();
|
||||||
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(updateDone).be.false();
|
should(updateDone).be.false('Update should not have been successful');
|
||||||
should(executeCommandStub.calledOnce).be.true();
|
should(executeSudoCommandStub.calledOnce).be.true();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testLinuxSuccessfulUpdate() {
|
async function testLinuxSuccessfulUpdate() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
||||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||||
should(executeCommandStub.calledOnce).be.true();
|
should(executeCommandStub.calledOnce).be.true();
|
||||||
@@ -209,51 +217,39 @@ async function testDarwinSuccessfulUpdate() {
|
|||||||
}];
|
}];
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||||
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
|
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
|
||||||
.callsFake(async (command: string, args: string[]) => {
|
.resolves({
|
||||||
should(command).be.equal('brew');
|
|
||||||
should(args).deepEqual(['info', 'azdata-cli', '--json']);
|
|
||||||
return Promise.resolve({
|
|
||||||
stderr: '',
|
stderr: '',
|
||||||
stdout: JSON.stringify(brewInfoOutput)
|
stdout: JSON.stringify(brewInfoOutput)
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.callsFake(async (_command: string, _args: string[]) => { // return success on all other command executions
|
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
|
|
||||||
});
|
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(executeCommandStub.callCount).be.equal(6);
|
should(executeCommandStub.callCount).be.equal(6);
|
||||||
|
should(executeCommandStub.getCall(2).args[0]).be.equal('brew', '3rd call should have been to brew');
|
||||||
|
should(executeCommandStub.getCall(2).args[1]).deepEqual(['info', 'azdata-cli', '--json'], '3rd call did not have expected arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function testWin32SuccessfulUpdate() {
|
async function testWin32SuccessfulUpdate() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').callsFake(async (command: string, args: string[]) => {
|
|
||||||
should(command).be.equal('msiexec');
|
|
||||||
should(args[0]).be.equal('/qn');
|
|
||||||
should(args[1]).be.equal('/i');
|
|
||||||
should(path.basename(args[2])).be.equal(constants.azdataUri);
|
|
||||||
return { stdout: '0.0.0', stderr: '' };
|
|
||||||
});
|
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(executeCommandStub.calledOnce).be.true();
|
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
|
||||||
|
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32SuccessfulInstall() {
|
async function testWin32SuccessfulInstall() {
|
||||||
|
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||||
.onFirstCall()
|
.onFirstCall()
|
||||||
.callsFake(async (_command: string, _args: string[]) => {
|
.rejects(new Error('not Found')) // First call mock the tool not being found
|
||||||
return Promise.reject(new Error('not Found'));
|
.resolves({ stdout: '1.0.0', stderr: '' });
|
||||||
})
|
executeSudoCommandStub
|
||||||
.callsFake(async (command: string, args: string[]) => {
|
.returns({ stdout: '', stderr: '' });
|
||||||
should(command).be.equal('msiexec');
|
|
||||||
should(args[0]).be.equal('/qn');
|
|
||||||
should(args[1]).be.equal('/i');
|
|
||||||
should(path.basename(args[2])).be.equal(constants.azdataUri);
|
|
||||||
return { stdout: '0.0.0', stderr: '' };
|
|
||||||
});
|
|
||||||
await azdata.checkAndInstallAzdata();
|
await azdata.checkAndInstallAzdata();
|
||||||
should(executeCommandStub.calledTwice).be.true();
|
should(executeCommandStub.calledTwice).be.true(`executeCommand should have been called twice. Actual ${executeCommandStub.getCalls().length}`);
|
||||||
|
should(executeSudoCommandStub.calledOnce).be.true(`executeSudoCommand should have been called once. Actual ${executeSudoCommandStub.getCalls().length}`);
|
||||||
|
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testDarwinSuccessfulInstall() {
|
async function testDarwinSuccessfulInstall() {
|
||||||
@@ -272,23 +268,17 @@ async function testDarwinSuccessfulInstall() {
|
|||||||
async function testLinuxSuccessfulInstall() {
|
async function testLinuxSuccessfulInstall() {
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||||
.onFirstCall()
|
.onFirstCall()
|
||||||
.callsFake(async (_command: string, _args: string[]) => {
|
.rejects(new Error('not Found'))
|
||||||
return Promise.reject(new Error('not Found'));
|
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
})
|
executeSudoCommandStub
|
||||||
.callsFake(async (_command: string, _args: string[]) => {
|
.resolves({ stdout: 'success', stderr: '' });
|
||||||
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
|
|
||||||
});
|
|
||||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand')
|
|
||||||
.callsFake(async (_command: string ) => {
|
|
||||||
return Promise.resolve({ stdout: 'success', stderr: '' });
|
|
||||||
});
|
|
||||||
await azdata.checkAndInstallAzdata();
|
await azdata.checkAndInstallAzdata();
|
||||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||||
should(executeCommandStub.calledThrice).be.true();
|
should(executeCommandStub.calledThrice).be.true();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testLinuxUnsuccessfulInstall() {
|
async function testLinuxUnsuccessfulInstall() {
|
||||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').rejects();
|
executeSudoCommandStub.rejects();
|
||||||
const downloadPromise = azdata.installAzdata();
|
const downloadPromise = azdata.installAzdata();
|
||||||
await should(downloadPromise).be.rejected();
|
await should(downloadPromise).be.rejected();
|
||||||
should(executeSudoCommandStub.calledOnce).be.true();
|
should(executeSudoCommandStub.calledOnce).be.true();
|
||||||
@@ -302,9 +292,9 @@ async function testDarwinUnsuccessfulInstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32UnsuccessfulInstall() {
|
async function testWin32UnsuccessfulInstall() {
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
|
executeSudoCommandStub.rejects();
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
const downloadPromise = azdata.installAzdata();
|
const downloadPromise = azdata.installAzdata();
|
||||||
await should(downloadPromise).be.rejected();
|
await should(downloadPromise).be.rejected();
|
||||||
should(executeCommandStub.calledOnce).be.true();
|
should(executeSudoCommandStub.calledOnce).be.true();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ describe('HttpClient', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getTextContent', function (): void {
|
describe('getTextContent', function (): void {
|
||||||
it.skip('Gets file contents correctly', async function (): Promise<void> {
|
it('Gets file contents correctly', async function (): Promise<void> {
|
||||||
nock('https://127.0.0.1')
|
nock('https://127.0.0.1')
|
||||||
.get('/arbitraryFile')
|
.get('/arbitraryFile')
|
||||||
.replyWithFile(200, __filename);
|
.replyWithFile(200, __filename);
|
||||||
const receivedContents = await HttpClient.getTextContent(`https://127.0.0.1/arbitraryFile`);
|
const receivedContents = await HttpClient.getTextContent(`https://127.0.0.1/arbitraryFile`);
|
||||||
should(receivedContents).equal(await fs.promises.readFile(__filename));
|
should(receivedContents).equal((await fs.promises.readFile(__filename)).toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects on response error', async function (): Promise<void> {
|
it('rejects on response error', async function (): Promise<void> {
|
||||||
|
|||||||
92
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
92
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
@@ -143,25 +143,13 @@ declare module 'azdata-ext' {
|
|||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
readyReplicas: string, // "1/1"
|
readyReplicas: string, // "1/1"
|
||||||
state: string, // "Ready"
|
state: string, // "Ready",
|
||||||
|
logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:miaa1'))
|
||||||
|
metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=miaa1-0
|
||||||
externalEndpoint?: string // "10.91.86.39:32718"
|
externalEndpoint?: string // "10.91.86.39:32718"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostgresServerShowResult {
|
|
||||||
apiVersion: string, // "arcdata.microsoft.com/v1alpha1"
|
|
||||||
kind: string, // "postgresql-12"
|
|
||||||
metadata: {
|
|
||||||
creationTimestamp: string, // "2020-08-19T20:25:11Z"
|
|
||||||
generation: number, // 1
|
|
||||||
name: string, // "chgagnon-pg"
|
|
||||||
namespace: string, // "arc",
|
|
||||||
resourceVersion: string, // "214944",
|
|
||||||
selfLink: string, // "/apis/arcdata.microsoft.com/v1alpha1/namespaces/arc/postgresql-12s/chgagnon-pg",
|
|
||||||
uid: string, // "26d0f5bb-0c0b-4225-a6b5-5be2bf6feac0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PostgresServerShowResult {
|
export interface PostgresServerShowResult {
|
||||||
apiVersion: string, // "arcdata.microsoft.com/v1alpha1"
|
apiVersion: string, // "arcdata.microsoft.com/v1alpha1"
|
||||||
kind: string, // "postgresql-12"
|
kind: string, // "postgresql-12"
|
||||||
@@ -175,25 +163,56 @@ declare module 'azdata-ext' {
|
|||||||
uid: string, // "26d0f5bb-0c0b-4225-a6b5-5be2bf6feac0"
|
uid: string, // "26d0f5bb-0c0b-4225-a6b5-5be2bf6feac0"
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
backups: {
|
engine: {
|
||||||
deltaMinutes: number, // 3,
|
extensions: {
|
||||||
fullMinutes: number, // 10,
|
name: string // "citus"
|
||||||
tiers: [
|
}[],
|
||||||
{
|
settings: {
|
||||||
retention: {
|
default: { [key: string]: string } // { "max_connections": "101", "work_mem": "4MB" }
|
||||||
maximums: string[], // [ "6", "512MB" ],
|
}
|
||||||
minimums: string[], // [ "3" ]
|
},
|
||||||
|
scale: {
|
||||||
|
shards: number // 1
|
||||||
|
},
|
||||||
|
scheduling: {
|
||||||
|
default: {
|
||||||
|
resources: {
|
||||||
|
requests: {
|
||||||
|
cpu: string, // "1.5"
|
||||||
|
memory: string // "256Mi"
|
||||||
|
},
|
||||||
|
limits: {
|
||||||
|
cpu: string, // "1.5"
|
||||||
|
memory: string // "256Mi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
type: string, // "NodePort"
|
||||||
|
port: number // 5432
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
volumeSize: string, // "1Gi"
|
data: {
|
||||||
|
className: string, // "local-storage"
|
||||||
|
size: string // "5Gi"
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
className: string, // "local-storage"
|
||||||
|
size: string // "5Gi"
|
||||||
|
},
|
||||||
|
backups: {
|
||||||
|
className: string, // "local-storage"
|
||||||
|
size: string // "5Gi"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
externalEndpoint: string, // "10.130.12.136:26630"
|
||||||
readyPods: string, // "1/1",
|
readyPods: string, // "1/1",
|
||||||
state: string // "Ready"
|
state: string, // "Ready"
|
||||||
}
|
logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:pg1'))
|
||||||
|
metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=pg1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +238,25 @@ declare module 'azdata-ext' {
|
|||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
|
delete(name: string): Promise<AzdataOutput<void>>,
|
||||||
list(): Promise<AzdataOutput<PostgresServerListResult[]>>,
|
list(): Promise<AzdataOutput<PostgresServerListResult[]>>,
|
||||||
show(name: string): Promise<AzdataOutput<PostgresServerShowResult>>
|
show(name: string): Promise<AzdataOutput<PostgresServerShowResult>>,
|
||||||
|
edit(
|
||||||
|
name: string,
|
||||||
|
args: {
|
||||||
|
adminPassword?: boolean,
|
||||||
|
coresLimit?: string,
|
||||||
|
coresRequest?: string,
|
||||||
|
engineSettings?: string,
|
||||||
|
extensions?: string,
|
||||||
|
memoryLimit?: string,
|
||||||
|
memoryRequest?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
port?: number,
|
||||||
|
replaceEngineSettings?: boolean,
|
||||||
|
workers?: number
|
||||||
|
},
|
||||||
|
additionalEnvVars?: { [key: string]: string }): Promise<AzdataOutput<void>>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sql: {
|
sql: {
|
||||||
|
|||||||
Reference in New Issue
Block a user