mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 02:51:36 -05:00
Compare commits
386 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a45ba7cf2 | ||
|
|
3b23809846 | ||
|
|
0ff54a11df | ||
|
|
ce547fa4b6 | ||
|
|
d47b5f1afb | ||
|
|
66ef175501 | ||
|
|
f92db4ae76 | ||
|
|
c520c009a5 | ||
|
|
fd148e557b | ||
|
|
b0d3d06b5d | ||
|
|
2fbfb2ad61 | ||
|
|
4b12216c07 | ||
|
|
98dc71a08b | ||
|
|
05f8bb94f3 | ||
|
|
de027df955 | ||
|
|
4a4888891b | ||
|
|
0b2d353cd2 | ||
|
|
2dd4232a40 | ||
|
|
dc0ccba767 | ||
|
|
a4bccb6e6c | ||
|
|
f1b9931116 | ||
|
|
0f229b3444 | ||
|
|
1a97516cb6 | ||
|
|
9ee941eb12 | ||
|
|
512c2d3ddc | ||
|
|
57f5e04590 | ||
|
|
fddfb72e10 | ||
|
|
90ac21ccfb | ||
|
|
310d651df8 | ||
|
|
63c461dca8 | ||
|
|
6c40f52f35 | ||
|
|
013ce71166 | ||
|
|
3a4caa64d2 | ||
|
|
aeabb325f6 | ||
|
|
b8da94f9ef | ||
|
|
e6f356accc | ||
|
|
991b9af198 | ||
|
|
09a4dfa5b0 | ||
|
|
8ed463b4d7 | ||
|
|
3b0cf9db50 | ||
|
|
9f761c44c4 | ||
|
|
c6308b77df | ||
|
|
33baaa475d | ||
|
|
35983659b1 | ||
|
|
2b103a79c3 | ||
|
|
99b5c5ce8c | ||
|
|
84d85ba43e | ||
|
|
7b709b37cd | ||
|
|
3d7edd2d6a | ||
|
|
cf97ced7f1 | ||
|
|
0060b0e27b | ||
|
|
47b249a7b1 | ||
|
|
c4ef48dcd8 | ||
|
|
366fe7c162 | ||
|
|
d189805bcc | ||
|
|
aef4474a08 | ||
|
|
5e34982fd9 | ||
|
|
43158a60e3 | ||
|
|
e3d672cea1 | ||
|
|
0567141bc4 | ||
|
|
6985d95300 | ||
|
|
e2dd257fa9 | ||
|
|
da7585eb44 | ||
|
|
2f8d00af56 | ||
|
|
7cdb21cca5 | ||
|
|
b35e78a07f | ||
|
|
052cb54199 | ||
|
|
42b1a10fec | ||
|
|
d28d77dbfc | ||
|
|
aadf2ae081 | ||
|
|
760cf01022 | ||
|
|
97978cbe81 | ||
|
|
466193adbe | ||
|
|
56ad631478 | ||
|
|
919cc732b7 | ||
|
|
244e27c2de | ||
|
|
0a181a1ba8 | ||
|
|
045dc3e558 | ||
|
|
30393a1f1b | ||
|
|
748bb53173 | ||
|
|
373a519f25 | ||
|
|
3af2b4a13d | ||
|
|
91676afd0d | ||
|
|
914fe8fc29 | ||
|
|
65cc61fdbd | ||
|
|
83af84774a | ||
|
|
b3e9428898 | ||
|
|
2427cbe3c6 | ||
|
|
179678b495 | ||
|
|
a7c1bcaf93 | ||
|
|
3362462142 | ||
|
|
87cc568493 | ||
|
|
8a67f87090 | ||
|
|
47151435e7 | ||
|
|
30dffdf696 | ||
|
|
68a22421f7 | ||
|
|
d6fd64c5eb | ||
|
|
245ae5b9ee | ||
|
|
788c84a1ee | ||
|
|
aafe0876bb | ||
|
|
e3c7e06983 | ||
|
|
251d250523 | ||
|
|
f4d0bdc784 | ||
|
|
0bbbb91adf | ||
|
|
39f65b1881 | ||
|
|
0e9d956ee5 | ||
|
|
01671b118d | ||
|
|
ef3d2e7d99 | ||
|
|
1b073c6748 | ||
|
|
daa897936b | ||
|
|
eaf9757565 | ||
|
|
d1d858090c | ||
|
|
c178b6327a | ||
|
|
c32c09e1a7 | ||
|
|
25e237fa35 | ||
|
|
118d03c151 | ||
|
|
27c86e3c45 | ||
|
|
07ad50670e | ||
|
|
8afd420971 | ||
|
|
5ff102d531 | ||
|
|
63a65f5821 | ||
|
|
2d8e0d648a | ||
|
|
7a35d4aeeb | ||
|
|
35207a1e04 | ||
|
|
7c14ec2b6d | ||
|
|
f8da3cc32a | ||
|
|
f01e9e2fc0 | ||
|
|
b2c203eaef | ||
|
|
24349885d3 | ||
|
|
2e9eff7ffc | ||
|
|
6a08af4d9a | ||
|
|
a0f56890b5 | ||
|
|
70fc6bd43d | ||
|
|
c60bcc0d0d | ||
|
|
c3c6d8ee8c | ||
|
|
9a71846e22 | ||
|
|
e4db31b334 | ||
|
|
63dc94009e | ||
|
|
77d397ce18 | ||
|
|
6aeea8f1df | ||
|
|
fdb426cda5 | ||
|
|
f72a252fb0 | ||
|
|
6fcfa93329 | ||
|
|
c4ce3bef8d | ||
|
|
9fda448303 | ||
|
|
01aefe7b9f | ||
|
|
cb8c4b80d0 | ||
|
|
668ab43865 | ||
|
|
be0edf9606 | ||
|
|
151522013f | ||
|
|
66c62fcce3 | ||
|
|
699648ff6d | ||
|
|
7968d51172 | ||
|
|
2e8d9c50d4 | ||
|
|
880cfc3b59 | ||
|
|
c2d45fa01f | ||
|
|
c0dd781d77 | ||
|
|
df5ed2c889 | ||
|
|
107023c7d0 | ||
|
|
ce4fa98691 | ||
|
|
b2a9074a25 | ||
|
|
c082b572d5 | ||
|
|
0509f8f0c3 | ||
|
|
88d28b7d51 | ||
|
|
df177ec779 | ||
|
|
64d432c8e2 | ||
|
|
380457122c | ||
|
|
7d6d8dbe96 | ||
|
|
f2dcfacc8c | ||
|
|
fd954ddcb2 | ||
|
|
c5c7ca019d | ||
|
|
6d4608dd8b | ||
|
|
f390c4cbc2 | ||
|
|
d031211693 | ||
|
|
c1f4c50177 | ||
|
|
87633faaa4 | ||
|
|
dd4e87ed41 | ||
|
|
b472539646 | ||
|
|
b2a2a48ed6 | ||
|
|
a3a06b92e8 | ||
|
|
3b0fff63d4 | ||
|
|
2bc6a881bd | ||
|
|
efa82650f8 | ||
|
|
d1892b514f | ||
|
|
32da4219a9 | ||
|
|
fb16924f93 | ||
|
|
94b99c7862 | ||
|
|
78d905a217 | ||
|
|
be3d966cf0 | ||
|
|
ba6359e1ff | ||
|
|
43cf19e316 | ||
|
|
1eb03404ad | ||
|
|
a322c5be9d | ||
|
|
d663ec6129 | ||
|
|
88fd0cae3b | ||
|
|
3e2bf7b9fa | ||
|
|
3ce6f9e78c | ||
|
|
efaf39f96a | ||
|
|
7c82d0291e | ||
|
|
86c2547b22 | ||
|
|
4a51cb2020 | ||
|
|
260fdac944 | ||
|
|
85082dee75 | ||
|
|
a9b338b5c4 | ||
|
|
f92aa1ead8 | ||
|
|
d4e367e4f9 | ||
|
|
57df7e706f | ||
|
|
b58c19684f | ||
|
|
d0c7028d97 | ||
|
|
dc471faa7a | ||
|
|
4aaa7eae29 | ||
|
|
1000e97091 | ||
|
|
9b31e7beac | ||
|
|
f0b158edda | ||
|
|
9f29efba85 | ||
|
|
de0719d91a | ||
|
|
b9041b0afe | ||
|
|
adb0ec3cab | ||
|
|
3eefc70cbe | ||
|
|
a7c6a98ad9 | ||
|
|
87f5f4edfc | ||
|
|
08d6b71929 | ||
|
|
aba576dd2f | ||
|
|
2c2e2bb984 | ||
|
|
07567d2514 | ||
|
|
9cbcf9e2c6 | ||
|
|
e0cad0231d | ||
|
|
379c60dd27 | ||
|
|
eaba5679d4 | ||
|
|
fdaf29ccb4 | ||
|
|
fa21781df2 | ||
|
|
f978331e7b | ||
|
|
8aedeab9d4 | ||
|
|
669987ccf3 | ||
|
|
2984244377 | ||
|
|
fbb17c047c | ||
|
|
36e1aeb43d | ||
|
|
62d86163c9 | ||
|
|
5171c37b6a | ||
|
|
05f1f84872 | ||
|
|
3d8fb454c0 | ||
|
|
430e2c9916 | ||
|
|
e026b743ab | ||
|
|
c170fdfc68 | ||
|
|
d0ecb292f7 | ||
|
|
7d3fa81d3a | ||
|
|
c1e365bdc8 | ||
|
|
c6f8f6ffd0 | ||
|
|
43e9f6da8c | ||
|
|
5fb7f9e452 | ||
|
|
64ea6c569a | ||
|
|
efdf38dbc4 | ||
|
|
1fe6939f92 | ||
|
|
8332fc6b1d | ||
|
|
7669ad0180 | ||
|
|
e6fd98caa8 | ||
|
|
814f5bea3a | ||
|
|
2e81300f9e | ||
|
|
eed495ef64 | ||
|
|
105636dda6 | ||
|
|
f4ef8d1374 | ||
|
|
01a594ea83 | ||
|
|
f0f6aee89b | ||
|
|
36286f7df6 | ||
|
|
ed5a2ee90d | ||
|
|
0483c5c8b2 | ||
|
|
7f614b0fde | ||
|
|
7c5de73fde | ||
|
|
e499fd9e80 | ||
|
|
a8e4d01ff0 | ||
|
|
ba0261eb98 | ||
|
|
dbf15745d0 | ||
|
|
6e4c7a1075 | ||
|
|
c85ad7b7d5 | ||
|
|
b705e1ca61 | ||
|
|
bc1bf60a98 | ||
|
|
6837b4b801 | ||
|
|
f127b7d94a | ||
|
|
d1d9795965 | ||
|
|
83300acb38 | ||
|
|
24b45a9baf | ||
|
|
4e67aa86d8 | ||
|
|
0dbde9e9b1 | ||
|
|
712633fadd | ||
|
|
7a419426ad | ||
|
|
f17689319c | ||
|
|
ee0896ea5d | ||
|
|
acea03ea61 | ||
|
|
d88c49702a | ||
|
|
5e29c936d7 | ||
|
|
0474a5ca9e | ||
|
|
d09ba43d15 | ||
|
|
164b100421 | ||
|
|
2d2c568609 | ||
|
|
ff6e377477 | ||
|
|
63e97caa94 | ||
|
|
eed792f3db | ||
|
|
3847271e67 | ||
|
|
f53a06a403 | ||
|
|
78a144b5ca | ||
|
|
3f047ae15a | ||
|
|
6df69f525c | ||
|
|
669623a228 | ||
|
|
96efba004d | ||
|
|
dc0eb133f9 | ||
|
|
ac2198c7d3 | ||
|
|
49519232ba | ||
|
|
8c9cc03c89 | ||
|
|
c4830d9efb | ||
|
|
5923793f0c | ||
|
|
126a0383ce | ||
|
|
c70d5b957e | ||
|
|
245156a66c | ||
|
|
ebb02de1c4 | ||
|
|
f662d480e7 | ||
|
|
49dbce5171 | ||
|
|
b3d8e522f7 | ||
|
|
4c421e0a38 | ||
|
|
4275fe89a7 | ||
|
|
9c3d88b64a | ||
|
|
0bf574c227 | ||
|
|
5059c94adc | ||
|
|
2a74ad4190 | ||
|
|
b45f3e7218 | ||
|
|
0ff8786885 | ||
|
|
1e81b6f054 | ||
|
|
785839baac | ||
|
|
d03fbbc066 | ||
|
|
06da33bb3b | ||
|
|
202036ca47 | ||
|
|
8530bf214e | ||
|
|
8faf115329 | ||
|
|
1bd1c64b08 | ||
|
|
68e0f86120 | ||
|
|
43183c90a1 | ||
|
|
b497063482 | ||
|
|
63d4cc0e80 | ||
|
|
351a55121d | ||
|
|
f8583f53c5 | ||
|
|
39aefa7e29 | ||
|
|
a4ffa64918 | ||
|
|
181cad5b75 | ||
|
|
4fd890e651 | ||
|
|
b5411f0f6f | ||
|
|
5496e9ac33 | ||
|
|
5a428b83ae | ||
|
|
1308878650 | ||
|
|
c85e164836 | ||
|
|
c72a194bc3 | ||
|
|
c131c252cd | ||
|
|
72c0d30517 | ||
|
|
ba87c8e245 | ||
|
|
11f5ddf062 | ||
|
|
8faa0cf0e2 | ||
|
|
f7bf914bcb | ||
|
|
d5f52ba53d | ||
|
|
f059612bd0 | ||
|
|
52c21a7885 | ||
|
|
743fd9f97c | ||
|
|
e7f614ebf7 | ||
|
|
bd1633c04c | ||
|
|
5524a3659c | ||
|
|
2785538afb | ||
|
|
0438e9cd41 | ||
|
|
601a4aaed1 | ||
|
|
a0f46fec65 | ||
|
|
a7311764be | ||
|
|
7fc69cc4d5 | ||
|
|
32a5ec3cd0 | ||
|
|
b4ab73a636 | ||
|
|
625d4bc4bf | ||
|
|
45d664fea2 | ||
|
|
cf05dc0016 | ||
|
|
5c17529e40 | ||
|
|
f5e38482c3 | ||
|
|
e3dc417df4 | ||
|
|
affe3a838b | ||
|
|
9fc2cff654 | ||
|
|
32ba55b7ed | ||
|
|
8b383294f7 | ||
|
|
2b8ae507aa | ||
|
|
f7fc145b0c | ||
|
|
6baf2ee982 | ||
|
|
ba20cdb885 | ||
|
|
d8433ddbf4 | ||
|
|
1c1da18f45 |
@@ -12,6 +12,8 @@
|
||||
**/vscode-api-tests/testWorkspace2/**
|
||||
**/extensions/**/out/**
|
||||
**/extensions/**/build/**
|
||||
**/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
|
||||
**/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts
|
||||
**/extensions/markdown-language-features/media/**
|
||||
**/extensions/markdown-language-features/notebook-out/**
|
||||
**/extensions/typescript-basics/test/colorize-fixtures/**
|
||||
|
||||
@@ -581,7 +581,9 @@
|
||||
"iconv-lite-umd",
|
||||
"jschardet",
|
||||
"@angular/*",
|
||||
"rxjs/**"
|
||||
"rxjs/**",
|
||||
"sanitize-html",
|
||||
"ansi_up"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -738,12 +740,12 @@
|
||||
"rxjs/**",
|
||||
"ng2-charts",
|
||||
"chart.js",
|
||||
"plotly.js-dist-min",
|
||||
"plotly.js",
|
||||
"angular2-grid",
|
||||
"html-to-image",
|
||||
"html-query-plan",
|
||||
"turndown",
|
||||
"gridstack",
|
||||
"gridstack/**",
|
||||
"mark.js",
|
||||
"vscode-textmate",
|
||||
"vscode-oniguruma",
|
||||
|
||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -3,10 +3,12 @@
|
||||
# Syntax can be found here: https://docs.github.com/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
|
||||
|
||||
/extensions/admin-tool-ext-win @Charles-Gagnon
|
||||
/extensions/arc/ @Charles-Gagnon
|
||||
/extensions/azdata/ @Charles-Gagnon
|
||||
/extensions/arc/ @Charles-Gagnon @swells @candiceye
|
||||
/extensions/azcli/ @Charles-Gagnon @swells @candiceye
|
||||
/extensions/azdata/ @Charles-Gagnon @swells @candiceye
|
||||
/extensions/big-data-cluster/ @Charles-Gagnon
|
||||
/extensions/dacpac/ @kisantia
|
||||
/extensions/notebook @azure-data-studio-notebook-devs
|
||||
/extensions/query-history/ @Charles-Gagnon
|
||||
/extensions/resource-deployment/ @Charles-Gagnon
|
||||
/extensions/schema-compare/ @kisantia
|
||||
@@ -14,3 +16,6 @@
|
||||
/extensions/mssql/config.json @Charles-Gagnon @alanrenmsft @kburtram
|
||||
|
||||
/src/sql/*.d.ts @alanrenmsft @Charles-Gagnon
|
||||
/src/sql/workbench/browser/modelComponents @Charles-Gagnon @alanrenmsft
|
||||
/src/sql/workbench/api @Charles-Gagnon @alanrenmsft
|
||||
/src/sql/**/notebook @azure-data-studio-notebook-devs
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -204,6 +204,14 @@ jobs:
|
||||
- name: Compile and Download
|
||||
run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
# This is required for keytar unittests, otherwise we hit
|
||||
# https://github.com/atom/node-keytar/issues/76
|
||||
- name: Create temporary keychain
|
||||
run: |
|
||||
security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
security default-keychain -s $RUNNER_TEMP/buildagent.keychain
|
||||
security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,11 +1,29 @@
|
||||
# Change Log
|
||||
|
||||
## Version 1.31.1
|
||||
* Release date: July 29, 2021
|
||||
* Release status: General Availability
|
||||
## Hotfix Release
|
||||
- Fix for [#16436 Database Connection Toolbar Missing](https://github.com/microsoft/azuredatastudio/issues/16436)
|
||||
|
||||
## Version 1.31.0
|
||||
* Release date: July 21, 2021
|
||||
* Release status: General Availability
|
||||
* New Notebook Features:
|
||||
* WYSIWYG link improvements
|
||||
* Extension Updates:
|
||||
* Import
|
||||
* SandDance
|
||||
* SQL Database Projects
|
||||
* Bug Fixes
|
||||
* Accessibility bug fixes
|
||||
|
||||
## Version 1.30.0
|
||||
* Release date: June 17, 2021
|
||||
* Release status: General Availability
|
||||
* New Notebook Features:
|
||||
* Show book's notebook TOC title in pinned notebooks view
|
||||
* Add new book icon
|
||||
* Add new book icon
|
||||
* Update Python to 3.8.10
|
||||
* Query Editor Features:
|
||||
* Added filtering/sorting feature for query result grid in query editor and notebook, the feature can be invoked from the column headers. Note that this feature is only available when you enable the preview features
|
||||
@@ -14,7 +32,7 @@
|
||||
* SQL Database Projects
|
||||
* Machine Learning
|
||||
* Bug Fixes
|
||||
* Fix WYSIWYG Table cell adding new line in table cell
|
||||
* Fix WYSIWYG Table cell adding new line in table cell
|
||||
|
||||
## Version 1.29.0
|
||||
* Release date: May 19, 2021
|
||||
|
||||
14
README.md
14
README.md
@@ -131,10 +131,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [Source EULA](LICENSE.txt).
|
||||
|
||||
[win-user]: https://go.microsoft.com/fwlink/?linkid=2165736
|
||||
[win-system]: https://go.microsoft.com/fwlink/?linkid=2165737
|
||||
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2165838
|
||||
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2165942
|
||||
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2165841
|
||||
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2165842
|
||||
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2165738
|
||||
[win-user]: https://go.microsoft.com/fwlink/?linkid=2168181
|
||||
[win-system]: https://go.microsoft.com/fwlink/?linkid=2168180
|
||||
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2168436
|
||||
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2168435
|
||||
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2168338
|
||||
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2168271
|
||||
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2168339
|
||||
|
||||
@@ -30,6 +30,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
|
||||
getmac: https://github.com/bevry/getmac
|
||||
graceful-fs: https://github.com/isaacs/node-graceful-fs
|
||||
gridstack: https://github.com/gridstack/gridstack.js
|
||||
html-to-image: https://github.com/bubkoo/html-to-image
|
||||
html-query-plan: https://github.com/JustinPealing/html-query-plan
|
||||
http-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
|
||||
https-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
|
||||
@@ -520,6 +521,32 @@ SOFTWARE.
|
||||
=========================================
|
||||
END OF gridstack NOTICES AND INFORMATION
|
||||
|
||||
%% html-to-image NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 W.Y.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
=========================================
|
||||
END OF html-to-image NOTICES AND INFORMATION
|
||||
|
||||
%% html-query-plan NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -171,7 +171,7 @@ steps:
|
||||
done
|
||||
displayName: Archive Logs
|
||||
continueOnError: true
|
||||
condition: succeededOrFailed()
|
||||
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
@@ -11,25 +11,25 @@ steps:
|
||||
inputs:
|
||||
versionSpec: "14.x"
|
||||
|
||||
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
|
||||
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
|
||||
inputs:
|
||||
versionSpec: "1.x"
|
||||
|
||||
- bash: |
|
||||
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
CHANNEL="G1C14HJ2F"
|
||||
# - bash: |
|
||||
# TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
# CHANNEL="G1C14HJ2F"
|
||||
|
||||
if [ "$TAG_VERSION" == "1.999.0" ]; then
|
||||
MESSAGE="<!here>. Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local."
|
||||
# if [ "$TAG_VERSION" == "1.999.0" ]; then
|
||||
# MESSAGE="<!here>. Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local."
|
||||
|
||||
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
-H 'Content-type: application/json; charset=utf-8' \
|
||||
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
|
||||
https://slack.com/api/chat.postMessage
|
||||
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
# -H 'Content-type: application/json; charset=utf-8' \
|
||||
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
|
||||
# https://slack.com/api/chat.postMessage
|
||||
|
||||
exit 1
|
||||
fi
|
||||
displayName: Check 1.999.0 tag
|
||||
# exit 1
|
||||
# fi
|
||||
# displayName: Check 1.999.0 tag
|
||||
|
||||
- bash: |
|
||||
# Install build dependencies
|
||||
@@ -37,47 +37,54 @@ steps:
|
||||
node build/azure-pipelines/publish-types/check-version.js
|
||||
displayName: Check version
|
||||
|
||||
# {{SQL CARBON EDIT}} Modify to fit our own scenario - specifically currently we need to use a fork of the repo since we don't
|
||||
# have an account with push access to DT
|
||||
- bash: |
|
||||
git config --global user.email "vscode@microsoft.com"
|
||||
git config --global user.name "VSCode"
|
||||
git config --global user.email "azuredatastudio@microsoft.com"
|
||||
git config --global user.name "Azure Data Studio"
|
||||
|
||||
git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1
|
||||
git clone https://$(GITHUB_TOKEN)@$(REPO) --depth=1
|
||||
node build/azure-pipelines/publish-types/update-types.js
|
||||
|
||||
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
cd DefinitelyTyped
|
||||
|
||||
# Sync up to latest from the DT repo
|
||||
git remote add upstream https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
git merge upstream/master
|
||||
git push origin
|
||||
|
||||
git diff --color | cat
|
||||
git add -A
|
||||
git status
|
||||
git checkout -b "vscode-types-$TAG_VERSION"
|
||||
git commit -m "VS Code $TAG_VERSION Extension API"
|
||||
git push origin "vscode-types-$TAG_VERSION"
|
||||
git checkout -b "azdata-types-$TAG_VERSION"
|
||||
git commit -m "Azure Data Studio $TAG_VERSION Extension API"
|
||||
git push origin "azdata-types-$TAG_VERSION"
|
||||
|
||||
displayName: Push update to DefinitelyTyped
|
||||
|
||||
- bash: |
|
||||
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
CHANNEL="G1C14HJ2F"
|
||||
# - bash: |
|
||||
# TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
# CHANNEL="G1C14HJ2F"
|
||||
|
||||
MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame champion, please open this link, examine changes and create a PR:"
|
||||
LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details."
|
||||
MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
|
||||
# MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame champion, please open this link, examine changes and create a PR:"
|
||||
# LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details."
|
||||
# MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
|
||||
|
||||
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
-H 'Content-type: application/json; charset=utf-8' \
|
||||
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
|
||||
https://slack.com/api/chat.postMessage
|
||||
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
# -H 'Content-type: application/json; charset=utf-8' \
|
||||
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
|
||||
# https://slack.com/api/chat.postMessage
|
||||
|
||||
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
-H 'Content-type: application/json; charset=utf-8' \
|
||||
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \
|
||||
https://slack.com/api/chat.postMessage
|
||||
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
# -H 'Content-type: application/json; charset=utf-8' \
|
||||
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \
|
||||
# https://slack.com/api/chat.postMessage
|
||||
|
||||
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
-H 'Content-type: application/json; charset=utf-8' \
|
||||
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \
|
||||
https://slack.com/api/chat.postMessage
|
||||
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
|
||||
# -H 'Content-type: application/json; charset=utf-8' \
|
||||
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \
|
||||
# https://slack.com/api/chat.postMessage
|
||||
|
||||
displayName: Send message on Slack
|
||||
# displayName: Send message on Slack
|
||||
|
||||
@@ -13,11 +13,11 @@ try {
|
||||
.execSync('git describe --tags `git rev-list --tags --max-count=1`')
|
||||
.toString()
|
||||
.trim();
|
||||
const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vs/vscode.d.ts`;
|
||||
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts');
|
||||
const dtsUri = `https://raw.githubusercontent.com/microsoft/azuredatastudio/${tag}/src/sql/azdata.d.ts`; // {{SQL CARBON EDIT}} Use our typings
|
||||
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/azdata/index.d.ts'); // {{SQL CARBON EDIT}} Use our typings
|
||||
cp.execSync(`curl ${dtsUri} --output ${outPath}`);
|
||||
updateDTSFile(outPath, tag);
|
||||
console.log(`Done updating vscode.d.ts at ${outPath}`);
|
||||
console.log(`Done updating azdata.d.ts at ${outPath}`); // {{SQL CARBON EDIT}} Use our typings
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
@@ -51,21 +51,25 @@ function getNewFileContent(content, tag) {
|
||||
function getNewFileHeader(tag) {
|
||||
const [major, minor] = tag.split('.');
|
||||
const shorttag = `${major}.${minor}`;
|
||||
// {{SQL CARBON EDIT}} Use our own header
|
||||
const header = [
|
||||
`// Type definitions for Visual Studio Code ${shorttag}`,
|
||||
`// Project: https://github.com/microsoft/vscode`,
|
||||
`// Definitions by: Visual Studio Code Team, Microsoft <https://github.com/Microsoft>`,
|
||||
`// Type definitions for Azure Data Studio ${shorttag}`,
|
||||
`// Project: https://github.com/microsoft/azuredatastudio`,
|
||||
`// Definitions by: Charles Gagnon <https://github.com/Charles-Gagnon>`,
|
||||
`// Alan Ren: <https://github.com/alanrenmsft>`,
|
||||
`// Karl Burtram: <https://github.com/kburtram>`,
|
||||
`// Ken Van Hyning: <https://github.com/kenvanhyning>`,
|
||||
`// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`,
|
||||
``,
|
||||
`/*---------------------------------------------------------------------------------------------`,
|
||||
` * Copyright (c) Microsoft Corporation. All rights reserved.`,
|
||||
` * Licensed under the Source EULA.`,
|
||||
` * See https://github.com/Microsoft/vscode/blob/main/LICENSE.txt for license information.`,
|
||||
` * Licensed under the MIT License.`,
|
||||
` * See https://github.com/Microsoft/azuredatastudio/blob/main/LICENSE.txt for license information.`,
|
||||
` *--------------------------------------------------------------------------------------------*/`,
|
||||
``,
|
||||
`/**`,
|
||||
` * Type Definition for Visual Studio Code ${shorttag} Extension API`,
|
||||
` * See https://code.visualstudio.com/api for more information`,
|
||||
` * Type Definition for Azure Data Studio ${shorttag} Extension API`,
|
||||
` * See https://docs.microsoft.com/sql/azure-data-studio/extensibility-apis for more information`,
|
||||
` */`
|
||||
].join('\n');
|
||||
return header;
|
||||
|
||||
@@ -16,13 +16,13 @@ try {
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vs/vscode.d.ts`;
|
||||
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts');
|
||||
const dtsUri = `https://raw.githubusercontent.com/microsoft/azuredatastudio/${tag}/src/sql/azdata.d.ts`; // {{SQL CARBON EDIT}} Use our typings
|
||||
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/azdata/index.d.ts'); // {{SQL CARBON EDIT}} Use our typings
|
||||
cp.execSync(`curl ${dtsUri} --output ${outPath}`);
|
||||
|
||||
updateDTSFile(outPath, tag);
|
||||
|
||||
console.log(`Done updating vscode.d.ts at ${outPath}`);
|
||||
console.log(`Done updating azdata.d.ts at ${outPath}`); // {{SQL CARBON EDIT}} Use our typings
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error('Failed to update types');
|
||||
@@ -63,21 +63,25 @@ function getNewFileHeader(tag: string) {
|
||||
const [major, minor] = tag.split('.');
|
||||
const shorttag = `${major}.${minor}`;
|
||||
|
||||
// {{SQL CARBON EDIT}} Use our own header
|
||||
const header = [
|
||||
`// Type definitions for Visual Studio Code ${shorttag}`,
|
||||
`// Project: https://github.com/microsoft/vscode`,
|
||||
`// Definitions by: Visual Studio Code Team, Microsoft <https://github.com/Microsoft>`,
|
||||
`// Type definitions for Azure Data Studio ${shorttag}`,
|
||||
`// Project: https://github.com/microsoft/azuredatastudio`,
|
||||
`// Definitions by: Charles Gagnon <https://github.com/Charles-Gagnon>`,
|
||||
`// Alan Ren: <https://github.com/alanrenmsft>`,
|
||||
`// Karl Burtram: <https://github.com/kburtram>`,
|
||||
`// Ken Van Hyning: <https://github.com/kenvanhyning>`,
|
||||
`// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`,
|
||||
``,
|
||||
`/*---------------------------------------------------------------------------------------------`,
|
||||
` * Copyright (c) Microsoft Corporation. All rights reserved.`,
|
||||
` * Licensed under the Source EULA.`,
|
||||
` * See https://github.com/Microsoft/vscode/blob/main/LICENSE.txt for license information.`,
|
||||
` * Licensed under the MIT License.`,
|
||||
` * See https://github.com/Microsoft/azuredatastudio/blob/main/LICENSE.txt for license information.`,
|
||||
` *--------------------------------------------------------------------------------------------*/`,
|
||||
``,
|
||||
`/**`,
|
||||
` * Type Definition for Visual Studio Code ${shorttag} Extension API`,
|
||||
` * See https://code.visualstudio.com/api for more information`,
|
||||
` * Type Definition for Azure Data Studio ${shorttag} Extension API`,
|
||||
` * See https://docs.microsoft.com/sql/azure-data-studio/extensibility-apis for more information`,
|
||||
` */`
|
||||
].join('\n');
|
||||
|
||||
|
||||
@@ -79,19 +79,8 @@ steps:
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn sqllint
|
||||
yarn gulp hygiene
|
||||
yarn strict-vscode
|
||||
yarn valid-layers-check
|
||||
displayName: Run hygiene, eslint
|
||||
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn gulp compile-build
|
||||
yarn gulp compile-extensions-build
|
||||
yarn gulp minify-vscode
|
||||
displayName: Compile
|
||||
yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check sqllint strict-vscode
|
||||
displayName: Compile & Hygiene
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
@@ -15,6 +15,8 @@ const task = require('./lib/task');
|
||||
const glob = require('glob');
|
||||
const vsce = require('vsce');
|
||||
const mkdirp = require('mkdirp');
|
||||
const rename = require('gulp-rename');
|
||||
const fs = require('fs');
|
||||
|
||||
gulp.task('fmt', () => formatStagedFiles());
|
||||
const formatFiles = (some) => {
|
||||
@@ -94,12 +96,14 @@ const root = path.dirname(__dirname);
|
||||
|
||||
gulp.task('package-external-extensions', task.series(
|
||||
task.define('bundle-external-extensions-build', () => ext.packageExternalExtensionsStream().pipe(gulp.dest('.build/external'))),
|
||||
task.define('create-external-extension-vsix-build', () => {
|
||||
task.define('create-external-extension-vsix-build', async () => {
|
||||
const vsixes = glob.sync('.build/external/extensions/*/package.json').map(manifestPath => {
|
||||
const extensionPath = path.dirname(path.join(root, manifestPath));
|
||||
const extensionName = path.basename(extensionPath);
|
||||
return { name: extensionName, path: extensionPath };
|
||||
}).map(element => {
|
||||
})
|
||||
.filter(element => ext.vscodeExternalExtensions.indexOf(element.name) === -1) // VS Code external extensions are bundled into ADS so no need to create a normal VSIX for them
|
||||
.map(element => {
|
||||
const pkgJson = require(path.join(element.path, 'package.json'));
|
||||
const vsixDirectory = path.join(root, '.build', 'extensions');
|
||||
mkdirp.sync(vsixDirectory);
|
||||
@@ -111,8 +115,46 @@ gulp.task('package-external-extensions', task.series(
|
||||
useYarn: true
|
||||
});
|
||||
});
|
||||
// Wait for all the initial VSIXes to be completed before making the VS Code ones since we'll be overwriting
|
||||
// values in the package.json for those.
|
||||
await Promise.all(vsixes);
|
||||
|
||||
return Promise.all(vsixes);
|
||||
// Go through and find the extensions which build separate versions of themselves for VS Code.
|
||||
// This is currently a pretty simplistic process, essentially just replacing certain values in
|
||||
// the package.json. It doesn't handle more complex tasks such as replacing localized strings.
|
||||
const vscodeVsixes = glob.sync('.build/external/extensions/*/package.vscode.json')
|
||||
.map(async vscodeManifestRelativePath => {
|
||||
const vscodeManifestFullPath = path.join(root, vscodeManifestRelativePath);
|
||||
const packageDir = path.dirname(vscodeManifestFullPath);
|
||||
const packageManifestPath = path.join(packageDir, 'package.json');
|
||||
const json = require('gulp-json-editor');
|
||||
const packageJsonStream = gulp.src(packageManifestPath) // Create stream for the original package.json
|
||||
.pipe(json(data => { // And now use gulp-json-editor to modify the contents
|
||||
const updateData = JSON.parse(fs.readFileSync(vscodeManifestFullPath)); // Read in the set of values to replace from package.vscode.json
|
||||
Object.keys(updateData).forEach(key => {
|
||||
data[key] = updateData[key];
|
||||
});
|
||||
// Remove ADS-only menus. This is a subset of the menus listed in https://github.com/microsoft/azuredatastudio/blob/main/src/vs/workbench/api/common/menusExtensionPoint.ts
|
||||
// More can be added to the list as needed.
|
||||
['objectExplorer/item/context', 'dataExplorer/context', 'dashboard/toolbar'].forEach(menu => {
|
||||
delete data.contributes.menus[menu];
|
||||
});
|
||||
return data;
|
||||
}, { beautify: false }))
|
||||
.pipe(gulp.dest(packageDir));
|
||||
await new Promise(resolve => packageJsonStream.on('finish', resolve)); // Wait for the files to finish being updated before packaging
|
||||
const pkgJson = JSON.parse(fs.readFileSync(packageManifestPath));
|
||||
const vsixDirectory = path.join(root, '.build', 'extensions');
|
||||
const packagePath = path.join(vsixDirectory, `${pkgJson.name}-${pkgJson.version}.vsix`);
|
||||
console.info('Creating vsix for ' + packageDir + ' result:' + packagePath);
|
||||
return vsce.createVSIX({
|
||||
cwd: packageDir,
|
||||
packagePath: packagePath,
|
||||
useYarn: true
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(vscodeVsixes);
|
||||
})
|
||||
));
|
||||
|
||||
|
||||
@@ -112,33 +112,33 @@ gulp.task(optimizeVSCodeTask);
|
||||
|
||||
// List of ADS extension XLF files that we want to put into the English resource folder.
|
||||
const extensionsFilter = filter([
|
||||
"**/admin-tool-ext-win.xlf",
|
||||
"**/agent.xlf",
|
||||
"**/arc.xlf",
|
||||
"**/asde-deployment.xlf",
|
||||
"**/azdata.xlf",
|
||||
"**/azurecore.xlf",
|
||||
"**/azurehybridtoolkit.xlf",
|
||||
"**/big-data-cluster.xlf",
|
||||
"**/cms.xlf",
|
||||
"**/dacpac.xlf",
|
||||
"**/data-workspace.xlf",
|
||||
"**/import.xlf",
|
||||
"**/kusto.xlf",
|
||||
"**/machine-learning.xlf",
|
||||
"**/Microsoft.sqlservernotebook.xlf",
|
||||
"**/mssql.xlf",
|
||||
"**/notebook.xlf",
|
||||
"**/profiler.xlf",
|
||||
"**/query-history.xlf",
|
||||
"**/resource-deployment.xlf",
|
||||
"**/schema-compare.xlf",
|
||||
"**/server-report.xlf",
|
||||
"**/sql-assessment.xlf",
|
||||
"**/sql-database-projects.xlf",
|
||||
"**/sql-migration.xlf",
|
||||
"**/xml-language-features.xlf"
|
||||
])
|
||||
'**/admin-tool-ext-win.xlf',
|
||||
'**/agent.xlf',
|
||||
'**/arc.xlf',
|
||||
'**/asde-deployment.xlf',
|
||||
'**/azdata.xlf',
|
||||
'**/azurecore.xlf',
|
||||
'**/azurehybridtoolkit.xlf',
|
||||
'**/big-data-cluster.xlf',
|
||||
'**/cms.xlf',
|
||||
'**/dacpac.xlf',
|
||||
'**/data-workspace.xlf',
|
||||
'**/import.xlf',
|
||||
'**/kusto.xlf',
|
||||
'**/machine-learning.xlf',
|
||||
'**/Microsoft.sqlservernotebook.xlf',
|
||||
'**/mssql.xlf',
|
||||
'**/notebook.xlf',
|
||||
'**/profiler.xlf',
|
||||
'**/query-history.xlf',
|
||||
'**/resource-deployment.xlf',
|
||||
'**/schema-compare.xlf',
|
||||
'**/server-report.xlf',
|
||||
'**/sql-assessment.xlf',
|
||||
'**/sql-database-projects.xlf',
|
||||
'**/sql-migration.xlf',
|
||||
'**/xml-language-features.xlf'
|
||||
]);
|
||||
|
||||
// Copy ADS extension XLFs into English resource folder.
|
||||
const importExtensionsTask = task.define('import-extensions-xlfs', function () {
|
||||
@@ -149,7 +149,7 @@ const importExtensionsTask = task.define('import-extensions-xlfs', function () {
|
||||
)
|
||||
.pipe(vfs.dest(`./resources/xlf/en`));
|
||||
});
|
||||
gulp.task(importExtensionsTask)
|
||||
gulp.task(importExtensionsTask);
|
||||
// {{SQL CARBON EDIT}} end
|
||||
|
||||
const sourceMappingURLBase = `https://sqlopsbuilds.blob.core.windows.net/sourcemaps/${commit}`;
|
||||
@@ -486,7 +486,7 @@ const vscodeTranslationsExport = task.define(
|
||||
'vscode-translations-export',
|
||||
task.series(
|
||||
compileBuildTask,
|
||||
compileLocalizationExtensionsBuildTask, // {{SQL CARBON EDIT}} now include all extensions in ADS, not just a subset. (replaces "compileExtensionsBuildTask" here).
|
||||
compileLocalizationExtensionsBuildTask, // {{SQL CARBON EDIT}} now include all extensions in ADS, not just a subset. (replaces 'compileExtensionsBuildTask' here).
|
||||
optimizeVSCodeTask,
|
||||
function () {
|
||||
const pathToMetadata = './out-vscode/nls.metadata.json';
|
||||
@@ -501,7 +501,7 @@ const vscodeTranslationsExport = task.define(
|
||||
}
|
||||
)
|
||||
);
|
||||
gulp.task(vscodeTranslationsExport)
|
||||
gulp.task(vscodeTranslationsExport);
|
||||
|
||||
// {{SQL CARBON EDIT}} Localization gulp task, runs vscodeTranslationsExport and imports a subset of the generated XLFs into the folder.
|
||||
gulp.task(task.define(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.translatePackageJSON = exports.packageRebuildExtensionsStream = exports.cleanRebuildExtensions = exports.packageExternalExtensionsStream = exports.scanBuiltinExtensions = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = exports.fromLocalNormal = exports.fromLocal = void 0;
|
||||
exports.translatePackageJSON = exports.packageRebuildExtensionsStream = exports.cleanRebuildExtensions = exports.packageExternalExtensionsStream = exports.scanBuiltinExtensions = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.vscodeExternalExtensions = exports.fromMarketplace = exports.fromLocalNormal = exports.fromLocal = void 0;
|
||||
const es = require("event-stream");
|
||||
const fs = require("fs");
|
||||
const glob = require("glob");
|
||||
@@ -232,6 +232,12 @@ const externalExtensions = [
|
||||
'sql-database-projects',
|
||||
'sql-migration'
|
||||
];
|
||||
/**
|
||||
* Extensions that are built into ADS but should be packaged externally as well for VS Code.
|
||||
*/
|
||||
exports.vscodeExternalExtensions = [
|
||||
'data-workspace'
|
||||
];
|
||||
// extensions that require a rebuild since they have native parts
|
||||
const rebuildExtensions = [
|
||||
'big-data-cluster',
|
||||
@@ -348,7 +354,7 @@ function packageExternalExtensionsStream() {
|
||||
const extensionName = path.basename(extensionPath);
|
||||
return { name: extensionName, path: extensionPath };
|
||||
})
|
||||
.filter(({ name }) => externalExtensions.indexOf(name) >= 0);
|
||||
.filter(({ name }) => externalExtensions.indexOf(name) >= 0 || exports.vscodeExternalExtensions.indexOf(name) >= 0);
|
||||
const builtExtensions = extenalExtensionDescriptions.map(extension => {
|
||||
return fromLocal(extension.path, false)
|
||||
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
|
||||
|
||||
@@ -268,6 +268,13 @@ const externalExtensions = [
|
||||
'sql-migration'
|
||||
];
|
||||
|
||||
/**
|
||||
* Extensions that are built into ADS but should be packaged externally as well for VS Code.
|
||||
*/
|
||||
export const vscodeExternalExtensions = [
|
||||
'data-workspace'
|
||||
];
|
||||
|
||||
// extensions that require a rebuild since they have native parts
|
||||
const rebuildExtensions = [
|
||||
'big-data-cluster',
|
||||
@@ -425,7 +432,7 @@ export function packageExternalExtensionsStream(): NodeJS.ReadWriteStream {
|
||||
const extensionName = path.basename(extensionPath);
|
||||
return { name: extensionName, path: extensionPath };
|
||||
})
|
||||
.filter(({ name }) => externalExtensions.indexOf(name) >= 0);
|
||||
.filter(({ name }) => externalExtensions.indexOf(name) >= 0 || vscodeExternalExtensions.indexOf(name) >= 0);
|
||||
|
||||
const builtExtensions = extenalExtensionDescriptions.map(extension => {
|
||||
return fromLocal(extension.path, false)
|
||||
|
||||
@@ -527,7 +527,11 @@ function createXlfFilesForCoreBundle() {
|
||||
if (file.isBuffer()) {
|
||||
const xlfs = Object.create(null);
|
||||
const json = JSON.parse(file.contents.toString('utf8'));
|
||||
for (let coreModule in json.keys) {
|
||||
// {{SQL CARBON EDIT}} - Must sort the keys for easier translation.
|
||||
let sortedKeys = Object.keys(json.keys).sort();
|
||||
for (let i = 0; i < sortedKeys.length; i++) {
|
||||
let coreModule = sortedKeys[i];
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
const projectResource = getResource(coreModule);
|
||||
const resource = projectResource.name;
|
||||
const project = projectResource.project;
|
||||
|
||||
@@ -652,7 +652,11 @@ export function createXlfFilesForCoreBundle(): ThroughStream {
|
||||
if (file.isBuffer()) {
|
||||
const xlfs: Map<XLF> = Object.create(null);
|
||||
const json: BundledFormat = JSON.parse((file.contents as Buffer).toString('utf8'));
|
||||
for (let coreModule in json.keys) {
|
||||
// {{SQL CARBON EDIT}} - Must sort the keys for easier translation.
|
||||
let sortedKeys = Object.keys(json.keys).sort();
|
||||
for (let i = 0; i < sortedKeys.length; i++) {
|
||||
let coreModule = sortedKeys[i];
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
const projectResource = getResource(coreModule);
|
||||
const resource = projectResource.name;
|
||||
const project = projectResource.project;
|
||||
|
||||
@@ -238,7 +238,7 @@ function refreshLangpacks() {
|
||||
}
|
||||
let packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString());
|
||||
//processing extension fields, version and folder name must be changed manually.
|
||||
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText);
|
||||
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText).toLowerCase();
|
||||
packageJSON['displayName'] = packageJSON['displayName'].replace('Visual Studio Code', textFields.displayNameText);
|
||||
packageJSON['publisher'] = textFields.publisherText;
|
||||
packageJSON['license'] = textFields.licenseText;
|
||||
@@ -266,18 +266,6 @@ function refreshLangpacks() {
|
||||
if (languageId === "zh-tw") {
|
||||
languageId = "zh-hant";
|
||||
}
|
||||
//remove extensions not part of ADS.
|
||||
if (fs.existsSync(translationDataFolder)) {
|
||||
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
|
||||
for (let extensionTag in totalExtensions) {
|
||||
let extensionFileName = totalExtensions[extensionTag];
|
||||
let xlfPath = path.join(location, `${languageId}`, extensionFileName.replace('.i18n.json', '.xlf'));
|
||||
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
|
||||
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
|
||||
rimraf.sync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Importing translations for ${languageId} from '${location}' to '${translationDataFolder}' ...`);
|
||||
let translationPaths = [];
|
||||
gulp.src(path.join(location, languageId, '**', '*.xlf'))
|
||||
@@ -356,6 +344,8 @@ function renameVscodeLangpacks() {
|
||||
}
|
||||
let locADSFolder = path.join('.', 'i18n', `ads-language-pack-${langId}`);
|
||||
let locVSCODEFolder = path.join('.', 'i18n', `vscode-language-pack-${langId}`);
|
||||
let translationDataFolder = path.join(locVSCODEFolder, 'translations');
|
||||
let xlfFolder = path.join('.', 'resources', 'xlf');
|
||||
try {
|
||||
fs.statSync(locVSCODEFolder);
|
||||
}
|
||||
@@ -363,12 +353,31 @@ function renameVscodeLangpacks() {
|
||||
console.log('vscode pack is not in ADS yet: ' + langId);
|
||||
continue;
|
||||
}
|
||||
gulp.src(`i18n/ads-language-pack-${langId}/*.md`)
|
||||
.pipe(vfs.dest(locVSCODEFolder, { overwrite: true }))
|
||||
.end(function () {
|
||||
rimraf.sync(locADSFolder);
|
||||
fs.renameSync(locVSCODEFolder, locADSFolder);
|
||||
//Delete any erroneous zip files found in vscode folder.
|
||||
let globZipArray = glob.sync(path.join(locVSCODEFolder, '*.zip'));
|
||||
globZipArray.forEach(element => {
|
||||
fs.unlinkSync(element);
|
||||
});
|
||||
// Delete extension files in vscode language pack that are not in ADS.
|
||||
if (fs.existsSync(translationDataFolder)) {
|
||||
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
|
||||
for (let extensionTag in totalExtensions) {
|
||||
let extensionFileName = totalExtensions[extensionTag];
|
||||
let xlfPath = path.join(xlfFolder, `${langId}`, extensionFileName.replace('.i18n.json', '.xlf'));
|
||||
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
|
||||
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
|
||||
rimraf.sync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Get list of md files in ADS langpack, to copy to vscode langpack prior to renaming.
|
||||
let globMDArray = glob.sync(path.join(locADSFolder, '*.md'));
|
||||
//Copy files to vscode langpack, then remove the ADS langpack, and finally rename the vscode langpack to match the ADS one.
|
||||
globMDArray.forEach(element => {
|
||||
fs.copyFileSync(element, path.join(locVSCODEFolder, path.parse(element).base));
|
||||
});
|
||||
rimraf.sync(locADSFolder);
|
||||
fs.renameSync(locVSCODEFolder, locADSFolder);
|
||||
}
|
||||
console.log("Langpack Rename Completed.");
|
||||
return Promise.resolve();
|
||||
|
||||
@@ -256,7 +256,7 @@ export function refreshLangpacks(): Promise<void> {
|
||||
}
|
||||
let packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString());
|
||||
//processing extension fields, version and folder name must be changed manually.
|
||||
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText);
|
||||
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText).toLowerCase();
|
||||
packageJSON['displayName'] = packageJSON['displayName'].replace('Visual Studio Code', textFields.displayNameText);
|
||||
packageJSON['publisher'] = textFields.publisherText;
|
||||
packageJSON['license'] = textFields.licenseText;
|
||||
@@ -287,20 +287,6 @@ export function refreshLangpacks(): Promise<void> {
|
||||
languageId = "zh-hant";
|
||||
}
|
||||
|
||||
//remove extensions not part of ADS.
|
||||
if (fs.existsSync(translationDataFolder)) {
|
||||
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
|
||||
for (let extensionTag in totalExtensions) {
|
||||
let extensionFileName = totalExtensions[extensionTag];
|
||||
let xlfPath = path.join(location, `${languageId}`, extensionFileName.replace('.i18n.json', '.xlf'))
|
||||
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
|
||||
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
|
||||
rimraf.sync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`Importing translations for ${languageId} from '${location}' to '${translationDataFolder}' ...`);
|
||||
let translationPaths: any = [];
|
||||
gulp.src(path.join(location, languageId, '**', '*.xlf'))
|
||||
@@ -380,6 +366,8 @@ export function renameVscodeLangpacks(): Promise<void> {
|
||||
}
|
||||
let locADSFolder = path.join('.', 'i18n', `ads-language-pack-${langId}`);
|
||||
let locVSCODEFolder = path.join('.', 'i18n', `vscode-language-pack-${langId}`);
|
||||
let translationDataFolder = path.join(locVSCODEFolder, 'translations');
|
||||
let xlfFolder = path.join('.', 'resources', 'xlf');
|
||||
try {
|
||||
fs.statSync(locVSCODEFolder);
|
||||
}
|
||||
@@ -387,12 +375,35 @@ export function renameVscodeLangpacks(): Promise<void> {
|
||||
console.log('vscode pack is not in ADS yet: ' + langId);
|
||||
continue;
|
||||
}
|
||||
gulp.src(`i18n/ads-language-pack-${langId}/*.md`)
|
||||
.pipe(vfs.dest(locVSCODEFolder, {overwrite: true}))
|
||||
.end(function () {
|
||||
rimraf.sync(locADSFolder);
|
||||
fs.renameSync(locVSCODEFolder, locADSFolder);
|
||||
});
|
||||
|
||||
//Delete any erroneous zip files found in vscode folder.
|
||||
let globZipArray = glob.sync(path.join(locVSCODEFolder, '*.zip'));
|
||||
globZipArray.forEach(element => {
|
||||
fs.unlinkSync(element);
|
||||
});
|
||||
|
||||
// Delete extension files in vscode language pack that are not in ADS.
|
||||
if (fs.existsSync(translationDataFolder)) {
|
||||
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
|
||||
for (let extensionTag in totalExtensions) {
|
||||
let extensionFileName = totalExtensions[extensionTag];
|
||||
let xlfPath = path.join(xlfFolder, `${langId}`, extensionFileName.replace('.i18n.json', '.xlf'))
|
||||
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
|
||||
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
|
||||
rimraf.sync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Get list of md files in ADS langpack, to copy to vscode langpack prior to renaming.
|
||||
let globMDArray = glob.sync(path.join(locADSFolder, '*.md'));
|
||||
|
||||
//Copy files to vscode langpack, then remove the ADS langpack, and finally rename the vscode langpack to match the ADS one.
|
||||
globMDArray.forEach(element => {
|
||||
fs.copyFileSync(element, path.join(locVSCODEFolder,path.parse(element).base));
|
||||
});
|
||||
rimraf.sync(locADSFolder);
|
||||
fs.renameSync(locVSCODEFolder, locADSFolder);
|
||||
}
|
||||
|
||||
console.log("Langpack Rename Completed.");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
(async () => {
|
||||
const serviceDownloader = require('service-downloader').ServiceDownloadProvider;
|
||||
const serviceDownloader = require('@microsoft/ads-service-downloader').ServiceDownloadProvider;
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/ads-extension-telemetry": "^1.1.3",
|
||||
"service-downloader": "0.2.1",
|
||||
"@microsoft/ads-service-downloader": "0.2.2",
|
||||
"vscode-nls": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -189,6 +189,20 @@
|
||||
dependencies:
|
||||
vscode-extension-telemetry "^0.1.6"
|
||||
|
||||
"@microsoft/ads-service-downloader@0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/ads-service-downloader/-/ads-service-downloader-0.2.2.tgz#1a32c62eadb77fdab23eed257aaa5b590ccf2ce4"
|
||||
integrity sha512-sEvp1dCQu8ZnUyrjX9qUo/S9IIvXkPCkCSuJ4IhuGEmGm2/++evixOtUgRKWYSRRvL1QskIUjZ0I59m6bKJ5yQ==
|
||||
dependencies:
|
||||
async-retry "^1.2.3"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.3"
|
||||
mkdirp "^0.5.1"
|
||||
tar "^6.1.6"
|
||||
tmp "^0.0.33"
|
||||
yauzl "^2.10.0"
|
||||
|
||||
"@types/mocha@^5.2.5":
|
||||
version "5.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
|
||||
@@ -299,10 +313,10 @@ charenc@~0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
||||
|
||||
chownr@^1.1.3:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
cls-hooked@^4.2.2:
|
||||
version "4.2.2"
|
||||
@@ -695,10 +709,10 @@ minipass@^3.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
|
||||
integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
|
||||
minizlib@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
@@ -839,20 +853,6 @@ semver@^6.0.0, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
service-downloader@0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/service-downloader/-/service-downloader-0.2.1.tgz#8bd756bc4bc0cbfdf04fe71d4337f19ce6196203"
|
||||
integrity sha512-5IEy2nyMJj/f41pI65b8RMeJyCecGNrMmNCpUW8hckZ9cBMyX+VCp8GjYoM6Mz/X0XSaGVz7V5gtCWjfeJI7gA==
|
||||
dependencies:
|
||||
async-retry "^1.2.3"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.3"
|
||||
mkdirp "^0.5.1"
|
||||
tar "^6.0.1"
|
||||
tmp "^0.0.33"
|
||||
yauzl "^2.10.0"
|
||||
|
||||
shimmer@^1.1.0, shimmer@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
||||
@@ -950,15 +950,15 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
tar@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa"
|
||||
integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==
|
||||
tar@^6.1.6:
|
||||
version "6.1.6"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.6.tgz#c23d797b0a1efe5d479b1490805c5443f3560c5d"
|
||||
integrity sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==
|
||||
dependencies:
|
||||
chownr "^1.1.3"
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^3.0.0"
|
||||
minizlib "^2.1.0"
|
||||
minizlib "^2.1.1"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
Welcome to Microsoft Azure Arc Extension for Azure Data Studio!
|
||||
|
||||
**This extension is only applicable to customers in the Azure Arc data services public preview.**
|
||||
|
||||
## Overview
|
||||
|
||||
This extension adds the following features to Azure Data Studio.
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"|Tools|Description|Installation|\n",
|
||||
"|---|---|---|\n",
|
||||
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
|
||||
"|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) |"
|
||||
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
|
||||
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "714582b9-10ee-409e-ab12-15a4825c9471"
|
||||
@@ -64,8 +65,9 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"import sys,os,json,html,getpass,time, tempfile\n",
|
||||
"import sys,os,getpass\n",
|
||||
"def run_command(command):\n",
|
||||
" print(\"Executing: \" + command)\n",
|
||||
" !{command}\n",
|
||||
@@ -73,12 +75,11 @@
|
||||
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
|
||||
" print(f'Successfully executed: {command}')"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -101,15 +102,15 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"run_command('azdata --version')"
|
||||
"run_command('az --version')"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "691671d7-3f05-406c-a183-4cff7d17f83d",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -122,6 +123,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"if \"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\" in os.environ:\n",
|
||||
" arc_admin_password = os.environ[\"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\"]\n",
|
||||
@@ -134,12 +136,11 @@
|
||||
" if arc_admin_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -152,17 +153,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"os.environ[\"KUBECONFIG\"] = arc_config_file\n",
|
||||
"run_command(f'kubectl config use-context {arc_cluster_context}')\n",
|
||||
"run_command('kubectl config current-context')"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -175,22 +176,22 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"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[\"AZDATA_USERNAME\"] = arc_admin_username\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
|
||||
"\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",
|
||||
"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'If you don\\'t see output produced by az, 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'az arcdata dc create --connectivity-mode indirect --name {arc_data_controller_name} --k8s-namespace {arc_data_controller_namespace} --subscription {arc_subscription} --resource-group {arc_resource_group} --location {arc_data_controller_location} --storage-class {arc_data_controller_storage_class} --profile-name {arc_profile} --infrastructure {arc_infrastructure} --use-k8s')\n",
|
||||
"print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') "
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -203,38 +204,16 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"# Setting context to Data Controller.\n",
|
||||
"#\n",
|
||||
"run_command(f'kubectl config set-context --current --namespace {arc_data_controller_namespace}')"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "c974561f-13d0-4e7a-b74b-d781c2e06d68"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### **Login to the Data Controller.**\n"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9376b2ab-0edf-478f-9e3c-5ff46ae3501a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Login to the Data Controller.\n",
|
||||
"#\n",
|
||||
"run_command(f'azdata login --namespace {arc_data_controller_namespace}')"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9aed0c5a-2c8a-4ad7-becb-60281923a196"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
" \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) |"
|
||||
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
|
||||
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "20fe3985-a01e-461c-bce0-235f7606cc3c"
|
||||
@@ -64,6 +65,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"import sys,os,json,subprocess\n",
|
||||
"def run_command():\n",
|
||||
@@ -76,16 +78,14 @@
|
||||
" print(f'Successfully executed: {cmd}')\n",
|
||||
" print(f'\\t>>>Output: {output.stdout.decode(\"utf-8\")}\\n')\n",
|
||||
" return output.stdout.decode(\"utf-8\")\n",
|
||||
"cmd = 'azdata --version'\n",
|
||||
"out = run_command()\n",
|
||||
""
|
||||
"cmd = 'az --version'\n",
|
||||
"out = run_command()\n"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "749d8dba-3da8-46e9-ae48-2b38056ab7a2",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -111,48 +111,50 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Login to the data controller.\n",
|
||||
"#\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
|
||||
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
|
||||
"endpoint_option = f' -e {controller_endpoint}' if controller_endpoint else \"\"\n",
|
||||
"cmd = f'azdata login --namespace {arc_data_controller_namespace} -u {controller_username}{endpoint_option}'\n",
|
||||
"out=run_command()"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "71366399-5963-4e24-b2f2-6bb5bffba4ec"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"print (f'Creating the PostgreSQL Hyperscale - Azure Arc instance')\n",
|
||||
"\n",
|
||||
"workers_option = f' -w {postgres_server_group_workers}' if postgres_server_group_workers else \"\"\n",
|
||||
"port_option = f' --port \"{postgres_server_group_port}\"' if postgres_server_group_port else \"\"\n",
|
||||
"engine_version_option = f' -ev {postgres_server_group_engine_version}' if postgres_server_group_engine_version else \"\"\n",
|
||||
"extensions_option = f' --extensions \"{postgres_server_group_extensions}\"' if postgres_server_group_extensions else \"\"\n",
|
||||
"volume_size_data_option = f' -vsd {postgres_server_group_volume_size_data}Gi' if postgres_server_group_volume_size_data 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 \"c={postgres_server_group_coordinator_cores_request},w={postgres_server_group_workers_cores_request}\"' if postgres_server_group_coordinator_cores_request and postgres_server_group_workers_cores_request else f' -cr \"c={postgres_server_group_coordinator_cores_request}\"' if postgres_server_group_coordinator_cores_request else f' -cr \"w={postgres_server_group_workers_cores_request}\"' if postgres_server_group_workers_cores_request else \"\"\n",
|
||||
"cores_limit_option = f' -cl \"c={postgres_server_group_coordinator_cores_limit},w={postgres_server_group_workers_cores_limit}\"' if postgres_server_group_coordinator_cores_limit and postgres_server_group_workers_cores_limit else f' -cl \"c={postgres_server_group_coordinator_cores_limit}\"' if postgres_server_group_coordinator_cores_limit else f' -cl \"w={postgres_server_group_workers_cores_limit}\"' if postgres_server_group_workers_cores_limit else \"\"\n",
|
||||
"memory_request_option = f' -mr \"c={postgres_server_group_coordinator_memory_request}Gi,w={postgres_server_group_workers_memory_request}Gi\"' if postgres_server_group_coordinator_memory_request and postgres_server_group_workers_memory_request else f' -mr \"c={postgres_server_group_coordinator_memory_request}Gi\"' if postgres_server_group_coordinator_memory_request else f' -mr \"w={postgres_server_group_workers_memory_request}Gi\"' if postgres_server_group_workers_memory_request else \"\"\n",
|
||||
"memory_limit_option = f' -ml \"c={postgres_server_group_coordinator_memory_limit}Gi,w={postgres_server_group_workers_memory_limit}Gi\"' if postgres_server_group_coordinator_memory_limit and postgres_server_group_workers_memory_limit else f' -ml \"c={postgres_server_group_coordinator_memory_limit}Gi\"' if postgres_server_group_coordinator_memory_limit else f' -ml \"w={postgres_server_group_workers_memory_limit}Gi\"' if postgres_server_group_workers_memory_limit else \"\"\n",
|
||||
"workers = f' --workers {postgres_server_group_workers}' if postgres_server_group_workers else \"\"\n",
|
||||
"port = f' --port \"{postgres_server_group_port}\"' if postgres_server_group_port else \"\"\n",
|
||||
"engine_version = f' --engine-version {postgres_server_group_engine_version}' if postgres_server_group_engine_version else \"\"\n",
|
||||
"extensions = f' --extensions \"{postgres_server_group_extensions}\"' if postgres_server_group_extensions else \"\"\n",
|
||||
"volume_size_data = f' --volume-size-data {postgres_server_group_volume_size_data}Gi' if postgres_server_group_volume_size_data else \"\"\n",
|
||||
"volume_size_logs = f' --volume-size-logs {postgres_server_group_volume_size_logs}Gi' if postgres_server_group_volume_size_logs else \"\"\n",
|
||||
"volume_size_backups = f' --volume-size-backups {postgres_server_group_volume_size_backups}Gi' if postgres_server_group_volume_size_backups else \"\"\n",
|
||||
"\n",
|
||||
"def get_per_role_argument(argument, roles, unit=''):\n",
|
||||
" value = ','.join(f'{role}={value}{unit}' for role,value in roles.items() if value not in (None, ''))\n",
|
||||
" return f' {argument} {value}' if value else ''\n",
|
||||
"\n",
|
||||
"cores_request = get_per_role_argument('--cores-request', {\n",
|
||||
" 'c': postgres_server_group_coordinator_cores_request,\n",
|
||||
" 'w': postgres_server_group_workers_cores_request\n",
|
||||
"})\n",
|
||||
"\n",
|
||||
"cores_limit = get_per_role_argument('--cores-limit', {\n",
|
||||
" 'c': postgres_server_group_coordinator_cores_limit,\n",
|
||||
" 'w': postgres_server_group_workers_cores_limit\n",
|
||||
"})\n",
|
||||
"\n",
|
||||
"memory_request = get_per_role_argument('--memory-request', {\n",
|
||||
" 'c': postgres_server_group_coordinator_memory_request,\n",
|
||||
" 'w': postgres_server_group_workers_memory_request\n",
|
||||
"}, 'Gi')\n",
|
||||
"\n",
|
||||
"memory_limit = get_per_role_argument('--memory-limit', {\n",
|
||||
" 'c': postgres_server_group_coordinator_memory_limit,\n",
|
||||
" 'w': postgres_server_group_workers_memory_limit\n",
|
||||
"}, 'Gi')\n",
|
||||
"\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\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",
|
||||
"cmd = f'az postgres arc-server create --name {postgres_server_group_name} --k8s-namespace {arc_data_controller_namespace} --use-k8s --storage-class-data {postgres_storage_class_data} --storage-class-logs {postgres_storage_class_logs} --storage-class-backups {postgres_storage_class_backups}{workers}{port}{engine_version}{extensions}{volume_size_data}{volume_size_logs}{volume_size_backups}{cores_request}{cores_limit}{memory_request}{memory_limit}'\n",
|
||||
"out=run_command()"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4fbaf071-55a1-40bc-be7e-7b9b5547b886"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -47,7 +47,8 @@
|
||||
" \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) |"
|
||||
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
|
||||
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d1c8258e-9efd-4380-a48c-cd675423ed2f"
|
||||
@@ -64,6 +65,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"import sys,os,json,subprocess\n",
|
||||
"def run_command():\n",
|
||||
@@ -76,16 +78,14 @@
|
||||
" print(f'Successfully executed: {cmd}')\n",
|
||||
" print(f'\\t>>>Output: {output.stdout.decode(\"utf-8\")}\\n')\n",
|
||||
" return output.stdout.decode(\"utf-8\")\n",
|
||||
"cmd = 'azdata --version'\n",
|
||||
"out = run_command()\n",
|
||||
""
|
||||
"cmd = 'az --version'\n",
|
||||
"out = run_command()\n"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "749d8dba-3da8-46e9-ae48-2b38056ab7a2",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
@@ -111,41 +111,24 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Login to the data controller.\n",
|
||||
"#\n",
|
||||
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
|
||||
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
|
||||
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||
"out=run_command()"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1437c536-17e8-4a7f-80c1-aa43ad02686c"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"print (f'Creating the SQL managed instance - Azure Arc instance')\n",
|
||||
"\n",
|
||||
"cores_request_option = f' -cr \"{sql_cores_request}\"' if sql_cores_request else \"\"\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",
|
||||
"cores_request_option = f' --cores-request \"{sql_cores_request}\"' if sql_cores_request else \"\"\n",
|
||||
"cores_limit_option = f' --cores-limit \"{sql_cores_limit}\"' if sql_cores_limit else \"\"\n",
|
||||
"memory_request_option = f' --memory-request \"{sql_memory_request}Gi\"' if sql_memory_request else \"\"\n",
|
||||
"memory_limit_option = f' --memory-limit \"{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} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
|
||||
"cmd = f'az sql mi-arc create --name {sql_instance_name} --k8s-namespace {arc_data_controller_namespace} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option} --use-k8s'\n",
|
||||
"out=run_command()"
|
||||
],
|
||||
"outputs": [],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4fbaf071-55a1-40bc-be7e-7b9b5547b886"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"icon": "images/extension.png",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": ">=1.28.0"
|
||||
"azdata": ">=1.32.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onCommand:arc.connectToController",
|
||||
@@ -18,7 +18,7 @@
|
||||
"onView:azureArc"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"Microsoft.azdata",
|
||||
"Microsoft.azcli",
|
||||
"Microsoft.resource-deployment"
|
||||
],
|
||||
"repository": {
|
||||
@@ -190,7 +190,7 @@
|
||||
"editable": false,
|
||||
"options": {
|
||||
"source": {
|
||||
"providerId": "arc.controller.config.profiles",
|
||||
"providerId": "azcli.arc.controller.config.profiles",
|
||||
"loadingText": "%arc.data.controller.cluster.config.profile.loading%",
|
||||
"loadingCompletedText": "%arc.data.controller.cluster.config.profile.loadingcompleted%"
|
||||
},
|
||||
@@ -289,6 +289,21 @@
|
||||
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_STORAGE_CLASS",
|
||||
"type": "kube_storage_class",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "options",
|
||||
"label": "%arc.data.controller.infrastructure%",
|
||||
"defaultValue": "azure",
|
||||
"required": true,
|
||||
"variableName": "AZDATA_NB_VAR_ARC_INFRASTRUCTURE",
|
||||
"options": [
|
||||
"azure",
|
||||
"gcp",
|
||||
"aws",
|
||||
"alibaba",
|
||||
"onpremises",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -507,6 +522,12 @@
|
||||
"type": "readonly_text",
|
||||
"isEvaluated": true,
|
||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
|
||||
},
|
||||
{
|
||||
"label": "%arc.data.controller.summary.data.controller.infrastructure%",
|
||||
"type": "readonly_text",
|
||||
"isEvaluated": true,
|
||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_INFRASTRUCTURE)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -519,8 +540,7 @@
|
||||
"name": "kubectl"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.4"
|
||||
"name": "azure-cli"
|
||||
}
|
||||
],
|
||||
"when": true
|
||||
@@ -573,11 +593,8 @@
|
||||
"providerId": "arc.controllers",
|
||||
"variableNames": {
|
||||
"namespace": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE",
|
||||
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||
"kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG",
|
||||
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT",
|
||||
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT"
|
||||
}
|
||||
},
|
||||
"optionsType": "dropdown"
|
||||
@@ -838,8 +855,7 @@
|
||||
"name": "kubectl"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.4"
|
||||
"name": "azure-cli"
|
||||
}
|
||||
],
|
||||
"when": "true"
|
||||
@@ -915,11 +931,9 @@
|
||||
"source": {
|
||||
"providerId": "arc.controllers",
|
||||
"variableNames": {
|
||||
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||
"namespace": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE",
|
||||
"kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG",
|
||||
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT",
|
||||
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT"
|
||||
}
|
||||
},
|
||||
"optionsType": "dropdown"
|
||||
@@ -1067,8 +1081,7 @@
|
||||
"name": "kubectl"
|
||||
},
|
||||
{
|
||||
"name": "azdata",
|
||||
"version": "20.3.4"
|
||||
"name": "azure-cli"
|
||||
}
|
||||
],
|
||||
"when": "mi-type=arc-mi"
|
||||
@@ -1092,6 +1105,9 @@
|
||||
"links": [
|
||||
{
|
||||
"text": "%arc.agreement.sql.help.text.learn.more%",
|
||||
"accessibilityInformation": {
|
||||
"label": "%arc.agreement.sql.help.text.learn.more.ariaLabel%"
|
||||
},
|
||||
"url": "https://go.microsoft.com/fwlink/?linkid=2141849"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"arc.data.controller.name": "Data controller name",
|
||||
"arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
|
||||
"arc.data.controller.location": "Location",
|
||||
"arc.data.controller.infrastructure": "Infrastructure",
|
||||
"arc.data.controller.admin.account.title": "Administrator account",
|
||||
"arc.data.controller.admin.account.name": "Data controller login",
|
||||
"arc.data.controller.admin.account.password": "Password",
|
||||
@@ -58,6 +59,7 @@
|
||||
"arc.data.controller.summary.resource.group": "Resource group",
|
||||
"arc.data.controller.summary.data.controller.name": "Data controller name",
|
||||
"arc.data.controller.summary.data.controller.namespace": "Data controller namespace",
|
||||
"arc.data.controller.summary.data.controller.infrastructure": "Data controller infrastructure",
|
||||
"arc.data.controller.summary.controller": "Controller",
|
||||
"arc.data.controller.summary.location": "Location",
|
||||
"arc.data.controller.agreement": "I accept {0} and {1}.",
|
||||
@@ -153,6 +155,7 @@
|
||||
"cores.limit.greater.than.or.equal.to.requested.cores": "Cores limit must be greater than or equal to requested cores",
|
||||
"requested.memory.less.than.or.equal.to.memory.limit": "Requested memory must be less than or equal to memory limit",
|
||||
"memory.limit.greater.than.or.equal.to.requested.memory": "Memory limit must be greater than or equal to requested memory",
|
||||
"arc.agreement.sql.help.text": "Azure Arc enabled Managed Instance provides SQL Server access and feature compatibility that can be deployed on the infrastructure of your choice. While this service is in preview, it has some feature limitations compared to SQL Managed Instance on Azure. {0}",
|
||||
"arc.agreement.sql.help.text.learn.more": "Learn more"
|
||||
"arc.agreement.sql.help.text": "Azure Arc enabled Managed Instance provides SQL Server access and feature compatibility that can be deployed on the infrastructure of your choice. {0}",
|
||||
"arc.agreement.sql.help.text.learn.more": "Learn more",
|
||||
"arc.agreement.sql.help.text.learn.more.ariaLabel": "Learn more about Azure Arc enabled Managed Instance"
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
import * as arc from 'arc';
|
||||
import * as rd from 'resource-deployment';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog';
|
||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||
import { ControllerTreeNode } from '../ui/tree/controllerTreeNode';
|
||||
|
||||
@@ -17,26 +15,10 @@ export class UserCancelledError extends Error implements rd.ErrorWithType {
|
||||
}
|
||||
export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension {
|
||||
return {
|
||||
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider),
|
||||
getControllerPassword: (controllerInfo: arc.ControllerInfo) => getControllerPassword(treeDataProvider, controllerInfo),
|
||||
reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo)
|
||||
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider)
|
||||
};
|
||||
}
|
||||
|
||||
export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
|
||||
const dialog = new PasswordToControllerDialog(treeDataProvider);
|
||||
dialog.showDialog(controllerInfo);
|
||||
const model = await dialog.waitForClose();
|
||||
if (!model) {
|
||||
throw new UserCancelledError(loc.userCancelledError);
|
||||
}
|
||||
return model.password;
|
||||
}
|
||||
|
||||
export async function getControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
|
||||
return await treeDataProvider.getPassword(controllerInfo);
|
||||
}
|
||||
|
||||
export async function getRegisteredDataControllers(treeDataProvider: AzureArcTreeDataProvider): Promise<arc.DataController[]> {
|
||||
return (await treeDataProvider.getChildren())
|
||||
.filter(node => node instanceof ControllerTreeNode)
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
||||
dialog.showDialog();
|
||||
const model = await dialog.waitForClose();
|
||||
if (model) {
|
||||
await treeDataProvider.addOrUpdateController(model.controllerModel, model.password);
|
||||
await treeDataProvider.addOrUpdateController(model.controllerModel);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -50,10 +50,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
||||
|
||||
vscode.commands.registerCommand('arc.editConnection', async (treeNode: ControllerTreeNode) => {
|
||||
const dialog = new ConnectToControllerDialog(treeDataProvider);
|
||||
dialog.showDialog(treeNode.model.info, await treeDataProvider.getPassword(treeNode.model.info));
|
||||
dialog.showDialog(treeNode.model.info);
|
||||
const model = await dialog.waitForClose();
|
||||
if (model) {
|
||||
await treeDataProvider.addOrUpdateController(model.controllerModel, model.password, true);
|
||||
await treeDataProvider.addOrUpdateController(model.controllerModel, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -42,8 +42,9 @@ export const dropText = localize('arc.drop', "Drop");
|
||||
export const saveText = localize('arc.save', "Save");
|
||||
export const discardText = localize('arc.discard', "Discard");
|
||||
export const resetPassword = localize('arc.resetPassword', "Reset Password");
|
||||
export const addExtensions = localize('arc.addExtensions', "Add extensions");
|
||||
export const dropExtension = localize('arc.dropExtension', "Drop extension");
|
||||
export const loadExtensions = localize('arc.loadExtensions', "Load extensions");
|
||||
export const unloadExtensions = localize('arc.unloadExtensions', "Unload extensions");
|
||||
export const noExtensions = localize('arc.noExtensions', "No extensions listed in configuration.");
|
||||
export const openInAzurePortal = localize('arc.openInAzurePortal', "Open in Azure Portal");
|
||||
export const resourceGroup = localize('arc.resourceGroup', "Resource Group");
|
||||
export const region = localize('arc.region', "Region");
|
||||
@@ -60,6 +61,8 @@ export const controllerEndpoint = localize('arc.controllerEndpoint', "Controller
|
||||
export const extensionName = localize('arc.extensionName', "Extension name");
|
||||
export const extensionsDescription = localize('arc.extensionsDescription', "PostgreSQL provides the ability to extend the functionality of your database by using extensions. Extensions allow for bundling multiple related SQL objects together in a single package that can be loaded or removed from your database with a single command. After being loaded in the database, extensions can function like built-in features.");
|
||||
export const extensionsFunction = localize('arc.extensionsFunction', "Some extensions must be loaded into PostgreSQL at startup time before they can be used. These preloaded extensions can be viewed and edited below.");
|
||||
export function extensionsAddFunction(extensions: string): string { return localize('arc.extensionsAddFunction', "Some extensions must be loaded into PostgreSQL at startup time before they can be used. To edit, type in comma separated list of valid extensions: ({0}).", extensions); }
|
||||
export function extensionsAddErrorrMessage(extensions: string): string { return localize('arc.extensionsAddErrorrMessage', "Value should be either of the following: ({0}).", extensions); }
|
||||
export const extensionsLearnMore = localize('arc.extensionsLearnMore', "Learn more about PostgreSQL extensions.");
|
||||
export const extensionsTableLoading = localize('arc.extensionsTableLoading', "Table of preloaded extensions are loading.");
|
||||
export const extensionsTableLabel = localize('arc.extensionsTableLabel', "Table of preloaded extensions.");
|
||||
@@ -72,6 +75,7 @@ export const grafanaDashboard = localize('arc.grafanaDashboard', "Grafana Dashbo
|
||||
export const kibanaDashboardDescription = localize('arc.kibanaDashboardDescription', "Dashboard for viewing logs");
|
||||
export const grafanaDashboardDescription = localize('arc.grafanaDashboardDescription', "Dashboard for viewing metrics");
|
||||
export const serviceEndpoints = localize('arc.serviceEndpoints', "Service endpoints");
|
||||
export const serviceEndpointsTable = localize('arc.serviceEndpointsTable', "Service endpoints table");
|
||||
export const databases = localize('arc.databases', "Databases");
|
||||
export const endpoint = localize('arc.endpoint', "Endpoint");
|
||||
export const description = localize('arc.description', "Description");
|
||||
@@ -206,6 +210,8 @@ export const noWorkerPods = localize('arc.noWorkerPods', "No worker pods in this
|
||||
export const podsReady = localize('arc.podsReady', "pods ready");
|
||||
export const podsPresent = localize('arc.podsPresent', "Pods Present");
|
||||
export const podsUsedDescription = localize('arc.podsUsedDescription', "Select a pod in the dropdown below for detailed health information.");
|
||||
export const podsUsedDescriptionAria = localize('arc.podsUsedDescriptionAria', "Select a pod in the dropdown below for detailed health information");
|
||||
export const podConditionsTable = localize('arc.podConditionsTable', "Pod conditions table");
|
||||
export const connectToPostgresDescription = localize('arc.connectToPostgresDescription', "A connection to the server is required to show and set database engine settings, which will require the PostgreSQL Extension to be installed.");
|
||||
export const postgresExtension = localize('arc.postgresExtension', "microsoft.azuredatastudio-postgresql");
|
||||
export const podInitialized = localize('arc.podInitialized', "Pod is initialized.");
|
||||
@@ -223,7 +229,7 @@ export function extensionInstalled(name: string): string { return localize('arc.
|
||||
export function updatingInstance(name: string): string { return localize('arc.updatingInstance', "Updating instance '{0}'...", name); }
|
||||
export function instanceDeleted(name: string): string { return localize('arc.instanceDeleted', "Instance '{0}' deleted", name); }
|
||||
export function instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
|
||||
export function extensionDropped(name: string): string { return localize('arc.extensionDropped', "Extension '{0}' deleted", name); }
|
||||
export function extensionsDropped(name: string): string { return localize('arc.extensionsDropped', "Extensions '{0}' dropped", name); }
|
||||
export function extensionsAdded(name: string): string { return localize('arc.extensionsAdded', "Extensions '{0}' added", 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); }
|
||||
@@ -242,6 +248,7 @@ export function numVCores(vCores: string | undefined): string {
|
||||
}
|
||||
}
|
||||
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
|
||||
export function connectionString(type: string): string { return localize({ key: 'arc.connectionString', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Connection string for {0}", type); }
|
||||
export function copyConnectionStringToClipboard(type: string): string { return localize({ key: 'arc.copyConnectionStringToClipboard', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Copy {0} Connection String to clipboard", type); }
|
||||
export function copyValueToClipboard(valueName: string): string { return localize({ key: 'arc.copyValueToClipboard', comment: ['{0} is the name of the type of value being copied (e.g. Coordinator endpoint)'] }, "Copy {0} to clipboard", valueName); }
|
||||
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ControllerInfo, ResourceType } from 'arc';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as vscode from 'vscode';
|
||||
import { UserCancelledError } from '../common/api';
|
||||
import { getCurrentClusterContext, getKubeConfigClusterContexts } from '../common/kubeUtils';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
|
||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||
|
||||
export type Registration = {
|
||||
@@ -19,13 +16,13 @@ export type Registration = {
|
||||
};
|
||||
|
||||
export class ControllerModel {
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private _endpoints: azdataExt.DcEndpointListResult[] = [];
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
private _endpoints: azExt.DcEndpointListResult[] = [];
|
||||
private _registrations: Registration[] = [];
|
||||
private _controllerConfig: azdataExt.DcConfigShowResult | undefined = undefined;
|
||||
private _controllerConfig: azExt.DcConfigShowResult | undefined = undefined;
|
||||
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.DcConfigShowResult | undefined>();
|
||||
private readonly _onEndpointsUpdated = new vscode.EventEmitter<azdataExt.DcEndpointListResult[]>();
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.DcConfigShowResult | undefined>();
|
||||
private readonly _onEndpointsUpdated = new vscode.EventEmitter<azExt.DcEndpointListResult[]>();
|
||||
private readonly _onRegistrationsUpdated = new vscode.EventEmitter<Registration[]>();
|
||||
private readonly _onInfoUpdated = new vscode.EventEmitter<ControllerInfo>();
|
||||
|
||||
@@ -38,85 +35,26 @@ export class ControllerModel {
|
||||
public endpointsLastUpdated?: Date;
|
||||
public registrationsLastUpdated?: Date;
|
||||
|
||||
constructor(public treeDataProvider: AzureArcTreeDataProvider, private _info: ControllerInfo, private _password?: string) {
|
||||
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
constructor(public treeDataProvider: AzureArcTreeDataProvider, private _info: ControllerInfo) {
|
||||
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
}
|
||||
|
||||
public get info(): ControllerInfo {
|
||||
return this._info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller context to use when executing azdata commands. This is in one of two forms :
|
||||
*
|
||||
* If no URL is specified for this controller then just the namespace is used (e.g. test-namespace)
|
||||
* If a URL is specified then a 3-part name is used, combining the namespace, username and URL separated by
|
||||
* / (e.g. test-namespace/admin/https://10.91.86.13:30080)
|
||||
*/
|
||||
public get controllerContext(): string {
|
||||
if (this._info.endpoint) {
|
||||
return `${this._info.namespace}/${this._info.username}/${this._info.endpoint}`;
|
||||
}
|
||||
return this._info.namespace;
|
||||
}
|
||||
|
||||
public set info(value: ControllerInfo) {
|
||||
this._info = value;
|
||||
this._onInfoUpdated.fire(this._info);
|
||||
}
|
||||
|
||||
public get azdataAdditionalEnvVars(): azdataExt.AdditionalEnvVars {
|
||||
public get azAdditionalEnvVars(): azExt.AdditionalEnvVars {
|
||||
return {
|
||||
'KUBECONFIG': this.info.kubeConfigFilePath,
|
||||
'KUBECTL_CONTEXT': this.info.kubeClusterContext
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls azdata login to set the context to this controller and acquires a login session to prevent other
|
||||
* calls from changing the context while commands for this session are being executed.
|
||||
* @param promptReconnect
|
||||
*/
|
||||
public async login(promptReconnect: boolean = false): Promise<void> {
|
||||
let promptForValidClusterContext: boolean = false;
|
||||
try {
|
||||
const contexts = getKubeConfigClusterContexts(this.info.kubeConfigFilePath);
|
||||
getCurrentClusterContext(contexts, this.info.kubeClusterContext, true); // this throws if this.info.kubeClusterContext is not found in 'contexts'
|
||||
} catch (error) {
|
||||
const response = await vscode.window.showErrorMessage(loc.clusterContextConfigNoLongerValid(this.info.kubeConfigFilePath, this.info.kubeClusterContext, error), loc.yes, loc.no);
|
||||
if (response === loc.yes) {
|
||||
promptForValidClusterContext = true;
|
||||
} else {
|
||||
if (!promptReconnect) { //throw unless we are required to prompt for reconnect anyways
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We haven't gotten our password yet or we want to prompt for a reconnect or we want to prompt to reacquire valid cluster context or any and all of these.
|
||||
if (!this._password || promptReconnect || promptForValidClusterContext) {
|
||||
this._password = '';
|
||||
if (this.info.rememberPassword) {
|
||||
// It should be in the credentials store, get it from there
|
||||
this._password = await this.treeDataProvider.getPassword(this.info);
|
||||
}
|
||||
if (promptReconnect || !this._password || promptForValidClusterContext) {
|
||||
// No password yet or we want to re-prompt for credentials so prompt for it from the user
|
||||
const dialog = new ConnectToControllerDialog(this.treeDataProvider);
|
||||
dialog.showDialog(this.info, this._password);
|
||||
const model = await dialog.waitForClose();
|
||||
if (model) {
|
||||
await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false);
|
||||
this._password = model.password;
|
||||
this._info = model.controllerModel.info;
|
||||
} else {
|
||||
throw new UserCancelledError(loc.userCancelledError);
|
||||
}
|
||||
}
|
||||
}
|
||||
await this._azdataApi.azdata.login({ endpoint: this.info.endpoint, namespace: this.info.namespace }, this.info.username, this._password, this.azdataAdditionalEnvVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the Tree Node for this model. This will also result in the model being refreshed.
|
||||
*/
|
||||
@@ -125,16 +63,14 @@ export class ControllerModel {
|
||||
if (node) {
|
||||
this.treeDataProvider.refreshNode(node);
|
||||
} else {
|
||||
await this.refresh(false);
|
||||
await this.refresh(false, this.info.namespace);
|
||||
}
|
||||
}
|
||||
public async refresh(showErrors: boolean = true): Promise<void> {
|
||||
// First need to log in to ensure that we're able to authenticate with the controller
|
||||
await this.login(false);
|
||||
public async refresh(showErrors: boolean = true, namespace: string): Promise<void> {
|
||||
const newRegistrations: Registration[] = [];
|
||||
await Promise.all([
|
||||
this._azdataApi.azdata.arc.dc.config.show(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||
this._controllerConfig = result.result;
|
||||
this._azApi.az.arcdata.dc.config.show(namespace, this.azAdditionalEnvVars).then(result => {
|
||||
this._controllerConfig = result.stdout;
|
||||
this.configLastUpdated = new Date();
|
||||
this._onConfigUpdated.fire(this._controllerConfig);
|
||||
}).catch(err => {
|
||||
@@ -147,8 +83,8 @@ export class ControllerModel {
|
||||
this._onConfigUpdated.fire(this._controllerConfig);
|
||||
throw err;
|
||||
}),
|
||||
this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||
this._endpoints = result.result;
|
||||
this._azApi.az.arcdata.dc.endpoint.list(namespace, this.azAdditionalEnvVars).then(result => {
|
||||
this._endpoints = result.stdout;
|
||||
this.endpointsLastUpdated = new Date();
|
||||
this._onEndpointsUpdated.fire(this._endpoints);
|
||||
}).catch(err => {
|
||||
@@ -162,8 +98,8 @@ export class ControllerModel {
|
||||
throw err;
|
||||
}),
|
||||
Promise.all([
|
||||
this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||
newRegistrations.push(...result.result.map(r => {
|
||||
this._azApi.az.postgres.arcserver.list(namespace, this.azAdditionalEnvVars).then(result => {
|
||||
newRegistrations.push(...result.stdout.map(r => {
|
||||
return {
|
||||
instanceName: r.name,
|
||||
state: r.state,
|
||||
@@ -171,14 +107,15 @@ export class ControllerModel {
|
||||
};
|
||||
}));
|
||||
}),
|
||||
this._azdataApi.azdata.arc.sql.mi.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||
newRegistrations.push(...result.result.map(r => {
|
||||
this._azApi.az.sql.miarc.list(namespace, this.azAdditionalEnvVars).then(result => {
|
||||
newRegistrations.push(...result.stdout.map(r => {
|
||||
return {
|
||||
instanceName: r.name,
|
||||
state: r.state,
|
||||
instanceType: ResourceType.sqlManagedInstances
|
||||
};
|
||||
}));
|
||||
|
||||
})
|
||||
]).then(() => {
|
||||
this._registrations = newRegistrations;
|
||||
@@ -188,11 +125,11 @@ export class ControllerModel {
|
||||
]);
|
||||
}
|
||||
|
||||
public get endpoints(): azdataExt.DcEndpointListResult[] {
|
||||
public get endpoints(): azExt.DcEndpointListResult[] {
|
||||
return this._endpoints;
|
||||
}
|
||||
|
||||
public getEndpoint(name: string): azdataExt.DcEndpointListResult | undefined {
|
||||
public getEndpoint(name: string): azExt.DcEndpointListResult | undefined {
|
||||
return this._endpoints.find(e => e.name === name);
|
||||
}
|
||||
|
||||
@@ -200,7 +137,7 @@ export class ControllerModel {
|
||||
return this._registrations;
|
||||
}
|
||||
|
||||
public get controllerConfig(): azdataExt.DcConfigShowResult | undefined {
|
||||
public get controllerConfig(): azExt.DcConfigShowResult | undefined {
|
||||
return this._controllerConfig;
|
||||
}
|
||||
|
||||
@@ -214,6 +151,6 @@ export class ControllerModel {
|
||||
* property to for use a display label for this controller
|
||||
*/
|
||||
public get label(): string {
|
||||
return `${this.info.name} (${this.controllerContext})`;
|
||||
return `${this.info.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { MiaaResourceInfo } from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as vscode from 'vscode';
|
||||
import { UserCancelledError } from '../common/api';
|
||||
import { Deferred } from '../common/promise';
|
||||
@@ -20,12 +20,12 @@ export type DatabaseModel = { name: string, status: string };
|
||||
|
||||
export class MiaaModel extends ResourceModel {
|
||||
|
||||
private _config: azdataExt.SqlMiShowResult | undefined;
|
||||
private _config: azExt.SqlMiShowResult | undefined;
|
||||
private _databases: DatabaseModel[] = [];
|
||||
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.SqlMiShowResult | undefined>();
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.SqlMiShowResult | undefined>();
|
||||
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
public onConfigUpdated = this._onConfigUpdated.event;
|
||||
public onDatabasesUpdated = this._onDatabasesUpdated.event;
|
||||
public configLastUpdated: Date | undefined;
|
||||
@@ -35,7 +35,7 @@ export class MiaaModel extends ResourceModel {
|
||||
|
||||
constructor(_controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
|
||||
super(_controllerModel, _miaaInfo, registration);
|
||||
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ export class MiaaModel extends ResourceModel {
|
||||
/**
|
||||
* The status of this instance
|
||||
*/
|
||||
public get config(): azdataExt.SqlMiShowResult | undefined {
|
||||
public get config(): azExt.SqlMiShowResult | undefined {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ export class MiaaModel extends ResourceModel {
|
||||
this._refreshPromise = new Deferred();
|
||||
try {
|
||||
try {
|
||||
const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext);
|
||||
this._config = result.result;
|
||||
const result = await this._azApi.az.sql.miarc.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars);
|
||||
this._config = result.stdout;
|
||||
this.configLastUpdated = new Date();
|
||||
this._onConfigUpdated.fire(this._config);
|
||||
} catch (err) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { PGResourceInfo } from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as vscode from 'vscode';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { ConnectToPGSqlDialog } from '../ui/dialogs/connectPGDialog';
|
||||
@@ -27,12 +27,12 @@ export type EngineSettingsModel = {
|
||||
};
|
||||
|
||||
export class PostgresModel extends ResourceModel {
|
||||
private _config?: azdataExt.PostgresServerShowResult;
|
||||
private _config?: azExt.PostgresServerShowResult;
|
||||
public workerNodesEngineSettings: EngineSettingsModel[] = [];
|
||||
public coordinatorNodeEngineSettings: EngineSettingsModel[] = [];
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
|
||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.PostgresServerShowResult>();
|
||||
public onConfigUpdated = this._onConfigUpdated.event;
|
||||
public configLastUpdated?: Date;
|
||||
public engineSettingsLastUpdated?: Date;
|
||||
@@ -42,11 +42,11 @@ export class PostgresModel extends ResourceModel {
|
||||
|
||||
constructor(_controllerModel: ControllerModel, private _pgInfo: PGResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
|
||||
super(_controllerModel, _pgInfo, registration);
|
||||
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
}
|
||||
|
||||
/** Returns the configuration of Postgres */
|
||||
public get config(): azdataExt.PostgresServerShowResult | undefined {
|
||||
public get config(): azExt.PostgresServerShowResult | undefined {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export class PostgresModel extends ResourceModel {
|
||||
}
|
||||
this._refreshPromise = new Deferred();
|
||||
try {
|
||||
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext)).result;
|
||||
this._config = (await this._azApi.az.postgres.arcserver.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars)).stdout;
|
||||
this.configLastUpdated = new Date();
|
||||
this._onConfigUpdated.fire(this._config);
|
||||
this._refreshPromise.resolve();
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arc from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as rd from 'resource-deployment';
|
||||
import { getControllerPassword, getRegisteredDataControllers, reacquireControllerPassword } from '../common/api';
|
||||
import { getRegisteredDataControllers } from '../common/api';
|
||||
import { throwUnless } from '../common/utils';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||
@@ -31,32 +30,17 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
|
||||
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
||||
switch (variableName) {
|
||||
case 'namespace': return controller.info.namespace || '';
|
||||
case 'endpoint': return controller.info.endpoint || '';
|
||||
case 'username': return controller.info.username;
|
||||
case 'kubeConfig': return controller.info.kubeConfigFilePath;
|
||||
case 'clusterContext': return controller.info.kubeClusterContext;
|
||||
case 'password': return this.getPassword(controller);
|
||||
default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
}
|
||||
|
||||
private async getPassword(controller: arc.DataController): Promise<string> {
|
||||
let password = await getControllerPassword(this._treeProvider, controller.info);
|
||||
if (!password) {
|
||||
password = await reacquireControllerPassword(this._treeProvider, controller.info);
|
||||
}
|
||||
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
|
||||
return password;
|
||||
}
|
||||
|
||||
public getIsPassword(variableName: string): boolean {
|
||||
switch (variableName) {
|
||||
case 'namespace': return false;
|
||||
case 'endpoint': return false;
|
||||
case 'username': return false;
|
||||
case 'kubeConfig': return false;
|
||||
case 'clusterContext': return false;
|
||||
case 'password': return true;
|
||||
default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +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 azdataExt from 'azdata-ext';
|
||||
|
||||
/**
|
||||
* Simple fake Azdata Api used to mock the API during tests
|
||||
*/
|
||||
export class FakeAzdataApi implements azdataExt.IAzdataApi {
|
||||
|
||||
private _arcApi = {
|
||||
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: {
|
||||
postgresInstances: <azdataExt.PostgresServerListResult[]>[],
|
||||
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
||||
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return { result: this.postgresInstances, logs: [], stdout: [], stderr: [] }; },
|
||||
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
|
||||
edit(
|
||||
_name: string,
|
||||
_args: {
|
||||
adminPassword?: boolean,
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
coordinatorEngineSettings?: string,
|
||||
engineSettings?: string,
|
||||
extensions?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
port?: number,
|
||||
replaceEngineSettings?: boolean,
|
||||
workerEngineSettings?: string,
|
||||
workers?: number
|
||||
},
|
||||
_additionalEnvVars?: azdataExt.AdditionalEnvVars
|
||||
): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
mi: {
|
||||
miaaInstances: <azdataExt.SqlMiListResult[]>[],
|
||||
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
||||
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return { logs: [], stdout: [], stderr: [], result: this.miaaInstances }; },
|
||||
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); },
|
||||
edit(
|
||||
_name: string,
|
||||
_args: {
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean
|
||||
}): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public set postgresInstances(instances: azdataExt.PostgresServerListResult[]) {
|
||||
this._arcApi.postgres.server.postgresInstances = instances;
|
||||
}
|
||||
|
||||
public set miaaInstances(instances: azdataExt.SqlMiListResult[]) {
|
||||
this._arcApi.sql.mi.miaaInstances = instances;
|
||||
}
|
||||
|
||||
//
|
||||
// API Implementation
|
||||
//
|
||||
public get arc() {
|
||||
return this._arcApi;
|
||||
}
|
||||
getPath(): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
login(_endpointOrNamespace: azdataExt.EndpointOrNamespace, _username: string, _password: string, _additionalEnvVars: azdataExt.AdditionalEnvVars = {}, _azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
|
||||
return <any>undefined;
|
||||
}
|
||||
version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSemVersion(): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider
|
||||
|
||||
export class FakeControllerModel extends ControllerModel {
|
||||
|
||||
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
|
||||
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>) {
|
||||
const _info: ControllerInfo = Object.assign({ id: uuid(), endpoint: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', namespace: '', username: '', rememberPassword: false, resources: [] }, info);
|
||||
super(treeDataProvider!, _info, password);
|
||||
super(treeDataProvider!, _info);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,221 +1,221 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// * Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
// *--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ControllerInfo } from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as vscode from 'vscode';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import * as kubeUtils from '../../common/kubeUtils';
|
||||
import { UserCancelledError } from '../../common/api';
|
||||
import { ControllerModel } from '../../models/controllerModel';
|
||||
import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
|
||||
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
|
||||
// import { ControllerInfo } from 'arc';
|
||||
// import * as azdata from 'azdata';
|
||||
// import * as azExt from 'azdata-ext';
|
||||
// import * as should from 'should';
|
||||
// import * as sinon from 'sinon';
|
||||
// import * as TypeMoq from 'typemoq';
|
||||
// import { v4 as uuid } from 'uuid';
|
||||
// import * as vscode from 'vscode';
|
||||
// import * as loc from '../../localizedConstants';
|
||||
// import * as kubeUtils from '../../common/kubeUtils';
|
||||
// import { UserCancelledError } from '../../common/api';
|
||||
// import { ControllerModel } from '../../models/controllerModel';
|
||||
// import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
|
||||
// import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
|
||||
|
||||
interface ExtensionGlobalMemento extends vscode.Memento {
|
||||
setKeysForSync(keys: string[]): void;
|
||||
}
|
||||
// interface ExtensionGlobalMemento extends vscode.Memento {
|
||||
// setKeysForSync(keys: string[]): void;
|
||||
// }
|
||||
|
||||
function getDefaultControllerInfo(): ControllerInfo {
|
||||
return {
|
||||
id: uuid(),
|
||||
endpoint: '127.0.0.1',
|
||||
kubeConfigFilePath: '/path/to/.kube/config',
|
||||
kubeClusterContext: 'currentCluster',
|
||||
username: 'admin',
|
||||
name: 'arc',
|
||||
namespace: 'arc-ns',
|
||||
rememberPassword: true,
|
||||
resources: []
|
||||
};
|
||||
}
|
||||
// function getDefaultControllerInfo(): ControllerInfo {
|
||||
// return {
|
||||
// id: uuid(),
|
||||
// endpoint: '127.0.0.1',
|
||||
// kubeConfigFilePath: '/path/to/.kube/config',
|
||||
// kubeClusterContext: 'currentCluster',
|
||||
// username: 'admin',
|
||||
// name: 'arc',
|
||||
// namespace: 'arc-ns',
|
||||
// rememberPassword: true,
|
||||
// resources: []
|
||||
// };
|
||||
// }
|
||||
|
||||
describe('ControllerModel', function (): void {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
// describe('ControllerModel', function (): void {
|
||||
// afterEach(function (): void {
|
||||
// sinon.restore();
|
||||
// });
|
||||
|
||||
describe('azdataLogin', function (): void {
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockGlobalState: TypeMoq.IMock<ExtensionGlobalMemento>;
|
||||
// describe('azdataLogin', function (): void {
|
||||
// let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
// let mockGlobalState: TypeMoq.IMock<ExtensionGlobalMemento>;
|
||||
|
||||
before(function (): void {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
||||
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
||||
});
|
||||
// before(function (): void {
|
||||
// mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
// mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
||||
// mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
||||
// });
|
||||
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
|
||||
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
|
||||
});
|
||||
// beforeEach(function (): void {
|
||||
// sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
|
||||
// sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||
// sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
|
||||
// });
|
||||
|
||||
it('Rejected with expected error when user cancels', async function (): Promise<void> {
|
||||
// Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
|
||||
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
|
||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
|
||||
});
|
||||
// it('Rejected with expected error when user cancels', async function (): Promise<void> {
|
||||
// // Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
|
||||
// sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
|
||||
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
// await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
|
||||
// });
|
||||
|
||||
it('Reads password from cred store', async function (): Promise<void> {
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Test password, not actually used")]
|
||||
// it('Reads password from cred store', async function (): Promise<void> {
|
||||
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Test password, not actually used")]
|
||||
|
||||
// Set up cred store to return our password
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: password }));
|
||||
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
// // Set up cred store to return our password
|
||||
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: password }));
|
||||
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
|
||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
|
||||
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
|
||||
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
|
||||
await model.login();
|
||||
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
// await model.login();
|
||||
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
// });
|
||||
|
||||
it('Prompt for password when not in cred store', async function (): Promise<void> {
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// it('Prompt for password when not in cred store', async function (): Promise<void> {
|
||||
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
|
||||
// Set up cred store to return empty password
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: '' }));
|
||||
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
// // Set up cred store to return empty password
|
||||
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: '' }));
|
||||
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
|
||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
|
||||
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
|
||||
|
||||
// Set up dialog to return new model with our password
|
||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
// // Set up dialog to return new model with our password
|
||||
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
// sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
|
||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
|
||||
await model.login();
|
||||
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
// await model.login();
|
||||
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
// });
|
||||
|
||||
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// Set up cred store to return a password to start with
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
// it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
|
||||
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// // Set up cred store to return a password to start with
|
||||
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
|
||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
|
||||
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
|
||||
|
||||
// Set up dialog to return new model with our new password from the reprompt
|
||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
// // Set up dialog to return new model with our new password from the reprompt
|
||||
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
|
||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||
|
||||
await model.login(true);
|
||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
// await model.login(true);
|
||||
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
// });
|
||||
|
||||
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// Set up cred store to return a password to start with
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
// it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
|
||||
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// // Set up cred store to return a password to start with
|
||||
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
|
||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
|
||||
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
|
||||
|
||||
// Set up dialog to return new model with our new password from the reprompt
|
||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
// // Set up dialog to return new model with our new password from the reprompt
|
||||
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||
|
||||
// Set up original model with a password
|
||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
|
||||
// // Set up original model with a password
|
||||
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
|
||||
|
||||
await model.login(true);
|
||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
// await model.login(true);
|
||||
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
// });
|
||||
|
||||
it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
|
||||
const treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
|
||||
// it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
|
||||
// const treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
|
||||
|
||||
// Set up cred store to return a password to start with
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
// // Set up cred store to return a password to start with
|
||||
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
|
||||
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||
|
||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
|
||||
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
||||
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
|
||||
|
||||
// Add existing model to provider
|
||||
const originalPassword = 'originalPassword';
|
||||
const model = new ControllerModel(
|
||||
treeDataProvider,
|
||||
getDefaultControllerInfo(),
|
||||
originalPassword
|
||||
);
|
||||
await treeDataProvider.addOrUpdateController(model, originalPassword);
|
||||
// // Add existing model to provider
|
||||
// const originalPassword = 'originalPassword';
|
||||
// const model = new ControllerModel(
|
||||
// treeDataProvider,
|
||||
// getDefaultControllerInfo(),
|
||||
// originalPassword
|
||||
// );
|
||||
// await treeDataProvider.addOrUpdateController(model, originalPassword);
|
||||
|
||||
const newInfo: ControllerInfo = {
|
||||
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
|
||||
endpoint: 'newUrl',
|
||||
kubeConfigFilePath: '/path/to/.kube/config',
|
||||
kubeClusterContext: 'currentCluster',
|
||||
username: 'newUser',
|
||||
name: 'newName',
|
||||
namespace: 'newNamespace',
|
||||
rememberPassword: true,
|
||||
resources: []
|
||||
};
|
||||
const newPassword = 'newPassword';
|
||||
// Set up dialog to return new model with our new password from the reprompt
|
||||
const newModel = new ControllerModel(
|
||||
treeDataProvider,
|
||||
newInfo,
|
||||
newPassword);
|
||||
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
|
||||
{ controllerModel: newModel, password: newPassword }));
|
||||
// const newInfo: ControllerInfo = {
|
||||
// id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
|
||||
// endpoint: 'newUrl',
|
||||
// kubeConfigFilePath: '/path/to/.kube/config',
|
||||
// kubeClusterContext: 'currentCluster',
|
||||
// username: 'newUser',
|
||||
// name: 'newName',
|
||||
// namespace: 'newNamespace',
|
||||
// rememberPassword: true,
|
||||
// resources: []
|
||||
// };
|
||||
// const newPassword = 'newPassword';
|
||||
// // Set up dialog to return new model with our new password from the reprompt
|
||||
// const newModel = new ControllerModel(
|
||||
// treeDataProvider,
|
||||
// newInfo,
|
||||
// newPassword);
|
||||
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
|
||||
// { controllerModel: newModel, password: newPassword }));
|
||||
|
||||
await model.login(true);
|
||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
|
||||
should(model.info).deepEqual(newInfo, 'Model info should have been updated');
|
||||
// await model.login(true);
|
||||
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||
// should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
|
||||
// should(model.info).deepEqual(newInfo, 'Model info should have been updated');
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// * Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
// *--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { PGResourceInfo, ResourceType } from 'arc';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
|
||||
import { ControllerModel, Registration } from '../../../models/controllerModel';
|
||||
import { PostgresModel } from '../../../models/postgresModel';
|
||||
import { PostgresConnectionStringsPage } from '../../../ui/dashboards/postgres/postgresConnectionStringsPage';
|
||||
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||
import { FakePostgresServerShowOutput } from '../../models/postgresModel.test';
|
||||
// import { PGResourceInfo, ResourceType } from 'arc';
|
||||
// import * as azExt from 'azdata-ext';
|
||||
// import * as should from 'should';
|
||||
// import * as sinon from 'sinon';
|
||||
// import * as TypeMoq from 'typemoq';
|
||||
// import * as vscode from 'vscode';
|
||||
// import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
|
||||
// import { ControllerModel, Registration } from '../../../models/controllerModel';
|
||||
// import { PostgresModel } from '../../../models/postgresModel';
|
||||
// import { PostgresConnectionStringsPage } from '../../../ui/dashboards/postgres/postgresConnectionStringsPage';
|
||||
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
// import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||
// import { FakePostgresServerShowOutput } from '../../models/postgresModel.test';
|
||||
|
||||
describe('postgresConnectionStringsPage', function (): void {
|
||||
let controllerModel: ControllerModel;
|
||||
let postgresModel: PostgresModel;
|
||||
let azdataApi: azdataExt.IAzdataApi;
|
||||
let postgresConnectionStrings: PostgresConnectionStringsPage;
|
||||
// describe('postgresConnectionStringsPage', function (): void {
|
||||
// let controllerModel: ControllerModel;
|
||||
// let postgresModel: PostgresModel;
|
||||
// let azdataApi: azExt.IAzdataApi;
|
||||
// let postgresConnectionStrings: PostgresConnectionStringsPage;
|
||||
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
// afterEach(function (): void {
|
||||
// sinon.restore();
|
||||
// });
|
||||
|
||||
beforeEach(async () => {
|
||||
// Stub the azdata CLI API
|
||||
azdataApi = new FakeAzdataApi();
|
||||
const azdataExt = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
azdataExt.setup(x => x.azdata).returns(() => azdataApi);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExt.object });
|
||||
// beforeEach(async () => {
|
||||
// // Stub the azdata CLI API
|
||||
// azdataApi = new FakeAzdataApi();
|
||||
// const azExt = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// azExt.setup(x => x.azdata).returns(() => azdataApi);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExt.object });
|
||||
|
||||
// Setup Controller Model
|
||||
controllerModel = new FakeControllerModel();
|
||||
// // Setup Controller Model
|
||||
// controllerModel = new FakeControllerModel();
|
||||
|
||||
//Stub calling azdata login and acquiring session
|
||||
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
|
||||
// // Setup PostgresModel
|
||||
// const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };
|
||||
// const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||
// postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||
|
||||
// Setup PostgresModel
|
||||
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };
|
||||
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||
// // Setup stub of show call
|
||||
// const postgresShow = sinon.stub().returns(FakePostgresServerShowOutput);
|
||||
// sinon.stub(azdataApi, 'arc').get(() => {
|
||||
// return { postgres: { server: { show(name: string) { return postgresShow(name); } } } };
|
||||
// });
|
||||
|
||||
// Setup stub of show call
|
||||
const postgresShow = sinon.stub().returns(FakePostgresServerShowOutput);
|
||||
sinon.stub(azdataApi, 'arc').get(() => {
|
||||
return { postgres: { server: { show(name: string) { return postgresShow(name); } } } };
|
||||
});
|
||||
// // Setup the PostgresConnectionsStringsPage
|
||||
// let { modelViewMock } = createModelViewMock();
|
||||
// postgresConnectionStrings = new PostgresConnectionStringsPage(modelViewMock.object, undefined!, postgresModel);
|
||||
// });
|
||||
|
||||
// Setup the PostgresConnectionsStringsPage
|
||||
let { modelViewMock } = createModelViewMock();
|
||||
postgresConnectionStrings = new PostgresConnectionStringsPage(modelViewMock.object, undefined!, postgresModel);
|
||||
});
|
||||
// describe('getConnectionStrings', function (): void {
|
||||
|
||||
describe('getConnectionStrings', function (): void {
|
||||
// it('Strings container should be empty since postgres model has not been refreshed', async function (): Promise<void> {
|
||||
// should(postgresConnectionStrings['getConnectionStrings']()).be.empty();
|
||||
// });
|
||||
|
||||
it('Strings container should be empty since postgres model has not been refreshed', async function (): Promise<void> {
|
||||
should(postgresConnectionStrings['getConnectionStrings']()).be.empty();
|
||||
});
|
||||
// it('String contain correct ip and port', async function (): Promise<void> {
|
||||
// // Call to provide external endpoint
|
||||
// await postgresModel.refresh();
|
||||
|
||||
it('String contain correct ip and port', async function (): Promise<void> {
|
||||
// Call to provide external endpoint
|
||||
await postgresModel.refresh();
|
||||
// let endpoint = FakePostgresServerShowOutput.result.status.primaryEndpoint.split(':');
|
||||
|
||||
let endpoint = FakePostgresServerShowOutput.result.status.primaryEndpoint.split(':');
|
||||
// postgresConnectionStrings['getConnectionStrings']().forEach(k => {
|
||||
// should(k.value.includes(endpoint[0])).be.True();
|
||||
// should(k.value.includes(endpoint[1])).be.True();
|
||||
// });
|
||||
// });
|
||||
|
||||
postgresConnectionStrings['getConnectionStrings']().forEach(k => {
|
||||
should(k.value.includes(endpoint[0])).be.True();
|
||||
should(k.value.includes(endpoint[1])).be.True();
|
||||
});
|
||||
});
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
@@ -1,110 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * 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 sinon from 'sinon';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as utils from '../../../common/utils';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { Deferred } from '../../../common/promise';
|
||||
import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
|
||||
import { StubButton } from '@microsoft/azdata-test/out/stubs/modelView/stubButton';
|
||||
import { PGResourceInfo, ResourceType } from 'arc';
|
||||
import { PostgresOverviewPage } from '../../../ui/dashboards/postgres/postgresOverviewPage';
|
||||
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||
import { PostgresModel } from '../../../models/postgresModel';
|
||||
import { ControllerModel, Registration } from '../../../models/controllerModel';
|
||||
// import * as vscode from 'vscode';
|
||||
// import * as sinon from 'sinon';
|
||||
// import * as TypeMoq from 'typemoq';
|
||||
// import * as azExt from 'azdata-ext';
|
||||
// import * as utils from '../../../common/utils';
|
||||
// import * as loc from '../../../localizedConstants';
|
||||
// import { Deferred } from '../../../common/promise';
|
||||
// import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
|
||||
// import { StubButton } from '@microsoft/azdata-test/out/stubs/modelView/stubButton';
|
||||
// import { PGResourceInfo, ResourceType } from 'arc';
|
||||
// import { PostgresOverviewPage } from '../../../ui/dashboards/postgres/postgresOverviewPage';
|
||||
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
// import { FakeAzApi } from '../../mocks/fakeAzdataApi';
|
||||
// import { PostgresModel } from '../../../models/postgresModel';
|
||||
// import { ControllerModel, Registration } from '../../../models/controllerModel';
|
||||
|
||||
describe('postgresOverviewPage', () => {
|
||||
let postgresOverview: PostgresOverviewPage;
|
||||
let azdataApi: azdataExt.IAzdataApi;
|
||||
let controllerModel: ControllerModel;
|
||||
let postgresModel: PostgresModel;
|
||||
// describe('postgresOverviewPage', () => {
|
||||
// let postgresOverview: PostgresOverviewPage;
|
||||
// let azdataApi: azExt.IAzdataApi;
|
||||
// let controllerModel: ControllerModel;
|
||||
// let postgresModel: PostgresModel;
|
||||
|
||||
let showInformationMessage: sinon.SinonStub;
|
||||
let showErrorMessage: sinon.SinonStub;
|
||||
// let showInformationMessage: sinon.SinonStub;
|
||||
// let showErrorMessage: sinon.SinonStub;
|
||||
|
||||
let informationMessageShown: Deferred;
|
||||
let errorMessageShown: Deferred;
|
||||
// let informationMessageShown: Deferred;
|
||||
// let errorMessageShown: Deferred;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Stub the azdata CLI API
|
||||
azdataApi = new FakeAzdataApi();
|
||||
const azdataExt = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||
azdataExt.setup(x => x.azdata).returns(() => azdataApi);
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExt.object });
|
||||
// beforeEach(async () => {
|
||||
// // Stub the azdata CLI API
|
||||
// azdataApi = new FakeAzdataApi();
|
||||
// const azExt = TypeMoq.Mock.ofType<azExt.IExtension>();
|
||||
// azExt.setup(x => x.azdata).returns(() => azdataApi);
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExt.object });
|
||||
|
||||
// Stub the window UI
|
||||
informationMessageShown = new Deferred();
|
||||
showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').callsFake(
|
||||
(_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
|
||||
informationMessageShown.resolve();
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
// // Stub the window UI
|
||||
// informationMessageShown = new Deferred();
|
||||
// showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').callsFake(
|
||||
// (_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
|
||||
// informationMessageShown.resolve();
|
||||
// return Promise.resolve(undefined);
|
||||
// });
|
||||
|
||||
errorMessageShown = new Deferred();
|
||||
showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').callsFake(
|
||||
(_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
|
||||
errorMessageShown.resolve();
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
// errorMessageShown = new Deferred();
|
||||
// showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').callsFake(
|
||||
// (_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
|
||||
// errorMessageShown.resolve();
|
||||
// return Promise.resolve(undefined);
|
||||
// });
|
||||
|
||||
// Setup the PostgresModel
|
||||
controllerModel = new FakeControllerModel();
|
||||
const postgresResource: PGResourceInfo = { name: 'my-pg', resourceType: '' };
|
||||
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||
const treeDataProvider = new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object);
|
||||
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, treeDataProvider);
|
||||
// // Setup the PostgresModel
|
||||
// controllerModel = new FakeControllerModel();
|
||||
// const postgresResource: PGResourceInfo = { name: 'my-pg', resourceType: '' };
|
||||
// const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||
// const treeDataProvider = new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object);
|
||||
// postgresModel = new PostgresModel(controllerModel, postgresResource, registration, treeDataProvider);
|
||||
|
||||
// Setup the PostgresOverviewPage
|
||||
const { modelViewMock } = createModelViewMock();
|
||||
postgresOverview = new PostgresOverviewPage(modelViewMock.object, undefined!, controllerModel, postgresModel);
|
||||
// Call the getter to initialize toolbar, but we don't need to use it for anything
|
||||
// eslint-disable-next-line code-no-unused-expressions
|
||||
postgresOverview['toolbarContainer'];
|
||||
});
|
||||
// // Setup the PostgresOverviewPage
|
||||
// const { modelViewMock } = createModelViewMock();
|
||||
// postgresOverview = new PostgresOverviewPage(modelViewMock.object, undefined!, controllerModel, postgresModel);
|
||||
// // Call the getter to initialize toolbar, but we don't need to use it for anything
|
||||
// // eslint-disable-next-line code-no-unused-expressions
|
||||
// postgresOverview['toolbarContainer'];
|
||||
// });
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
// afterEach(() => {
|
||||
// sinon.restore();
|
||||
// });
|
||||
|
||||
describe('delete button', () => {
|
||||
let refreshTreeNode: sinon.SinonStub;
|
||||
// describe('delete button', () => {
|
||||
// let refreshTreeNode: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(utils, 'promptForInstanceDeletion').returns(Promise.resolve(true));
|
||||
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
|
||||
refreshTreeNode = sinon.stub(controllerModel, 'refreshTreeNode');
|
||||
});
|
||||
// beforeEach(() => {
|
||||
// sinon.stub(utils, 'promptForInstanceDeletion').returns(Promise.resolve(true));
|
||||
// refreshTreeNode = sinon.stub(controllerModel, 'refreshTreeNode');
|
||||
// });
|
||||
|
||||
it('deletes Postgres on success', async () => {
|
||||
// Stub 'azdata arc postgres server delete' to return success
|
||||
const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete');
|
||||
// it('deletes Postgres on success', async () => {
|
||||
// // Stub 'azdata arc postgres server delete' to return success
|
||||
// const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete');
|
||||
|
||||
(postgresOverview['deleteButton'] as StubButton).click();
|
||||
await informationMessageShown;
|
||||
sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||
sinon.assert.calledOnceWithExactly(showInformationMessage, loc.instanceDeleted(postgresModel.info.name));
|
||||
sinon.assert.notCalled(showErrorMessage);
|
||||
sinon.assert.calledOnce(refreshTreeNode);
|
||||
});
|
||||
// (postgresOverview['deleteButton'] as StubButton).click();
|
||||
// await informationMessageShown;
|
||||
// sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||
// sinon.assert.calledOnceWithExactly(showInformationMessage, loc.instanceDeleted(postgresModel.info.name));
|
||||
// sinon.assert.notCalled(showErrorMessage);
|
||||
// sinon.assert.calledOnce(refreshTreeNode);
|
||||
// });
|
||||
|
||||
it('shows an error message on failure', async () => {
|
||||
// Stub 'azdata arc postgres server delete' to throw an exception
|
||||
const error = new Error('something bad happened');
|
||||
const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete').throws(error);
|
||||
// it('shows an error message on failure', async () => {
|
||||
// // Stub 'azdata arc postgres server delete' to throw an exception
|
||||
// const error = new Error('something bad happened');
|
||||
// const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete').throws(error);
|
||||
|
||||
(postgresOverview['deleteButton'] as StubButton).click();
|
||||
await errorMessageShown;
|
||||
sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||
sinon.assert.notCalled(showInformationMessage);
|
||||
sinon.assert.calledOnceWithExactly(showErrorMessage, loc.instanceDeletionFailed(postgresModel.info.name, error.message));
|
||||
sinon.assert.notCalled(refreshTreeNode);
|
||||
});
|
||||
});
|
||||
});
|
||||
// (postgresOverview['deleteButton'] as StubButton).click();
|
||||
// await errorMessageShown;
|
||||
// sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||
// sinon.assert.notCalled(showInformationMessage);
|
||||
// sinon.assert.calledOnceWithExactly(showErrorMessage, loc.instanceDeletionFailed(postgresModel.info.name, error.message));
|
||||
// sinon.assert.notCalled(refreshTreeNode);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// * Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
// *--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ControllerInfo } from 'arc';
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { ControllerModel } from '../../../models/controllerModel';
|
||||
import { ConnectToControllerDialog } from '../../../ui/dialogs/connectControllerDialog';
|
||||
// import { ControllerInfo } from 'arc';
|
||||
// import * as should from 'should';
|
||||
// import * as sinon from 'sinon';
|
||||
// import { v4 as uuid } from 'uuid';
|
||||
// import * as loc from '../../../localizedConstants';
|
||||
// import { ControllerModel } from '../../../models/controllerModel';
|
||||
// import { ConnectToControllerDialog } from '../../../ui/dialogs/connectControllerDialog';
|
||||
|
||||
describe('ConnectControllerDialog', function (): void {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
// describe('ConnectControllerDialog', function (): void {
|
||||
// afterEach(function (): void {
|
||||
// sinon.restore();
|
||||
// });
|
||||
|
||||
(<{ info: ControllerInfo | undefined, description: string }[]>[
|
||||
{ info: undefined, description: 'all input' },
|
||||
{ info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
|
||||
{ info: { endpoint: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
|
||||
it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
|
||||
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
connectControllerDialog.showDialog(test.info, undefined);
|
||||
await connectControllerDialog.isInitialized;
|
||||
const validateResult = await connectControllerDialog.validate();
|
||||
should(validateResult).be.false();
|
||||
});
|
||||
});
|
||||
// (<{ info: ControllerInfo | undefined, description: string }[]>[
|
||||
// { info: undefined, description: 'all input' },
|
||||
// { info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
|
||||
// { info: { endpoint: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
|
||||
// it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
|
||||
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
// connectControllerDialog.showDialog(test.info, undefined);
|
||||
// await connectControllerDialog.isInitialized;
|
||||
// const validateResult = await connectControllerDialog.validate();
|
||||
// should(validateResult).be.false();
|
||||
// });
|
||||
// });
|
||||
|
||||
it('validate returns false if controller refresh fails', async function (): Promise<void> {
|
||||
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
|
||||
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
const info: ControllerInfo = { id: uuid(), endpoint: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] };
|
||||
connectControllerDialog.showDialog(info, 'pwd');
|
||||
await connectControllerDialog.isInitialized;
|
||||
const validateResult = await connectControllerDialog.validate();
|
||||
should(validateResult).be.false('Validation should have returned false');
|
||||
});
|
||||
// it('validate returns false if controller refresh fails', async function (): Promise<void> {
|
||||
// sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
|
||||
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
// const info: ControllerInfo = { id: uuid(), endpoint: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] };
|
||||
// connectControllerDialog.showDialog(info, 'pwd');
|
||||
// await connectControllerDialog.isInitialized;
|
||||
// const validateResult = await connectControllerDialog.validate();
|
||||
// should(validateResult).be.false('Validation should have returned false');
|
||||
// });
|
||||
|
||||
it('validate replaces http with https', async function (): Promise<void> {
|
||||
await validateConnectControllerDialog(
|
||||
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30081');
|
||||
});
|
||||
// it('validate replaces http with https', async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog(
|
||||
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30081');
|
||||
// });
|
||||
|
||||
it('validate appends https if missing', async function (): Promise<void> {
|
||||
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30080');
|
||||
});
|
||||
// it('validate appends https if missing', async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30080');
|
||||
// });
|
||||
|
||||
it('validate appends default port if missing', async function (): Promise<void> {
|
||||
await validateConnectControllerDialog({ id: uuid(), endpoint: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30080');
|
||||
});
|
||||
// it('validate appends default port if missing', async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog({ id: uuid(), endpoint: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30080');
|
||||
// });
|
||||
|
||||
it('validate appends both port and https if missing', async function (): Promise<void> {
|
||||
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30080');
|
||||
});
|
||||
// it('validate appends both port and https if missing', async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30080');
|
||||
// });
|
||||
|
||||
for (const name of ['', undefined]) {
|
||||
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
|
||||
await validateConnectControllerDialog(
|
||||
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30081');
|
||||
});
|
||||
}
|
||||
// for (const name of ['', undefined]) {
|
||||
// it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog(
|
||||
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30081');
|
||||
// });
|
||||
// }
|
||||
|
||||
it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
|
||||
await validateConnectControllerDialog(
|
||||
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
'https://127.0.0.1:30081',
|
||||
undefined);
|
||||
});
|
||||
});
|
||||
// it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
|
||||
// await validateConnectControllerDialog(
|
||||
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
|
||||
// 'https://127.0.0.1:30081',
|
||||
// undefined);
|
||||
// });
|
||||
// });
|
||||
|
||||
async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl: string, arcInstanceName: string = 'arc-instance'): Promise<void> {
|
||||
const expectedControllerInfoName = info.name || arcInstanceName || loc.defaultControllerName;
|
||||
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
// Stub out refresh calls to controllerModel - we'll test those separately
|
||||
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.resolve());
|
||||
// stub out controller registration response to return a known instanceName for the dc.
|
||||
/*
|
||||
sinon.stub(ControllerModel.prototype, 'controllerRegistration').get(() => {
|
||||
return <Registration>{ instanceName: arcInstanceName };
|
||||
});
|
||||
*/
|
||||
connectControllerDialog.showDialog(info, 'pwd');
|
||||
await connectControllerDialog.isInitialized;
|
||||
const validateResult = await connectControllerDialog.validate();
|
||||
should(validateResult).be.true('Validation should have returned true');
|
||||
const model = await connectControllerDialog.waitForClose();
|
||||
should(model?.controllerModel.info.endpoint).equal(expectedUrl);
|
||||
should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
|
||||
}
|
||||
// async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl: string, arcInstanceName: string = 'arc-instance'): Promise<void> {
|
||||
// const expectedControllerInfoName = info.name || arcInstanceName || loc.defaultControllerName;
|
||||
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||
// // Stub out refresh calls to controllerModel - we'll test those separately
|
||||
// sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.resolve());
|
||||
// // stub out controller registration response to return a known instanceName for the dc.
|
||||
// /*
|
||||
// sinon.stub(ControllerModel.prototype, 'controllerRegistration').get(() => {
|
||||
// return <Registration>{ instanceName: arcInstanceName };
|
||||
// });
|
||||
// */
|
||||
// connectControllerDialog.showDialog(info, 'pwd');
|
||||
// await connectControllerDialog.isInitialized;
|
||||
// const validateResult = await connectControllerDialog.validate();
|
||||
// should(validateResult).be.true('Validation should have returned true');
|
||||
// const model = await connectControllerDialog.waitForClose();
|
||||
// should(model?.controllerModel.info.endpoint).equal(expectedUrl);
|
||||
// should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
|
||||
// }
|
||||
|
||||
@@ -1,184 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// * Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
// *--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ControllerInfo, ResourceType } from 'arc';
|
||||
import 'mocha';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sinon from 'sinon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as kubeUtils from '../../../common/kubeUtils';
|
||||
import { ControllerModel } from '../../../models/controllerModel';
|
||||
import { MiaaModel } from '../../../models/miaaModel';
|
||||
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
|
||||
import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
|
||||
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||
// import { ControllerInfo, ResourceType } from 'arc';
|
||||
// import 'mocha';
|
||||
// import * as should from 'should';
|
||||
// import * as TypeMoq from 'typemoq';
|
||||
// import * as sinon from 'sinon';
|
||||
// import { v4 as uuid } from 'uuid';
|
||||
// import * as vscode from 'vscode';
|
||||
// import * as azdataExt from 'azdata-ext';
|
||||
// import * as kubeUtils from '../../../common/kubeUtils';
|
||||
// import { ControllerModel } from '../../../models/controllerModel';
|
||||
// import { MiaaModel } from '../../../models/miaaModel';
|
||||
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
|
||||
// import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
|
||||
// import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
|
||||
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
|
||||
// import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
|
||||
|
||||
interface ExtensionGlobalMemento extends vscode.Memento {
|
||||
setKeysForSync(keys: string[]): void;
|
||||
}
|
||||
// interface ExtensionGlobalMemento extends vscode.Memento {
|
||||
// setKeysForSync(keys: string[]): void;
|
||||
// }
|
||||
|
||||
function getDefaultControllerInfo(): ControllerInfo {
|
||||
return {
|
||||
id: uuid(),
|
||||
endpoint: '127.0.0.1',
|
||||
kubeConfigFilePath: '/path/to/.kube/config',
|
||||
kubeClusterContext: 'currentCluster',
|
||||
username: 'sa',
|
||||
name: 'my-arc',
|
||||
namespace: 'arc-ns',
|
||||
rememberPassword: true,
|
||||
resources: []
|
||||
};
|
||||
}
|
||||
// function getDefaultControllerInfo(): ControllerInfo {
|
||||
// return {
|
||||
// id: uuid(),
|
||||
// endpoint: '127.0.0.1',
|
||||
// kubeConfigFilePath: '/path/to/.kube/config',
|
||||
// kubeClusterContext: 'currentCluster',
|
||||
// username: 'sa',
|
||||
// name: 'my-arc',
|
||||
// namespace: 'arc-ns',
|
||||
// rememberPassword: true,
|
||||
// resources: []
|
||||
// };
|
||||
// }
|
||||
|
||||
describe('AzureArcTreeDataProvider tests', function (): void {
|
||||
let treeDataProvider: AzureArcTreeDataProvider;
|
||||
beforeEach(function (): void {
|
||||
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
||||
mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
||||
//treeDataProviderMock = TypeMoq.Mock.ofType<AzureArcTreeDataProvider>();
|
||||
treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
|
||||
});
|
||||
// describe('AzureArcTreeDataProvider tests', function (): void {
|
||||
// let treeDataProvider: AzureArcTreeDataProvider;
|
||||
// beforeEach(function (): void {
|
||||
// const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
// const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
||||
// mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
// mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
||||
// //treeDataProviderMock = TypeMoq.Mock.ofType<AzureArcTreeDataProvider>();
|
||||
// treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
|
||||
// });
|
||||
|
||||
describe('addOrUpdateController', function (): void {
|
||||
it('Multiple Controllers are added correctly', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = false;
|
||||
let children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
const controllerModel = new FakeControllerModel();
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
// describe('addOrUpdateController', function (): void {
|
||||
// it('Multiple Controllers are added correctly', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = false;
|
||||
// let children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
// const controllerModel = new FakeControllerModel();
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
|
||||
// Add a couple more
|
||||
const controllerModel2 = new FakeControllerModel();
|
||||
const controllerModel3 = new FakeControllerModel();
|
||||
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
await treeDataProvider.addOrUpdateController(controllerModel3, '');
|
||||
children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(3, 'Additional Controller nodes should be added correctly');
|
||||
});
|
||||
// // Add a couple more
|
||||
// const controllerModel2 = new FakeControllerModel();
|
||||
// const controllerModel3 = new FakeControllerModel();
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel3, '');
|
||||
// children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(3, 'Additional Controller nodes should be added correctly');
|
||||
// });
|
||||
|
||||
it('Adding a Controller more than once doesn\'t create duplicates', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = false;
|
||||
let children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
||||
});
|
||||
// it('Adding a Controller more than once doesn\'t create duplicates', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = false;
|
||||
// let children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
||||
// });
|
||||
|
||||
it('Updating an existing controller works as expected', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = false;
|
||||
let children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
const originalInfo: ControllerInfo = getDefaultControllerInfo();
|
||||
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
|
||||
const newInfo: ControllerInfo = { id: originalInfo.id, endpoint: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', namespace: 'new-namespace', username: 'admin', rememberPassword: false, resources: [] };
|
||||
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
|
||||
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
||||
should((<ControllerTreeNode>children[0]).model.info).deepEqual(newInfo);
|
||||
});
|
||||
});
|
||||
// it('Updating an existing controller works as expected', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = false;
|
||||
// let children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||
// const originalInfo: ControllerInfo = getDefaultControllerInfo();
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// should(children.length).equal(1, 'Controller node should be added correctly');
|
||||
// should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
|
||||
// const newInfo: ControllerInfo = { id: originalInfo.id, endpoint: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', namespace: 'new-namespace', username: 'admin', rememberPassword: false, resources: [] };
|
||||
// const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
// should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
||||
// should((<ControllerTreeNode>children[0]).model.info).deepEqual(newInfo);
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('getChildren', function (): void {
|
||||
it('should return an empty array before loading stored controllers is completed', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = true;
|
||||
let children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(0, 'While loading we should return an empty array');
|
||||
});
|
||||
// describe('getChildren', function (): void {
|
||||
// it('should return an empty array before loading stored controllers is completed', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = true;
|
||||
// let children = await treeDataProvider.getChildren();
|
||||
// should(children.length).equal(0, 'While loading we should return an empty array');
|
||||
// });
|
||||
|
||||
it('should return no children after loading', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = false;
|
||||
let children = await treeDataProvider.getChildren();
|
||||
should(children.length).equal(0, 'After loading we should have 0 children');
|
||||
});
|
||||
// it('should return no children after loading', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = false;
|
||||
// let children = await treeDataProvider.getChildren();
|
||||
// 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();
|
||||
const pgInstances = [{ name: 'pg1', state: '', workers: 0 }];
|
||||
const miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
|
||||
fakeAzdataApi.postgresInstances = pgInstances;
|
||||
fakeAzdataApi.miaaInstances = miaaInstances;
|
||||
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
|
||||
// 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();
|
||||
// const pgInstances = [{ name: 'pg1', state: '', workers: 0 }];
|
||||
// const miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
|
||||
// fakeAzdataApi.postgresInstances = pgInstances;
|
||||
// fakeAzdataApi.miaaInstances = miaaInstances;
|
||||
// mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
|
||||
|
||||
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
|
||||
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
|
||||
const children = await treeDataProvider.getChildren(controllerNode);
|
||||
should(children.filter(c => c.label === pgInstances[0].name).length).equal(1, 'Should have a Postgres child');
|
||||
should(children.filter(c => c.label === miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
|
||||
should(children.length).equal(2, 'Should have exactly 2 children');
|
||||
});
|
||||
});
|
||||
// sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
|
||||
// sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// const controllerNode = treeDataProvider.getControllerNode(controllerModel);
|
||||
// const children = await treeDataProvider.getChildren(controllerNode);
|
||||
// should(children.filter(c => c.label === pgInstances[0].name).length).equal(1, 'Should have a Postgres child');
|
||||
// should(children.filter(c => c.label === miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
|
||||
// should(children.length).equal(2, 'Should have exactly 2 children');
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('removeController', function (): void {
|
||||
it('removing a controller should work as expected', async function (): Promise<void> {
|
||||
treeDataProvider['_loading'] = false;
|
||||
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
const info2 = getDefaultControllerInfo();
|
||||
info2.username = 'cloudsa';
|
||||
const controllerModel2 = new ControllerModel(treeDataProvider, info2);
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
|
||||
await treeDataProvider.removeController(children[0]);
|
||||
should((await treeDataProvider.getChildren()).length).equal(1, 'Node should have been removed');
|
||||
await treeDataProvider.removeController(children[0]);
|
||||
should((await treeDataProvider.getChildren()).length).equal(1, 'Removing same node again should do nothing');
|
||||
await treeDataProvider.removeController(children[1]);
|
||||
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node should work');
|
||||
await treeDataProvider.removeController(children[1]);
|
||||
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
|
||||
});
|
||||
});
|
||||
// describe('removeController', function (): void {
|
||||
// it('removing a controller should work as expected', async function (): Promise<void> {
|
||||
// treeDataProvider['_loading'] = false;
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
// const info2 = getDefaultControllerInfo();
|
||||
// info2.username = 'cloudsa';
|
||||
// const controllerModel2 = new ControllerModel(treeDataProvider, info2);
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||
// const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
|
||||
// await treeDataProvider.removeController(children[0]);
|
||||
// should((await treeDataProvider.getChildren()).length).equal(1, 'Node should have been removed');
|
||||
// await treeDataProvider.removeController(children[0]);
|
||||
// should((await treeDataProvider.getChildren()).length).equal(1, 'Removing same node again should do nothing');
|
||||
// await treeDataProvider.removeController(children[1]);
|
||||
// should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node should work');
|
||||
// await treeDataProvider.removeController(children[1]);
|
||||
// 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, getDefaultControllerInfo());
|
||||
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||
await should(openDashboardPromise).be.rejected();
|
||||
});
|
||||
// describe('openResourceDashboard', function (): void {
|
||||
// it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
// 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, getDefaultControllerInfo());
|
||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||
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, getDefaultControllerInfo());
|
||||
// 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, getDefaultControllerInfo());
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
// it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
|
||||
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||
// 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');
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
7
extensions/arc/src/typings/arc.d.ts
vendored
7
extensions/arc/src/typings/arc.d.ts
vendored
@@ -36,12 +36,9 @@ declare module 'arc' {
|
||||
export type ControllerInfo = {
|
||||
id: string,
|
||||
kubeConfigFilePath: string,
|
||||
kubeClusterContext: string
|
||||
endpoint: string | undefined,
|
||||
kubeClusterContext: string,
|
||||
namespace: string,
|
||||
name: string,
|
||||
username: string,
|
||||
rememberPassword: boolean,
|
||||
resources: ResourceInfo[]
|
||||
};
|
||||
|
||||
@@ -51,7 +48,5 @@ declare module 'arc' {
|
||||
}
|
||||
export interface IExtension {
|
||||
getRegisteredDataControllers(): Promise<DataController[]>;
|
||||
getControllerPassword(controllerInfo: ControllerInfo): Promise<string>;
|
||||
reacquireControllerPassword(controllerInfo: ControllerInfo, password: string, retryCount?: number): Promise<string>;
|
||||
}
|
||||
}
|
||||
|
||||
2
extensions/arc/src/typings/refs.d.ts
vendored
2
extensions/arc/src/typings/refs.d.ts
vendored
@@ -7,5 +7,5 @@
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../azdata/src/typings/azdata-ext.d.ts'/>
|
||||
/// <reference path='../../../azcli/src/typings/az-ext.d.ts'/>
|
||||
/// <reference path='../../../resource-deployment/src/typings/resource-deployment.d.ts'/>
|
||||
|
||||
@@ -26,7 +26,7 @@ export class FilePicker {
|
||||
) {
|
||||
const buttonWidth = 80;
|
||||
this.filePathInputBox = modelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
.withProps({
|
||||
value: initialPath,
|
||||
ariaLabel: ariaLabel,
|
||||
validationErrorMessage: validationErrorMessage,
|
||||
@@ -43,7 +43,7 @@ export class FilePicker {
|
||||
}).component();
|
||||
|
||||
this.filePickerButton = modelBuilder.button()
|
||||
.withProperties<azdata.ButtonProperties>({
|
||||
.withProps({
|
||||
label: loc.browse,
|
||||
width: buttonWidth,
|
||||
secondary: true
|
||||
@@ -89,5 +89,5 @@ function createFlexContainer(modelBuilder: azdata.ModelBuilder, items: azdata.Co
|
||||
alignItems = alignItems || (rowLayout ? 'center' : undefined);
|
||||
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px', } } : {};
|
||||
const flexLayout: azdata.FlexLayout = { flexFlow: flexFlow, height: height, width: width, alignItems: alignItems };
|
||||
return modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProperties<azdata.ComponentProperties>({ CSSStyles: cssStyles || {} }).component();
|
||||
return modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProps({ CSSStyles: cssStyles || {} }).component();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export abstract class KeyValue extends vscode.Disposable {
|
||||
alignItems: 'center'
|
||||
}).component();
|
||||
|
||||
const keyComponent = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const keyComponent = modelBuilder.text().withProps({
|
||||
value: key,
|
||||
CSSStyles: { ...cssStyles.text, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
@@ -80,7 +80,7 @@ export class TextKeyValue extends KeyValue {
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.text = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
this.text = modelBuilder.text().withProps({
|
||||
value: value,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
@@ -101,16 +101,17 @@ export abstract class BaseInputKeyValue extends KeyValue {
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, multiline: boolean) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.input = modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
this.input = modelBuilder.inputBox().withProps({
|
||||
value: value,
|
||||
readOnly: true,
|
||||
multiline: multiline
|
||||
multiline: multiline,
|
||||
ariaLabel: loc.connectionString(key)
|
||||
}).component();
|
||||
|
||||
const inputContainer = modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
inputContainer.addItem(this.input);
|
||||
|
||||
const copy = modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const copy = modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.copy,
|
||||
width: '17px',
|
||||
height: '17px',
|
||||
@@ -153,7 +154,7 @@ export class LinkKeyValue extends KeyValue {
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, onClick: (e: any) => any) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.link = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
this.link = modelBuilder.hyperlink().withProps({
|
||||
label: value,
|
||||
url: ''
|
||||
}).component();
|
||||
|
||||
@@ -26,7 +26,7 @@ export class RadioOptionsGroup {
|
||||
private _loadingCompleteMessage: string,
|
||||
private _loadingCompleteErrorMessage: (error: any) => string
|
||||
) {
|
||||
this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
||||
this._divContainer = this._modelBuilder.divContainer().withProps({ clickable: false }).component();
|
||||
this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export class RadioOptionsGroup {
|
||||
const options = optionsInfo.values!;
|
||||
let defaultValue: string = optionsInfo.defaultValue!;
|
||||
options.forEach((option: string) => {
|
||||
const radioOption = this._modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||
const radioOption = this._modelBuilder.radioButton().withProps({
|
||||
label: option,
|
||||
checked: option === defaultValue,
|
||||
name: this._groupName,
|
||||
|
||||
@@ -18,7 +18,7 @@ export class ControllerDashboard extends Dashboard {
|
||||
public override async showDashboard(): Promise<void> {
|
||||
await super.showDashboard();
|
||||
// Kick off the model refresh but don't wait on it since that's all handled with callbacks anyways
|
||||
this._controllerModel.refresh(false).catch(err => console.log(`Error refreshing Controller dashboard ${err}`));
|
||||
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing Controller dashboard ${err}`));
|
||||
}
|
||||
|
||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||
|
||||
@@ -58,7 +58,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
|
||||
protected async refresh(): Promise<void> {
|
||||
await this._controllerModel.refresh();
|
||||
await this._controllerModel.refresh(false, this._controllerModel.info.namespace);
|
||||
}
|
||||
|
||||
public get container(): azdata.Component {
|
||||
@@ -67,8 +67,8 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
this._propertiesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
|
||||
|
||||
this._arcResourcesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
|
||||
this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||
data: [],
|
||||
this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
dataValues: [],
|
||||
columns: [
|
||||
{
|
||||
displayName: '',
|
||||
@@ -126,7 +126,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
// Resources
|
||||
const arcResourcesTitle = this.modelView.modelBuilder.text()
|
||||
.withProperties<azdata.TextComponentProperties>({ value: loc.arcResources })
|
||||
.withProps({ value: loc.arcResources })
|
||||
.component();
|
||||
|
||||
contentContainer.addItem(arcResourcesTitle, {
|
||||
@@ -140,7 +140,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
public get toolbarContainer(): azdata.ToolbarContainer {
|
||||
|
||||
const newInstance = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const newInstance = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.newInstance,
|
||||
iconPath: IconPathHelper.add
|
||||
}).component();
|
||||
@@ -156,7 +156,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Refresh
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.refresh,
|
||||
iconPath: IconPathHelper.refresh
|
||||
}).component();
|
||||
@@ -173,7 +173,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
}));
|
||||
|
||||
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.openInAzurePortal,
|
||||
iconPath: IconPathHelper.openInTab,
|
||||
enabled: !!this._controllerModel.controllerConfig
|
||||
@@ -190,7 +190,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
}));
|
||||
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.troubleshoot,
|
||||
iconPath: IconPathHelper.wrench
|
||||
}).component();
|
||||
@@ -224,12 +224,12 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
this.controllerProperties.instanceNamespace = config?.metadata.namespace || this.controllerProperties.instanceNamespace;
|
||||
this.refreshDisplayedProperties();
|
||||
|
||||
this._arcResourcesTable.data = this._controllerModel.registrations
|
||||
let registrations: (string | azdata.ImageComponent | azdata.HyperlinkComponent)[][] = this._controllerModel.registrations
|
||||
.filter(r => r.instanceType !== ResourceType.dataControllers)
|
||||
.map(r => {
|
||||
const iconPath = getResourceTypeIcon(r.instanceType ?? '');
|
||||
const imageComponent = this.modelView.modelBuilder.image()
|
||||
.withProperties<azdata.ImageComponentProperties>({
|
||||
.withProps({
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
iconPath: iconPath,
|
||||
@@ -238,7 +238,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
}).component();
|
||||
|
||||
const nameComponent = this.modelView.modelBuilder.hyperlink()
|
||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||
.withProps({
|
||||
label: r.instanceName || '',
|
||||
url: ''
|
||||
}).component();
|
||||
@@ -249,6 +249,13 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
return [imageComponent, nameComponent, resourceTypeToDisplayName(r.instanceType), r.state];
|
||||
});
|
||||
|
||||
let registrationsData = registrations.map(r => {
|
||||
return r.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
this._arcResourcesTable.setDataValues(registrationsData);
|
||||
this._arcResourcesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
@@ -30,11 +30,11 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
memoryRequest?: string
|
||||
} = {};
|
||||
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _miaaModel: MiaaModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.initializeConfigurationBoxes();
|
||||
|
||||
@@ -59,33 +59,33 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.computeAndStorage,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
|
||||
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.miaaComputeAndStorageDescriptionPartOne,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'max-width': 'auto' }
|
||||
}).component();
|
||||
|
||||
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.scalingCompute,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-managed-instance',
|
||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.computeAndStorageDescriptionPartFour,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.computeAndStorageDescriptionPartFive,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.computeAndStorageDescriptionPartSix,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
@@ -112,7 +112,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Save Edits
|
||||
this.saveButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
this.saveButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.saveText,
|
||||
iconPath: IconPathHelper.save,
|
||||
enabled: false
|
||||
@@ -130,8 +130,8 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
await this._azdataApi.azdata.arc.sql.mi.edit(
|
||||
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.azdataAdditionalEnvVars, this._miaaModel.controllerModel.controllerContext);
|
||||
await this._azApi.az.sql.miarc.edit(
|
||||
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.info.namespace, this._miaaModel.controllerModel.azAdditionalEnvVars);
|
||||
} catch (err) {
|
||||
this.saveButton!.enabled = true;
|
||||
throw err;
|
||||
@@ -153,7 +153,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Discard
|
||||
this.discardButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
this.discardButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.discardText,
|
||||
iconPath: IconPathHelper.discard,
|
||||
enabled: false
|
||||
@@ -179,7 +179,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
}
|
||||
|
||||
private initializeConfigurationBoxes() {
|
||||
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 1,
|
||||
inputType: 'number',
|
||||
@@ -197,7 +197,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
})
|
||||
);
|
||||
|
||||
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 1,
|
||||
inputType: 'number',
|
||||
@@ -215,7 +215,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
})
|
||||
);
|
||||
|
||||
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 2,
|
||||
inputType: 'number',
|
||||
@@ -233,7 +233,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
})
|
||||
);
|
||||
|
||||
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||
readOnly: false,
|
||||
min: 2,
|
||||
inputType: 'number',
|
||||
@@ -277,7 +277,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
||||
alignItems: 'center'
|
||||
}).component();
|
||||
|
||||
const keyComponent = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const keyComponent = this.modelView.modelBuilder.text().withProps({
|
||||
value: `${key} :`,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
@@ -41,12 +41,12 @@ export class MiaaConnectionStringsPage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.connectionStrings,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
|
||||
const info = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const info = this.modelView.modelBuilder.text().withProps({
|
||||
value: `${loc.selectConnectionString}`,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
@@ -60,7 +60,7 @@ export class MiaaConnectionStringsPage extends DashboardPage {
|
||||
content.addItem(this._keyValueContainer.container);
|
||||
|
||||
this._connectionStringsMessage = this.modelView.modelBuilder.text()
|
||||
.withProperties<azdata.TextComponentProperties>({ CSSStyles: { 'text-align': 'center' } })
|
||||
.withProps({ CSSStyles: { 'text-align': 'center' } })
|
||||
.component();
|
||||
content.addItem(this._connectionStringsMessage);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MiaaDashboard extends Dashboard {
|
||||
public override async showDashboard(): Promise<void> {
|
||||
await super.showDashboard();
|
||||
// Kick off the model refreshes but don't wait on it since that's all handled with callbacks anyways
|
||||
this._controllerModel.refresh().catch(err => console.log(`Error refreshing controller model for MIAA dashboard ${err}`));
|
||||
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing controller model for MIAA dashboard ${err}`));
|
||||
this._miaaModel.refresh().catch(err => console.log(`Error refreshing MIAA model for MIAA dashboard ${err}`));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as azurecore from 'azurecore';
|
||||
import * as vscode from 'vscode';
|
||||
import { getDatabaseStateDisplayText, promptForInstanceDeletion } from '../../../common/utils';
|
||||
@@ -34,7 +34,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
private _connectToServerButton!: azdata.ButtonComponent;
|
||||
private _databasesTableLoading!: azdata.LoadingComponent;
|
||||
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
private readonly _azurecoreApi: azurecore.IExtension;
|
||||
|
||||
private _instanceProperties = {
|
||||
@@ -50,7 +50,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
||||
|
||||
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
|
||||
@@ -75,7 +75,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
|
||||
protected async refresh(): Promise<void> {
|
||||
await Promise.all([this._controllerModel.refresh(), this._miaaModel.refresh()]);
|
||||
await Promise.all([this._controllerModel.refresh(false, this._controllerModel.info.namespace), this._miaaModel.refresh()]);
|
||||
}
|
||||
|
||||
public get container(): azdata.Component {
|
||||
@@ -112,7 +112,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
this._databasesContainer.addItem(this._connectToServerLoading, { CSSStyles: { 'margin-top': '20px' } });
|
||||
|
||||
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component();
|
||||
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
@@ -132,11 +132,11 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: []
|
||||
dataValues: []
|
||||
}).component();
|
||||
|
||||
this._databasesMessage = this.modelView.modelBuilder.text()
|
||||
.withProperties<azdata.TextComponentProperties>({ CSSStyles: { 'text-align': 'center' } })
|
||||
.withProps({ CSSStyles: { 'text-align': 'center' } })
|
||||
.component();
|
||||
|
||||
// Update loaded components with data
|
||||
@@ -162,9 +162,9 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
// Service endpoints
|
||||
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
|
||||
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
|
||||
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
|
||||
|
||||
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
@@ -198,15 +198,15 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: [
|
||||
[loc.kibanaDashboard, this._kibanaLoading, loc.kibanaDashboardDescription],
|
||||
[loc.grafanaDashboard, this._grafanaLoading, loc.grafanaDashboardDescription]]
|
||||
dataValues: [
|
||||
[{ value: loc.kibanaDashboard }, { value: this._kibanaLoading }, { value: loc.kibanaDashboardDescription }],
|
||||
[{ value: loc.grafanaDashboard }, { value: this._grafanaLoading }, { value: loc.grafanaDashboardDescription }]]
|
||||
}).component();
|
||||
|
||||
rootContainer.addItem(endpointsTable);
|
||||
|
||||
// Databases
|
||||
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component());
|
||||
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.databases, CSSStyles: titleCSS }).component());
|
||||
this.disposables.push(
|
||||
this._connectToServerButton!.onDidClick(async () => {
|
||||
this._connectToServerButton!.enabled = false;
|
||||
@@ -227,7 +227,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
public get toolbarContainer(): azdata.ToolbarContainer {
|
||||
|
||||
const deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const deleteButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.deleteText,
|
||||
iconPath: IconPathHelper.delete
|
||||
}).component();
|
||||
@@ -244,7 +244,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token) => {
|
||||
return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
|
||||
return await this._azApi.az.sql.miarc.delete(this._miaaModel.info.name, this._controllerModel.info.namespace, this._controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
);
|
||||
await this._controllerModel.refreshTreeNode();
|
||||
@@ -265,7 +265,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Refresh
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.refresh,
|
||||
iconPath: IconPathHelper.refresh
|
||||
}).component();
|
||||
@@ -285,7 +285,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
}));
|
||||
|
||||
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.openInAzurePortal,
|
||||
iconPath: IconPathHelper.openInTab,
|
||||
enabled: !!this._controllerModel.controllerConfig
|
||||
@@ -302,7 +302,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
}
|
||||
}));
|
||||
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.troubleshoot,
|
||||
iconPath: IconPathHelper.wrench
|
||||
}).component();
|
||||
@@ -366,7 +366,13 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
// If we were able to get the databases it means we have a good connection so update the username too
|
||||
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
|
||||
this.refreshDisplayedProperties();
|
||||
this._databasesTable.data = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
|
||||
let databaseDisplayText = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
|
||||
let databasesTextValues = databaseDisplayText.map(d => {
|
||||
return d.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
this._databasesTable.setDataValues(databasesTextValues);
|
||||
this._databasesTableLoading.loading = false;
|
||||
|
||||
if (this._miaaModel.databasesLastUpdated) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
@@ -45,11 +45,11 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
private discardButton!: azdata.ButtonComponent;
|
||||
private saveButton!: azdata.ButtonComponent;
|
||||
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.initializeConfigurationBoxes();
|
||||
|
||||
@@ -88,7 +88,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.addingWorkerNodes,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli',
|
||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
@@ -99,7 +99,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.scalingCompute,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli',
|
||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
@@ -165,7 +165,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
try {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
workers: this.saveArgs.workers,
|
||||
@@ -174,7 +174,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
memoryRequest: this.schedulingParamsToEdit(this.saveArgs.memoryRequest!),
|
||||
memoryLimit: this.schedulingParamsToEdit(this.saveArgs.memoryLimit!)
|
||||
},
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
} catch (err) {
|
||||
// If an error occurs while editing the instance then re-enable the save button since
|
||||
// the edit wasn't successfully applied
|
||||
@@ -479,21 +480,14 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
|
||||
const keyComponent = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.workerNodeCount,
|
||||
requiredIndicator: true,
|
||||
description: loc.workerNodesInformation,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const keyContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
keyContainer.addItem(keyComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
||||
|
||||
const information = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.information,
|
||||
title: loc.workerNodesInformation,
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
keyContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
|
||||
flexContainer.addItem(keyContainer, keyFlex);
|
||||
|
||||
const inputContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
@@ -547,6 +541,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
if (component.value === originalValue) {
|
||||
return false;
|
||||
} else if ((!component.valid)) {
|
||||
this.discardButton.enabled = true;
|
||||
return false;
|
||||
} else {
|
||||
this.saveButton.enabled = true;
|
||||
@@ -575,21 +570,13 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||
|
||||
const titleComponent = this.modelView.modelBuilder.text().withProps({
|
||||
value: title,
|
||||
description: description,
|
||||
CSSStyles: { ...cssStyles.title, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const titleContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
titleContainer.addItem(titleComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
||||
|
||||
const information = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.information,
|
||||
title: description,
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
titleContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
|
||||
flexContainer.addItem(titleContainer, titleFlex);
|
||||
|
||||
let configurationSection = this.modelView.modelBuilder.divContainer().component();
|
||||
|
||||
@@ -38,17 +38,17 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.connectionStrings,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
|
||||
const info = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
const info = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.selectConnectionString,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const link = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
const link = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnAboutPostgresClients,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/get-connection-endpoints-and-connection-strings-postgres-hyperscale',
|
||||
}).component();
|
||||
@@ -63,7 +63,7 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
||||
|
||||
this.connectionStringsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.keyValueContainer.container)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
.withProps({
|
||||
loading: !this._postgresModel.configLastUpdated
|
||||
}).component();
|
||||
|
||||
|
||||
@@ -36,27 +36,27 @@ export class PostgresCoordinatorNodeParametersPage extends PostgresParametersPag
|
||||
}
|
||||
|
||||
protected async saveParameterEdits(engineSettings: string): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ coordinatorEngineSettings: engineSettings },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
|
||||
}
|
||||
|
||||
protected async resetAllParameters(): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ coordinatorEngineSettings: `''`, replaceEngineSettings: true },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
|
||||
protected async resetParameter(parameterName: string): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ coordinatorEngineSettings: parameterName + '=' },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export class PostgresDashboard extends Dashboard {
|
||||
await super.showDashboard();
|
||||
|
||||
// Kick off the model refresh but don't wait on it since that's all handled with callbacks anyways
|
||||
this._controllerModel.refresh().catch(err => console.log(`Error refreshing controller model for Postgres dashboard ${err}`));
|
||||
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing controller model for Postgres dashboard ${err}`));
|
||||
this._postgresModel.refresh().catch(err => console.log(`Error refreshing Postgres model for Postgres dashboard ${err}`));
|
||||
}
|
||||
|
||||
|
||||
@@ -33,17 +33,17 @@ export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.diagnoseAndSolveProblems,
|
||||
CSSStyles: { ...cssStyles.title, 'margin-bottom': '20px' }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.clickTheTroubleshootButton('Postgres'),
|
||||
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
|
||||
}).component());
|
||||
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.wrench,
|
||||
label: loc.troubleshoot,
|
||||
width: '160px'
|
||||
|
||||
@@ -5,28 +5,28 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
import { PostgresModel } from '../../../models/postgresModel';
|
||||
import { AddPGExtensionsDialog } from '../../dialogs/addPGExtensionsDialog';
|
||||
import { Deferred } from '../../../common/promise';
|
||||
|
||||
export class PostgresExtensionsPage extends DashboardPage {
|
||||
|
||||
private extensionNames: string[] = [];
|
||||
private droppedExtensions: string[] = [];
|
||||
private extensionsTable!: azdata.DeclarativeTableComponent;
|
||||
private extensionsLoading!: azdata.LoadingComponent;
|
||||
private addExtensionsButton!: azdata.ButtonComponent;
|
||||
private _dropExtPromise?: Deferred<void>;
|
||||
private dropExtensionsButton!: azdata.ButtonComponent;
|
||||
private extensionsLink!: azdata.HyperlinkComponent;
|
||||
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.disposables.push(
|
||||
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated())));
|
||||
@@ -79,23 +79,23 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.extensionName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '20px',
|
||||
isReadOnly: true,
|
||||
width: '95%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.dropText,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: false,
|
||||
width: '10%',
|
||||
displayName: loc.extensionName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '100%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: []
|
||||
dataValues: []
|
||||
}).component();
|
||||
|
||||
this.extensionsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
@@ -114,21 +114,22 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Add extensions
|
||||
this.addExtensionsButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.addExtensions,
|
||||
ariaLabel: loc.addExtensions,
|
||||
label: loc.loadExtensions,
|
||||
ariaLabel: loc.loadExtensions,
|
||||
iconPath: IconPathHelper.add
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
this.addExtensionsButton.onDidClick(async () => {
|
||||
const addExtDialog = new AddPGExtensionsDialog(this._postgresModel);
|
||||
addExtDialog.showDialog(loc.addExtensions);
|
||||
addExtDialog.showDialog(loc.loadExtensions);
|
||||
|
||||
let extArg = await addExtDialog.waitForClose();
|
||||
if (extArg) {
|
||||
try {
|
||||
this.addExtensionsButton.enabled = false;
|
||||
let extensionList = this.extensionNames.join() + ',' + extArg;
|
||||
this.dropExtensionsButton.enabled = false;
|
||||
let extensionList = this.extensionNames.length ? this.extensionNames.join() + ',' + extArg : extArg;
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
@@ -137,12 +138,13 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
extensions: extensionList
|
||||
},
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
|
||||
try {
|
||||
await this._postgresModel.refresh();
|
||||
@@ -152,7 +154,7 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
}
|
||||
);
|
||||
|
||||
vscode.window.showInformationMessage(loc.extensionsAdded(extensionList));
|
||||
vscode.window.showInformationMessage(loc.extensionsAdded(extArg));
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
@@ -162,43 +164,20 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
}
|
||||
}));
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||
{ component: this.addExtensionsButton }
|
||||
]).component();
|
||||
}
|
||||
|
||||
private refreshExtensionsTable(): void {
|
||||
let extensions = this._postgresModel.config!.spec.engine.extensions;
|
||||
this.extensionsTable.data = extensions.map(e => {
|
||||
|
||||
this.extensionNames.push(e.name);
|
||||
|
||||
return [e.name, this.createDropButton(e.name)];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates drop button to add to each row of extensions table.
|
||||
* Allows user to drop individual extension.
|
||||
* @param name name of postgres extension the drop button will be tied to.
|
||||
*/
|
||||
public createDropButton(name: string): azdata.ButtonComponent {
|
||||
// Can drop individual extensions
|
||||
let button = this.modelView.modelBuilder.button().withProps({
|
||||
// Drop extensions
|
||||
this.dropExtensionsButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.unloadExtensions,
|
||||
ariaLabel: loc.unloadExtensions,
|
||||
iconPath: IconPathHelper.delete,
|
||||
ariaLabel: loc.dropExtension,
|
||||
title: loc.dropExtension,
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
enabled: true
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.disposables.push(
|
||||
button.onDidClick(async () => {
|
||||
this.dropExtensionsButton.onDidClick(async () => {
|
||||
try {
|
||||
this.addExtensionsButton.enabled = false;
|
||||
button.enabled = false;
|
||||
await this.dropExtension(name);
|
||||
this.dropExtensionsButton.enabled = false;
|
||||
await this.dropExtension();
|
||||
|
||||
try {
|
||||
await this._postgresModel.refresh();
|
||||
@@ -206,63 +185,103 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage(loc.extensionDropped(name));
|
||||
vscode.window.showInformationMessage(loc.extensionsDropped(this.droppedExtensions.join()));
|
||||
this.droppedExtensions = [];
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
} finally {
|
||||
this.addExtensionsButton.enabled = true;
|
||||
}
|
||||
}));
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||
{ component: this.addExtensionsButton },
|
||||
{ component: this.dropExtensionsButton }
|
||||
]).component();
|
||||
}
|
||||
|
||||
private refreshExtensionsTable(): void {
|
||||
let extensions = this._postgresModel.config!.spec.engine.extensions;
|
||||
let extenesionFinalData: azdata.DeclarativeTableCellValue[][] = [];
|
||||
let extensionBasicData: (string | azdata.CheckBoxComponent | azdata.ImageComponent)[][] = [];
|
||||
|
||||
if (extensions) {
|
||||
extensionBasicData = extensions.map(e => {
|
||||
this.extensionNames.push(e.name);
|
||||
return [this.createDropCheckBox(e.name), e.name];
|
||||
});
|
||||
} else {
|
||||
extensionBasicData = [[this.modelView.modelBuilder.image().component(), loc.noExtensions]];
|
||||
}
|
||||
|
||||
extenesionFinalData = extensionBasicData.map(e => {
|
||||
return e.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
|
||||
this.extensionsTable.setDataValues(extenesionFinalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates checkboxes to select which extensions to drop.
|
||||
* Allows user to drop multiple extension.
|
||||
* @param name name of postgres extension the checkbox will be tied to.
|
||||
*/
|
||||
public createDropCheckBox(name: string): azdata.CheckBoxComponent {
|
||||
// Can select extensions to drop
|
||||
let checkBox = this.modelView.modelBuilder.checkBox().withProps({
|
||||
ariaLabel: loc.unloadExtensions,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
if (name === 'citus') {
|
||||
checkBox.enabled = false;
|
||||
}
|
||||
|
||||
this.disposables.push(
|
||||
checkBox.onChanged(() => {
|
||||
if (checkBox.checked) {
|
||||
this.droppedExtensions.push(name);
|
||||
this.dropExtensionsButton.focus();
|
||||
} else {
|
||||
let index = this.droppedExtensions.indexOf(name, 0);
|
||||
this.droppedExtensions.splice(index, 1);
|
||||
}
|
||||
this.dropExtensionsButton.enabled = this.droppedExtensions.length ? true : false;
|
||||
})
|
||||
);
|
||||
|
||||
// Dropping the citus extension is not supported.
|
||||
if (name === 'citus') {
|
||||
button.enabled = false;
|
||||
}
|
||||
|
||||
return button;
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls edit on postgres extensions with an updated extensions list.
|
||||
* @param name name of postgres extension to not inlcude when editing list of extensions
|
||||
*/
|
||||
public async dropExtension(name: string): Promise<void> {
|
||||
// Only allow one drop to be happening at a time
|
||||
if (this._dropExtPromise) {
|
||||
vscode.window.showErrorMessage(loc.dropMultipleExtensions);
|
||||
return this._dropExtPromise.promise;
|
||||
}
|
||||
public async dropExtension(): Promise<void> {
|
||||
this.droppedExtensions.forEach(d => {
|
||||
let index = this.extensionNames.indexOf(d, 0);
|
||||
this.extensionNames.splice(index, 1);
|
||||
});
|
||||
|
||||
this._dropExtPromise = new Deferred();
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
let index = this.extensionNames.indexOf(name, 0);
|
||||
this.extensionNames.splice(index, 1);
|
||||
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
extensions: this.extensionNames.join()
|
||||
},
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars
|
||||
);
|
||||
}
|
||||
);
|
||||
this._dropExtPromise.resolve();
|
||||
} catch (err) {
|
||||
this._dropExtPromise.reject(err);
|
||||
throw err;
|
||||
} finally {
|
||||
this._dropExtPromise = undefined;
|
||||
}
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
extensions: this.extensionNames.join()
|
||||
},
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private handleConfigUpdated(): void {
|
||||
@@ -271,6 +290,7 @@ export class PostgresExtensionsPage extends DashboardPage {
|
||||
this.extensionsLink.url = `https://www.postgresql.org/docs/${this._postgresModel.engineVersion}/external-extensions.html`;
|
||||
this.extensionNames = [];
|
||||
this.refreshExtensionsTable();
|
||||
this.addExtensionsButton.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles, iconSize } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
@@ -35,11 +35,11 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
private podStatusTable!: azdata.DeclarativeTableComponent;
|
||||
private podStatusData: PodStatusModel[] = [];
|
||||
|
||||
private readonly _azdataApi: azdataExt.IExtension;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.disposables.push(
|
||||
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
|
||||
@@ -65,13 +65,13 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
|
||||
// Properties
|
||||
this.properties = this.modelView.modelBuilder.propertiesContainer()
|
||||
.withProperties<azdata.PropertiesContainerComponentProperties>({
|
||||
.withProps({
|
||||
propertyItems: this.getProperties()
|
||||
}).component();
|
||||
|
||||
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.properties)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
.withProps({
|
||||
loading: !this._controllerModel.registrationsLastUpdated && !this._postgresModel.configLastUpdated
|
||||
}).component();
|
||||
|
||||
@@ -79,9 +79,10 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
|
||||
// Service endpoints
|
||||
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.serviceEndpoints,
|
||||
CSSStyles: titleCSS
|
||||
CSSStyles: titleCSS,
|
||||
headingLevel: 1
|
||||
}).component());
|
||||
|
||||
this.kibanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||
@@ -89,13 +90,13 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
this.grafanaLink = this.modelView.modelBuilder.hyperlink().component();
|
||||
|
||||
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withProperties<azdata.LoadingComponentProperties>(
|
||||
.withProps(
|
||||
{ loading: !this._postgresModel?.configLastUpdated }
|
||||
)
|
||||
.component();
|
||||
|
||||
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withProperties<azdata.LoadingComponentProperties>(
|
||||
.withProps(
|
||||
{ loading: !this._postgresModel?.configLastUpdated }
|
||||
)
|
||||
.component();
|
||||
@@ -105,8 +106,9 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
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().withProps({
|
||||
width: '100%',
|
||||
ariaLabel: loc.serviceEndpoints,
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.name,
|
||||
@@ -139,20 +141,24 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: [
|
||||
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
||||
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
||||
dataValues: [
|
||||
[{ value: loc.kibanaDashboard }, { value: this.kibanaLoading }, { value: loc.kibanaDashboardDescription }],
|
||||
[{ value: loc.grafanaDashboard }, { value: this.grafanaLoading }, { value: loc.grafanaDashboardDescription }]]
|
||||
}).component();
|
||||
content.addItem(endpointsTable);
|
||||
|
||||
// Server Group Nodes
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.serverGroupNodes,
|
||||
CSSStyles: titleCSS
|
||||
CSSStyles: titleCSS,
|
||||
headingLevel: 1
|
||||
}).component());
|
||||
|
||||
|
||||
|
||||
this.podStatusTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
width: '100%',
|
||||
ariaLabel: loc.serverGroupNodes,
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.name,
|
||||
@@ -185,14 +191,14 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: [this.podStatusData.map(p => [p.podName, p.type, p.status])]
|
||||
dataValues: this.createPodStatusDataValues()
|
||||
}).component();
|
||||
|
||||
|
||||
|
||||
this.serverGroupNodesLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.podStatusTable)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
.withProps({
|
||||
loading: !this._postgresModel.configLastUpdated
|
||||
}).component();
|
||||
|
||||
@@ -206,7 +212,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// Reset password
|
||||
const resetPasswordButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const resetPasswordButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.resetPassword,
|
||||
iconPath: IconPathHelper.edit
|
||||
}).component();
|
||||
@@ -217,13 +223,14 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
try {
|
||||
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
|
||||
if (password) {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{
|
||||
adminPassword: true,
|
||||
noWait: true
|
||||
},
|
||||
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azdataAdditionalEnvVars));
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azAdditionalEnvVars));
|
||||
vscode.window.showInformationMessage(loc.passwordReset);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -234,7 +241,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Delete service
|
||||
this.deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
this.deleteButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.deleteText,
|
||||
iconPath: IconPathHelper.delete
|
||||
}).component();
|
||||
@@ -251,7 +258,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token) => {
|
||||
return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
|
||||
return await this._azApi.az.postgres.arcserver.delete(this._postgresModel.info.name, this._postgresModel.controllerModel.info.namespace, this._controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
);
|
||||
await this._controllerModel.refreshTreeNode();
|
||||
@@ -272,7 +279,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Refresh
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.refresh,
|
||||
iconPath: IconPathHelper.refresh
|
||||
}).component();
|
||||
@@ -288,7 +295,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
|
||||
await Promise.all([
|
||||
this._postgresModel.refresh(),
|
||||
this._controllerModel.refresh()
|
||||
this._controllerModel.refresh(false, this._controllerModel.info.namespace)
|
||||
]);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
@@ -299,7 +306,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
}));
|
||||
|
||||
// Open in Azure portal
|
||||
const openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.openInAzurePortal,
|
||||
iconPath: IconPathHelper.openInTab
|
||||
}).component();
|
||||
@@ -345,7 +352,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
let podModels: PodStatusModel[] = [];
|
||||
const podStatus = this._postgresModel.config?.status.podsStatus;
|
||||
|
||||
podStatus?.forEach(p => {
|
||||
podStatus?.forEach((p: { conditions: any[]; name: any; role: string; }) => {
|
||||
// If a condition of the pod has a status of False, pod is not Ready
|
||||
const status = p.conditions.find(c => c.status === 'False') ? loc.notReady : loc.ready;
|
||||
|
||||
@@ -389,6 +396,15 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
return podModels;
|
||||
}
|
||||
|
||||
private createPodStatusDataValues(): azdata.DeclarativeTableCellValue[][] {
|
||||
let podDataValue: (string | azdata.Component)[][] = this.podStatusData.map(p => [p.podName, p.type, p.status]);
|
||||
return podDataValue.map(p => {
|
||||
return p.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private refreshDashboardLinks(): void {
|
||||
if (this._postgresModel.config) {
|
||||
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
|
||||
@@ -406,7 +422,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
private refreshServerNodes(): void {
|
||||
if (this._postgresModel.config) {
|
||||
this.podStatusData = this.getPodStatus();
|
||||
this.podStatusTable.data = this.podStatusData.map(p => [p.podName, p.type, p.status]);
|
||||
this.podStatusTable.setDataValues(this.createPodStatusDataValues());
|
||||
this.serverGroupNodesLoading.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { UserCancelledError } from '../../../common/api';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
@@ -37,12 +37,12 @@ export abstract class PostgresParametersPage extends DashboardPage {
|
||||
private changedComponentValues: Set<string> = new Set();
|
||||
private parameterUpdates: Map<string, string> = new Map();
|
||||
|
||||
protected readonly _azdataApi: azdataExt.IExtension;
|
||||
protected readonly _azApi: azExt.IExtension;
|
||||
|
||||
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, protected _postgresModel: PostgresModel) {
|
||||
super(modelView, dashboard);
|
||||
|
||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
||||
|
||||
this.initializeSearchBox();
|
||||
|
||||
@@ -551,8 +551,7 @@ export abstract class PostgresParametersPage extends DashboardPage {
|
||||
iconPath: IconPathHelper.information,
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
enabled: false,
|
||||
title: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
|
||||
description: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
|
||||
}).component();
|
||||
|
||||
return {
|
||||
|
||||
@@ -44,7 +44,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.properties,
|
||||
CSSStyles: { ...cssStyles.title, 'margin-bottom': '25px' }
|
||||
}).component());
|
||||
@@ -55,7 +55,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
|
||||
this.loading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.keyValueContainer.container)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
.withProps({
|
||||
loading: !this._postgresModel.configLastUpdated && !this._controllerModel.registrationsLastUpdated
|
||||
}).component();
|
||||
|
||||
@@ -65,7 +65,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
}
|
||||
|
||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.refresh,
|
||||
iconPath: IconPathHelper.refresh
|
||||
}).component();
|
||||
@@ -77,7 +77,7 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
this.loading!.loading = true;
|
||||
await Promise.all([
|
||||
this._postgresModel.refresh(),
|
||||
this._controllerModel.refresh()
|
||||
this._controllerModel.refresh(false, this._controllerModel.info.namespace)
|
||||
]);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PostgresModel } from '../../../models/postgresModel';
|
||||
|
||||
export type PodHealthModel = {
|
||||
condition: string,
|
||||
details?: azdata.Component,
|
||||
details: azdata.Component,
|
||||
lastUpdate: string
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@ export class PostgresResourceHealthPage extends DashboardPage {
|
||||
this.podConditionsContainer = this.modelView.modelBuilder.divContainer().component();
|
||||
this.podConditionsTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||
width: '100%',
|
||||
ariaLabel: loc.podConditionsTable,
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.condition,
|
||||
@@ -119,10 +120,13 @@ export class PostgresResourceHealthPage extends DashboardPage {
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
}
|
||||
],
|
||||
data: [this.coordinatorData.map(p => [p.condition, p.details, p.lastUpdate])]
|
||||
dataValues: this.createPodConditionsDataValues(this.coordinatorData)
|
||||
}).component();
|
||||
|
||||
this.podDropDown = this.modelView.modelBuilder.dropDown().withProps({ width: '150px' }).component();
|
||||
this.podDropDown = this.modelView.modelBuilder.dropDown().withProps({
|
||||
width: '150px',
|
||||
ariaLabel: loc.podsUsedDescriptionAria
|
||||
}).component();
|
||||
this.disposables.push(
|
||||
this.podDropDown.onValueChanged(() => {
|
||||
this.podConditionsTable.setFilter(this.podConditionsTableIndexes.get(String(this.podDropDown.value)));
|
||||
@@ -133,7 +137,7 @@ export class PostgresResourceHealthPage extends DashboardPage {
|
||||
this.podConditionsContainer.addItem(this.podConditionsTable);
|
||||
this.podConditionsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.podConditionsContainer)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
.withProps({
|
||||
loading: !this._postgresModel.configLastUpdated
|
||||
}).component();
|
||||
|
||||
@@ -234,11 +238,20 @@ export class PostgresResourceHealthPage extends DashboardPage {
|
||||
this.podConditionsTableIndexes.set(p.name, indexes);
|
||||
});
|
||||
|
||||
this.podConditionsTable.data = this.podsData.map(p => [p.condition, p.details, p.lastUpdate]);
|
||||
this.podConditionsTable.setDataValues(this.createPodConditionsDataValues(this.podsData));
|
||||
|
||||
return podNames;
|
||||
}
|
||||
|
||||
private createPodConditionsDataValues(podInfo: PodHealthModel[]): azdata.DeclarativeTableCellValue[][] {
|
||||
let podDataValues: (string | azdata.Component)[][] = podInfo.map(p => [p.condition, p.details, p.lastUpdate]);
|
||||
return podDataValues.map(p => {
|
||||
return p.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private findPodIssues(): string[] {
|
||||
const podStatus = this._postgresModel.config?.status.podsStatus;
|
||||
let issueCount = 0;
|
||||
|
||||
@@ -34,22 +34,22 @@ export class PostgresSupportRequestPage extends DashboardPage {
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.newSupportRequest,
|
||||
CSSStyles: { ...cssStyles.title, 'margin-bottom': '20px' }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.clickTheNewSupportRequestButton,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
|
||||
}).component());
|
||||
|
||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.supportRequestNote,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
|
||||
}).component());
|
||||
|
||||
const supportRequestButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
const supportRequestButton = this.modelView.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.support,
|
||||
label: loc.newSupportRequest,
|
||||
width: '205px'
|
||||
|
||||
@@ -37,26 +37,26 @@ export class PostgresWorkerNodeParametersPage extends PostgresParametersPage {
|
||||
}
|
||||
|
||||
protected async saveParameterEdits(engineSettings: string): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ workerEngineSettings: engineSettings },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
|
||||
protected async resetAllParameters(): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ workerEngineSettings: `''`, replaceEngineSettings: true },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
|
||||
protected async resetParameter(parameterName: string): Promise<void> {
|
||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||
await this._azApi.az.postgres.arcserver.edit(
|
||||
this._postgresModel.info.name,
|
||||
{ workerEngineSettings: parameterName + '=' },
|
||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||
this._postgresModel.controllerModel.controllerContext);
|
||||
this._postgresModel.controllerModel.info.namespace,
|
||||
this._postgresModel.controllerModel.azAdditionalEnvVars);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { cssStyles } from '../../constants';
|
||||
import { InitializingComponent } from '../components/initializingComponent';
|
||||
import { PostgresModel } from '../../models/postgresModel';
|
||||
|
||||
export const validExtensions = ['citus', 'pgaudit', 'pgautofailover', 'pg_cron', 'pg_partman', 'plv8', 'postgis', 'postgis_raster', 'postgis_sfcgal', 'postgis_tiger_geocoder', 'tdigest'];
|
||||
|
||||
export class AddPGExtensionsDialog extends InitializingComponent {
|
||||
protected modelBuilder!: azdata.ModelBuilder;
|
||||
|
||||
@@ -27,12 +29,12 @@ export class AddPGExtensionsDialog extends InitializingComponent {
|
||||
dialog.registerContent(async view => {
|
||||
this.modelBuilder = view.modelBuilder;
|
||||
|
||||
const info = this.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: loc.extensionsFunction,
|
||||
const info = this.modelBuilder.text().withProps({
|
||||
value: loc.extensionsAddFunction(validExtensions.join(', ')),
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const link = this.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
const link = this.modelBuilder.hyperlink().withProps({
|
||||
label: loc.extensionsLearnMore,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/using-extensions-in-postgresql-hyperscale-server-group',
|
||||
}).component();
|
||||
@@ -42,10 +44,18 @@ export class AddPGExtensionsDialog extends InitializingComponent {
|
||||
infoAndLink.addItem(link);
|
||||
|
||||
this.extensionsListInputBox = this.modelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
.withProps({
|
||||
value: '',
|
||||
ariaLabel: loc.extensionsAddList,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
validationErrorMessage: loc.extensionsAddErrorrMessage(validExtensions.join(','))
|
||||
}).withValidation((component) => {
|
||||
if (!component.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let newExtensions = component.value.split(',');
|
||||
return newExtensions.every(e => validExtensions.includes(e));
|
||||
}).component();
|
||||
|
||||
let formModel = this.modelBuilder.formContainer()
|
||||
@@ -56,7 +66,8 @@ export class AddPGExtensionsDialog extends InitializingComponent {
|
||||
},
|
||||
{
|
||||
component: this.extensionsListInputBox,
|
||||
title: loc.extensionsAddList
|
||||
title: loc.extensionsAddList,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
@@ -67,7 +78,7 @@ export class AddPGExtensionsDialog extends InitializingComponent {
|
||||
});
|
||||
|
||||
dialog.registerCloseValidator(async () => await this.validate());
|
||||
dialog.okButton.label = loc.addExtensions;
|
||||
dialog.okButton.label = loc.loadExtensions;
|
||||
dialog.cancelButton.label = loc.cancel;
|
||||
azdata.window.openDialog(dialog);
|
||||
return dialog;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { ControllerInfo, ResourceInfo } from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as vscode from 'vscode';
|
||||
import { Deferred } from '../../common/promise';
|
||||
@@ -13,12 +12,11 @@ import * as loc from '../../localizedConstants';
|
||||
import { ControllerModel } from '../../models/controllerModel';
|
||||
import { InitializingComponent } from '../components/initializingComponent';
|
||||
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
||||
import { getErrorMessage } from '../../common/utils';
|
||||
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
|
||||
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts, KubeClusterContext } from '../../common/kubeUtils';
|
||||
import { FilePicker } from '../components/filePicker';
|
||||
|
||||
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
||||
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel };
|
||||
|
||||
abstract class ControllerDialogBase extends InitializingComponent {
|
||||
protected _toDispose: vscode.Disposable[] = [];
|
||||
@@ -29,9 +27,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
protected kubeConfigInputBox!: FilePicker;
|
||||
protected clusterContextRadioGroup!: RadioOptionsGroup;
|
||||
protected nameInputBox!: azdata.InputBoxComponent;
|
||||
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||
protected urlInputBox!: azdata.InputBoxComponent;
|
||||
|
||||
private _kubeClusters: KubeClusterContext[] = [];
|
||||
|
||||
@@ -46,13 +41,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
component: this.namespaceInputBox,
|
||||
title: loc.namespace,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.urlInputBox,
|
||||
title: loc.controllerUrl,
|
||||
layout: {
|
||||
info: loc.controllerUrlDescription
|
||||
}
|
||||
}, {
|
||||
component: this.kubeConfigInputBox.component(),
|
||||
title: loc.controllerKubeConfig,
|
||||
@@ -68,14 +56,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
layout: {
|
||||
info: loc.controllerNameDescription
|
||||
}
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: loc.controllerUsername,
|
||||
required: true
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: loc.controllerPassword,
|
||||
required: true
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -83,16 +63,11 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
protected abstract fieldToFocusOn(): azdata.Component;
|
||||
protected readonlyFields(): azdata.Component[] { return []; }
|
||||
|
||||
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||
protected initializeFields(controllerInfo: ControllerInfo | undefined) {
|
||||
this.namespaceInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: controllerInfo?.namespace,
|
||||
}).component();
|
||||
this.urlInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: controllerInfo?.endpoint,
|
||||
placeHolder: loc.controllerUrlPlaceholder,
|
||||
}).component();
|
||||
this.kubeConfigInputBox = new FilePicker(
|
||||
this.modelBuilder,
|
||||
controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(),
|
||||
@@ -113,15 +88,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
.withProps({
|
||||
value: controllerInfo?.name
|
||||
}).component();
|
||||
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
value: controllerInfo?.username
|
||||
}).component();
|
||||
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
inputType: 'password',
|
||||
value: password
|
||||
}).component();
|
||||
}
|
||||
|
||||
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
||||
@@ -150,13 +116,13 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
this.namespaceInputBox.value = currentContext?.namespace;
|
||||
}
|
||||
|
||||
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||
public showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
|
||||
this.id = controllerInfo?.id ?? uuid();
|
||||
this.resources = controllerInfo?.resources ?? [];
|
||||
this._toDispose.push(this.dialog.cancelButton.onClick(() => this.handleCancel()));
|
||||
this.dialog.registerContent(async (view) => {
|
||||
this.modelBuilder = view.modelBuilder;
|
||||
this.initializeFields(controllerInfo, password);
|
||||
this.initializeFields(controllerInfo);
|
||||
|
||||
let formModel = this.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
@@ -192,75 +158,37 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
||||
return this.completionPromise.promise;
|
||||
}
|
||||
|
||||
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
|
||||
protected getControllerInfo(): ControllerInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
endpoint: url || undefined,
|
||||
namespace: this.namespaceInputBox.value!.trim(),
|
||||
kubeConfigFilePath: this.kubeConfigInputBox.value!,
|
||||
kubeClusterContext: this.clusterContextRadioGroup.value!,
|
||||
name: this.nameInputBox.value ?? '',
|
||||
username: this.usernameInputBox.value!,
|
||||
rememberPassword: rememberPassword,
|
||||
resources: this.resources
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectToControllerDialog extends ControllerDialogBase {
|
||||
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||
|
||||
protected fieldToFocusOn() {
|
||||
return this.namespaceInputBox;
|
||||
}
|
||||
|
||||
protected override getComponents() {
|
||||
return [
|
||||
...super.getComponents(),
|
||||
{
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
}];
|
||||
}
|
||||
|
||||
protected override initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||
super.initializeFields(controllerInfo, password);
|
||||
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||
.withProperties<azdata.CheckBoxProperties>({
|
||||
label: loc.rememberPassword,
|
||||
checked: controllerInfo?.rememberPassword
|
||||
}).component();
|
||||
}
|
||||
|
||||
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||
super(treeDataProvider, loc.connectToController);
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
if (!this.namespaceInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
||||
if (!this.namespaceInputBox.value) {
|
||||
return false;
|
||||
}
|
||||
let url = this.urlInputBox.value?.trim() || '';
|
||||
if (url) {
|
||||
// Only support https connections
|
||||
if (url.toLowerCase().startsWith('http://')) {
|
||||
url = url.replace('http', 'https');
|
||||
}
|
||||
// Append https if they didn't type it in
|
||||
if (!url.toLowerCase().startsWith('https://')) {
|
||||
url = `https://${url}`;
|
||||
}
|
||||
// Append default port if one wasn't specified
|
||||
if (!/.*:\d*$/.test(url)) {
|
||||
url = `${url}:30080`;
|
||||
}
|
||||
}
|
||||
|
||||
const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
|
||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||
const controllerInfo: ControllerInfo = this.getControllerInfo();
|
||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo);
|
||||
try {
|
||||
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
||||
await controllerModel.refresh(false);
|
||||
await controllerModel.refresh(false, this.namespaceInputBox.value);
|
||||
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
||||
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
||||
} catch (err) {
|
||||
@@ -270,74 +198,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
||||
};
|
||||
return false;
|
||||
}
|
||||
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||
this.completionPromise.resolve({ controllerModel: controllerModel });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export class PasswordToControllerDialog extends ControllerDialogBase {
|
||||
|
||||
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||
super(treeDataProvider, loc.passwordToController);
|
||||
}
|
||||
|
||||
protected fieldToFocusOn() {
|
||||
return this.passwordInputBox;
|
||||
}
|
||||
|
||||
protected override readonlyFields(): azdata.Component[] {
|
||||
return [
|
||||
this.urlInputBox,
|
||||
...this.kubeConfigInputBox.items,
|
||||
...this.clusterContextRadioGroup.items,
|
||||
this.nameInputBox,
|
||||
this.usernameInputBox
|
||||
];
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
if (!this.passwordInputBox.value) {
|
||||
return false;
|
||||
}
|
||||
const controllerInfo: ControllerInfo = this.getControllerInfo(this.urlInputBox.value!, false);
|
||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||
try {
|
||||
await azdataApi.azdata.login(
|
||||
{
|
||||
endpoint: controllerInfo.endpoint,
|
||||
namespace: controllerInfo.namespace
|
||||
},
|
||||
controllerInfo.username,
|
||||
this.passwordInputBox.value,
|
||||
{
|
||||
'KUBECONFIG': this.kubeConfigInputBox.value!,
|
||||
'KUBECTL_CONTEXT': this.clusterContextRadioGroup.value!
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
if (getErrorMessage(e).match(/Wrong username or password/i)) {
|
||||
this.dialog.message = {
|
||||
text: loc.loginFailed,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return false;
|
||||
} else {
|
||||
this.dialog.message = {
|
||||
text: loc.errorVerifyingPassword(e),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||
return true;
|
||||
}
|
||||
|
||||
public override showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
|
||||
const dialog = super.showDialog(controllerInfo);
|
||||
dialog.okButton.label = loc.ok;
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,22 +35,22 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
||||
this.modelBuilder = view.modelBuilder;
|
||||
|
||||
this.serverNameInputBox = this.modelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
.withProps({
|
||||
value: connectionProfile?.serverName,
|
||||
enabled: false
|
||||
readOnly: true
|
||||
}).component();
|
||||
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
.withProps({
|
||||
value: connectionProfile?.userName
|
||||
}).component();
|
||||
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
.withProps({
|
||||
inputType: 'password',
|
||||
value: connectionProfile?.password
|
||||
})
|
||||
.component();
|
||||
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||
.withProperties<azdata.CheckBoxProperties>({
|
||||
.withProps({
|
||||
label: loc.rememberPassword,
|
||||
checked: connectionProfile?.savePassword
|
||||
}).component();
|
||||
@@ -78,7 +78,7 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await view.initializeModel(formModel);
|
||||
this.serverNameInputBox.focus();
|
||||
this.usernameInputBox.focus();
|
||||
this.initialized = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ControllerInfo } from 'arc';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { ControllerModel } from '../../models/controllerModel';
|
||||
import { ControllerTreeNode } from './controllerTreeNode';
|
||||
@@ -18,7 +17,6 @@ const mementoToken = 'arcDataControllers.v2';
|
||||
*/
|
||||
export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNode> {
|
||||
|
||||
private _credentialsProvider = azdata.credentials.getProvider('arcControllerPasswords');
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode | undefined> = new vscode.EventEmitter<TreeNode | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<TreeNode | undefined> = this._onDidChangeTreeData.event;
|
||||
|
||||
@@ -51,14 +49,13 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
|
||||
return element;
|
||||
}
|
||||
|
||||
public async addOrUpdateController(model: ControllerModel, password: string, refreshTree = true): Promise<void> {
|
||||
public async addOrUpdateController(model: ControllerModel, refreshTree = true): Promise<void> {
|
||||
const controllerNode = this.getControllerNode(model);
|
||||
if (controllerNode) {
|
||||
controllerNode.model.info = model.info;
|
||||
} else {
|
||||
this._controllerNodes.push(new ControllerTreeNode(model, this._context, this));
|
||||
}
|
||||
await this.updatePassword(model, password);
|
||||
if (refreshTree) {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
@@ -71,22 +68,10 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
|
||||
|
||||
public async removeController(controllerNode: ControllerTreeNode): Promise<void> {
|
||||
this._controllerNodes = this._controllerNodes.filter(node => node !== controllerNode);
|
||||
await this.deletePassword(controllerNode.model.info);
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
await this.saveControllers();
|
||||
}
|
||||
|
||||
public async getPassword(info: ControllerInfo): Promise<string> {
|
||||
const provider = await this._credentialsProvider;
|
||||
const credential = await provider.readCredential(info.id);
|
||||
return credential.password;
|
||||
}
|
||||
|
||||
private async deletePassword(info: ControllerInfo): Promise<void> {
|
||||
const provider = await this._credentialsProvider;
|
||||
await provider.deleteCredential(info.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the specified node, or the entire tree if node is undefined
|
||||
* @param node The node to refresh, or undefined for the whole tree
|
||||
@@ -95,15 +80,6 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
}
|
||||
|
||||
private async updatePassword(model: ControllerModel, password: string): Promise<void> {
|
||||
const provider = await this._credentialsProvider;
|
||||
if (model.info.rememberPassword) {
|
||||
await provider.saveCredential(model.info.id, password);
|
||||
} else {
|
||||
await provider.deleteCredential(model.info.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSavedControllers(): Promise<void> {
|
||||
try {
|
||||
const controllerMementos: ControllerInfo[] = this._context.globalState.get(mementoToken) || [];
|
||||
|
||||
@@ -39,12 +39,12 @@ export class ControllerTreeNode extends TreeNode {
|
||||
|
||||
public override async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
await this.model.refresh(false);
|
||||
await this.model.refresh(false, this.model.info.namespace);
|
||||
this.updateChildren(this.model.registrations);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(loc.errorConnectingToController(err));
|
||||
try {
|
||||
await this.model.refresh(false);
|
||||
await this.model.refresh(false, this.model.info.namespace);
|
||||
this.updateChildren(this.model.registrations);
|
||||
} catch (err) {
|
||||
if (!(err instanceof UserCancelledError)) {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# Microsoft Azure Data CLI Extension for Azure Data Studio
|
||||
# Microsoft Azure 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 public preview. Other usage is not supported at this time.**
|
||||
Welcome to Microsoft Azure CLI Extension for Azure Data Studio!
|
||||
|
||||
## Overview
|
||||
|
||||
This extension adds support for the Azure Data CLI (azdata) within Azure Data Studio.
|
||||
This extension adds support for the Azure CLI (az) within Azure Data Studio.
|
||||
|
||||
See https://docs.microsoft.com/cli/azure/?view=azure-cli-latest for more information on the tool.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "azcli",
|
||||
"displayName": "%azdata.displayName%",
|
||||
"description": "%azdata.description%",
|
||||
"displayName": "%azcli.arc.displayName%",
|
||||
"description": "%azcli.arc.description%",
|
||||
"version": "0.1.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
@@ -26,104 +26,19 @@
|
||||
"configuration": [
|
||||
{
|
||||
"type": "object",
|
||||
"title": "%azdata.config.title%",
|
||||
"title": "%azcli.arc.config.title%",
|
||||
"properties": {
|
||||
"azcli.logDebugInfo": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%azdata.config.debug%"
|
||||
},
|
||||
"azcli.acceptEula": {
|
||||
"type": "string",
|
||||
"default": "prompt",
|
||||
"enum": [
|
||||
"dontPrompt",
|
||||
"prompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%azdata.acceptEula.dontPrompt.description%",
|
||||
"%azdata.acceptEula.prompt.description%"
|
||||
],
|
||||
"description": "%azdata.acceptEula.description%"
|
||||
},
|
||||
"azcli.install": {
|
||||
"type": "string",
|
||||
"default": "prompt",
|
||||
"enum": [
|
||||
"dontPrompt",
|
||||
"prompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%azdata.install.dontPrompt.description%",
|
||||
"%azdata.install.prompt.description%"
|
||||
],
|
||||
"description": "%azdata.install.description%"
|
||||
},
|
||||
"azcli.update": {
|
||||
"type": "string",
|
||||
"default": "prompt",
|
||||
"enum": [
|
||||
"dontPrompt",
|
||||
"prompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%azdata.update.dontPrompt.description%",
|
||||
"%azdata.update.prompt.description%"
|
||||
],
|
||||
"description": "%azdata.update.description%"
|
||||
},
|
||||
"azcli.requiredUpdate": {
|
||||
"type": "string",
|
||||
"default": "prompt",
|
||||
"enum": [
|
||||
"dontPrompt",
|
||||
"prompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%azdata.update.dontPrompt.description%",
|
||||
"%azdata.update.prompt.description%"
|
||||
],
|
||||
"description": "%azdata.requiredUpdate.description%"
|
||||
"description": "%azcli.arc.config.debug%"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "azcli.acceptEula",
|
||||
"title": "%azdata.acceptEula.command.name%",
|
||||
"category": "%command.category%"
|
||||
},
|
||||
{
|
||||
"command": "azcli.install",
|
||||
"title": "%azdata.install.command.name%",
|
||||
"category": "%command.category%"
|
||||
},
|
||||
{
|
||||
"command": "azcli.update",
|
||||
"title": "%azdata.update.command.name%",
|
||||
"category": "%command.category%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "azcli.acceptEula",
|
||||
"when": "!azcli.eulaAccepted"
|
||||
},
|
||||
{
|
||||
"command": "azcli.install",
|
||||
"when": "!azcli.found"
|
||||
},
|
||||
{
|
||||
"command": "azcli.update",
|
||||
"when": "azcli.found"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resourceDeploymentOptionsSources": [
|
||||
{
|
||||
"id": "arc.controller.config.profiles"
|
||||
"id": "azcli.arc.controller.config.profiles"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
{
|
||||
"azdata.displayName": "Azure CLI",
|
||||
"azdata.description": "Support for Azure CLI.",
|
||||
"azdata.config.title": "Azure CLI Configuration",
|
||||
"azdata.config.debug": "Log debug info to the output channel for all executed az commands",
|
||||
"azcli.arc.displayName": "Azure CLI",
|
||||
"azcli.arc.description": "Support for Azure CLI.",
|
||||
"azcli.arc.config.title": "Azure CLI Configuration",
|
||||
"azcli.arc.config.debug": "Log debug info to the output channel for all executed az commands",
|
||||
|
||||
"command.category": "Azure CLI",
|
||||
"azdata.acceptEula.command.name": "Accept Eula",
|
||||
"azdata.install.command.name": "Install",
|
||||
"azdata.update.command.name": "Check for Update",
|
||||
"azdata.category": "Azure CLI",
|
||||
|
||||
"azdata.acceptEula.description": "Choose how acceptance of EULA for the Azure CLI is done",
|
||||
"azdata.acceptEula.prompt.description": "The user will be prompted for acceptance of EULA for the Azure CLI",
|
||||
"azdata.acceptEula.dontPrompt.description": "The user will not be prompted for acceptance of EULA for the Azure CLI",
|
||||
|
||||
"azdata.install.description": "Choose how install of Azure CLI is done",
|
||||
"azdata.install.prompt.description": "The user will be prompted for installation of the Azure CLI",
|
||||
"azdata.install.dontPrompt.description": "The user will not be prompted for installation of the Azure CLI",
|
||||
|
||||
"azdata.update.description": "Choose whether you will be prompted when an update of the Azure CLI is available.",
|
||||
"azdata.requiredUpdate.description": "Choose whether you will be prompted when a required update of the Azure CLI is available.",
|
||||
"azdata.update.prompt.description": "The user will be prompted for update of the Azure CLI",
|
||||
"azdata.update.dontPrompt.description": "The user will not be prompted for update of the Azure CLI"
|
||||
"azcli.arc.category": "Azure CLI"
|
||||
}
|
||||
|
||||
@@ -3,200 +3,137 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as vscode from 'vscode';
|
||||
import { IAzdataTool, isEulaAccepted, MIN_AZDATA_VERSION, promptForEula } from './azdata';
|
||||
import * as azExt from 'az-ext';
|
||||
import { IAzTool } from './az';
|
||||
import Logger from './common/logger';
|
||||
import { NoAzdataError } from './common/utils';
|
||||
import * as constants from './constants';
|
||||
import { NoAzureCLIError } from './common/utils';
|
||||
import * as loc from './localizedConstants';
|
||||
import { AzdataToolService } from './services/azdataToolService';
|
||||
import { AzToolService } from './services/azToolService';
|
||||
|
||||
/**
|
||||
* Validates that :
|
||||
* - Azdata is installed
|
||||
* - The Azdata version is >= the minimum required version
|
||||
* - The Azdata CLI has been accepted
|
||||
* @param azdata The azdata tool to check
|
||||
* @param eulaAccepted Whether the Azdata CLI EULA has been accepted
|
||||
* - Az is installed
|
||||
* @param az The az tool to check
|
||||
*/
|
||||
async function validateAzdata(azdata: IAzdataTool | undefined, eulaAccepted: boolean): Promise<void> {
|
||||
throwIfNoAzdataOrEulaNotAccepted(azdata, eulaAccepted);
|
||||
await throwIfRequiredVersionMissing(azdata);
|
||||
export function validateAz(az: IAzTool | undefined) {
|
||||
throwIfNoAz(az);
|
||||
}
|
||||
|
||||
export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
|
||||
throwIfNoAzdata(azdata);
|
||||
if (!eulaAccepted) {
|
||||
Logger.log(loc.eulaNotAccepted);
|
||||
throw new Error(loc.eulaNotAccepted);
|
||||
export function throwIfNoAz(localAz: IAzTool | undefined): asserts localAz {
|
||||
if (!localAz) {
|
||||
Logger.log(loc.noAzureCLI);
|
||||
throw new NoAzureCLIError();
|
||||
}
|
||||
}
|
||||
|
||||
export async function throwIfRequiredVersionMissing(azdata: IAzdataTool): Promise<void> {
|
||||
const currentVersion = await azdata.getSemVersion();
|
||||
if (currentVersion.compare(MIN_AZDATA_VERSION) < 0) {
|
||||
throw new Error(loc.missingRequiredVersion(MIN_AZDATA_VERSION.raw));
|
||||
}
|
||||
}
|
||||
|
||||
export function throwIfNoAzdata(localAzdata: IAzdataTool | undefined): asserts localAzdata {
|
||||
if (!localAzdata) {
|
||||
Logger.log(loc.noAzdata);
|
||||
throw new NoAzdataError();
|
||||
}
|
||||
}
|
||||
|
||||
export function getExtensionApi(memento: vscode.Memento, azdataToolService: AzdataToolService, localAzdataDiscovered: Promise<IAzdataTool | undefined>): azdataExt.IExtension {
|
||||
export function getExtensionApi(azToolService: AzToolService): azExt.IExtension {
|
||||
return {
|
||||
isEulaAccepted: async () => {
|
||||
throwIfNoAzdata(await localAzdataDiscovered); // ensure that we have discovered Azdata
|
||||
return !!memento.get<boolean>(constants.eulaAccepted);
|
||||
},
|
||||
promptForEula: async (requireUserAction: boolean = true): Promise<boolean> => {
|
||||
await localAzdataDiscovered;
|
||||
return promptForEula(memento, true /* userRequested */, requireUserAction);
|
||||
},
|
||||
azdata: getAzdataApi(localAzdataDiscovered, azdataToolService, memento)
|
||||
az: getAzApi(azToolService)
|
||||
};
|
||||
}
|
||||
|
||||
export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefined>, azdataToolService: AzdataToolService, memento: vscode.Memento): azdataExt.IAzdataApi {
|
||||
export function getAzApi(azToolService: AzToolService): azExt.IAzApi {
|
||||
return {
|
||||
arc: {
|
||||
arcdata: {
|
||||
dc: {
|
||||
create: async (
|
||||
namespace: string,
|
||||
name: string,
|
||||
connectivityMode: string,
|
||||
resourceGroup: string,
|
||||
location: string,
|
||||
subscription: string,
|
||||
profileName?: string,
|
||||
storageClass?: string,
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars, azdataContext);
|
||||
},
|
||||
endpoint: {
|
||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.dc.endpoint.list(additionalEnvVars, azdataContext);
|
||||
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.arcdata.dc.endpoint.list(namespace, additionalEnvVars);
|
||||
}
|
||||
},
|
||||
config: {
|
||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.dc.config.list(additionalEnvVars, azdataContext);
|
||||
list: async (additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.arcdata.dc.config.list(additionalEnvVars);
|
||||
},
|
||||
show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.dc.config.show(additionalEnvVars, azdataContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
postgres: {
|
||||
server: {
|
||||
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.postgres.server.delete(name, additionalEnvVars, azdataContext);
|
||||
},
|
||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.postgres.server.list(additionalEnvVars, azdataContext);
|
||||
},
|
||||
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.postgres.server.show(name, additionalEnvVars, azdataContext);
|
||||
},
|
||||
edit: async (
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean;
|
||||
coresLimit?: string;
|
||||
coresRequest?: string;
|
||||
coordinatorEngineSettings?: string;
|
||||
engineSettings?: string;
|
||||
extensions?: string;
|
||||
memoryLimit?: string;
|
||||
memoryRequest?: string;
|
||||
noWait?: boolean;
|
||||
port?: number;
|
||||
replaceEngineSettings?: boolean;
|
||||
workerEngineSettings?: string;
|
||||
workers?: number;
|
||||
},
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.postgres.server.edit(name, args, additionalEnvVars, azdataContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
mi: {
|
||||
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.sql.mi.delete(name, additionalEnvVars, azdataContext);
|
||||
},
|
||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.sql.mi.list(additionalEnvVars, azdataContext);
|
||||
},
|
||||
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.sql.mi.show(name, additionalEnvVars, azdataContext);
|
||||
},
|
||||
edit: async (
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string;
|
||||
coresRequest?: string;
|
||||
memoryLimit?: string;
|
||||
memoryRequest?: string;
|
||||
noWait?: boolean;
|
||||
},
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
azdataContext?: string
|
||||
) => {
|
||||
await localAzdataDiscovered;
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.arc.sql.mi.edit(name, args, additionalEnvVars, azdataContext);
|
||||
show: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.arcdata.dc.config.show(namespace, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getPath: async () => {
|
||||
await localAzdataDiscovered;
|
||||
throwIfNoAzdata(azdataToolService.localAzdata);
|
||||
return azdataToolService.localAzdata.getPath();
|
||||
postgres: {
|
||||
arcserver: {
|
||||
delete: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.postgres.arcserver.delete(name, namespace, additionalEnvVars);
|
||||
},
|
||||
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.postgres.arcserver.list(namespace, additionalEnvVars);
|
||||
},
|
||||
show: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.postgres.arcserver.show(name, namespace, additionalEnvVars);
|
||||
},
|
||||
edit: async (
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean;
|
||||
coresLimit?: string;
|
||||
coresRequest?: string;
|
||||
coordinatorEngineSettings?: string;
|
||||
engineSettings?: string;
|
||||
extensions?: string;
|
||||
memoryLimit?: string;
|
||||
memoryRequest?: string;
|
||||
noWait?: boolean;
|
||||
port?: number;
|
||||
replaceEngineSettings?: boolean;
|
||||
workerEngineSettings?: string;
|
||||
workers?: number;
|
||||
},
|
||||
namespace: string,
|
||||
additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.postgres.arcserver.edit(name, args, namespace, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
},
|
||||
login: async (endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||
return azdataToolService.localAzdata!.login(endpointOrNamespace, username, password, additionalEnvVars, azdataContext);
|
||||
sql: {
|
||||
miarc: {
|
||||
delete: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.sql.miarc.delete(name, namespace, additionalEnvVars);
|
||||
},
|
||||
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.sql.miarc.list(namespace, additionalEnvVars);
|
||||
},
|
||||
show: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.sql.miarc.show(name, namespace, additionalEnvVars);
|
||||
},
|
||||
edit: async (
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string;
|
||||
coresRequest?: string;
|
||||
memoryLimit?: string;
|
||||
memoryRequest?: string;
|
||||
noWait?: boolean;
|
||||
},
|
||||
namespace: string,
|
||||
additionalEnvVars?: azExt.AdditionalEnvVars
|
||||
) => {
|
||||
validateAz(azToolService.localAz);
|
||||
return azToolService.localAz!.sql.miarc.edit(name, args, namespace, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
},
|
||||
getPath: async () => {
|
||||
throwIfNoAz(azToolService.localAz);
|
||||
return azToolService.localAz.getPath();
|
||||
},
|
||||
getSemVersion: async () => {
|
||||
await localAzdataDiscovered;
|
||||
throwIfNoAzdata(azdataToolService.localAzdata);
|
||||
return azdataToolService.localAzdata.getSemVersion();
|
||||
throwIfNoAz(azToolService.localAz);
|
||||
return azToolService.localAz.getSemVersion();
|
||||
},
|
||||
version: async () => {
|
||||
await localAzdataDiscovered;
|
||||
throwIfNoAzdata(azdataToolService.localAzdata);
|
||||
return azdataToolService.localAzdata.version();
|
||||
throwIfNoAz(azToolService.localAz);
|
||||
return azToolService.localAz.version();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
320
extensions/azcli/src/az.ts
Normal file
320
extensions/azcli/src/az.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azExt from 'az-ext';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import { executeCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
||||
import Logger from './common/logger';
|
||||
import { NoAzureCLIError, searchForCmd } from './common/utils';
|
||||
import { azConfigSection, azFound, debugConfigKey, latestAzArcExtensionVersion } from './constants';
|
||||
import * as loc from './localizedConstants';
|
||||
|
||||
/**
|
||||
* The latest Azure CLI arcdata extension version for this extension to function properly
|
||||
*/
|
||||
export const LATEST_AZ_ARC_EXTENSION_VERSION = new SemVer(latestAzArcExtensionVersion);
|
||||
|
||||
export const enum AzDeployOption {
|
||||
dontPrompt = 'dontPrompt',
|
||||
prompt = 'prompt'
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for an object to interact with the az tool installed on the box.
|
||||
*/
|
||||
export interface IAzTool extends azExt.IAzApi {
|
||||
/**
|
||||
* Executes az with the specified arguments (e.g. --version) and returns the result
|
||||
* @param args The args to pass to az
|
||||
* @param parseResult A function used to parse out the raw result into the desired shape
|
||||
*/
|
||||
executeCommand<R>(args: string[], additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<R>>
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to interact with the az tool installed on the box.
|
||||
*/
|
||||
export class AzTool implements azExt.IAzApi {
|
||||
|
||||
private _semVersion: SemVer;
|
||||
|
||||
constructor(private _path: string, version: string) {
|
||||
this._semVersion = new SemVer(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* The semVersion corresponding to this installation of az. version() method should have been run
|
||||
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||
* Az has gotten reinstalled in the background after this IAzApi object was constructed.
|
||||
*/
|
||||
public async getSemVersion(): Promise<SemVer> {
|
||||
return this._semVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the path where az tool is installed
|
||||
*/
|
||||
public async getPath(): Promise<string> {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
public arcdata = {
|
||||
dc: {
|
||||
endpoint: {
|
||||
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcEndpointListResult[]>> => {
|
||||
return this.executeCommand<azExt.DcEndpointListResult[]>(['arcdata', 'dc', 'endpoint', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
}
|
||||
},
|
||||
config: {
|
||||
list: (additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcConfigListResult[]>> => {
|
||||
return this.executeCommand<azExt.DcConfigListResult[]>(['arcdata', 'dc', 'config', 'list'], additionalEnvVars);
|
||||
},
|
||||
show: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcConfigShowResult>> => {
|
||||
return this.executeCommand<azExt.DcConfigShowResult>(['arcdata', 'dc', 'config', 'show', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public postgres = {
|
||||
arcserver: {
|
||||
delete: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
|
||||
return this.executeCommand<void>(['postgres', 'arc-server', 'delete', '-n', name, '--k8s-namespace', namespace, '--force', '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.PostgresServerListResult[]>> => {
|
||||
return this.executeCommand<azExt.PostgresServerListResult[]>(['postgres', 'arc-server', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
show: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.PostgresServerShowResult>> => {
|
||||
return this.executeCommand<azExt.PostgresServerShowResult>(['postgres', 'arc-server', 'show', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean,
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
coordinatorEngineSettings?: string,
|
||||
engineSettings?: string,
|
||||
extensions?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
port?: number,
|
||||
replaceEngineSettings?: boolean,
|
||||
workerEngineSettings?: string,
|
||||
workers?: number
|
||||
},
|
||||
namespace: string,
|
||||
additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
|
||||
const argsArray = ['postgres', 'arc-server', 'edit', '-n', name, '--k8s-namespace', namespace, '--use-k8s'];
|
||||
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
|
||||
if (args.coordinatorEngineSettings) { argsArray.push('--coordinator-settings', args.coordinatorEngineSettings); }
|
||||
if (args.engineSettings) { argsArray.push('--engine-settings', args.engineSettings); }
|
||||
if (args.extensions) { argsArray.push('--extensions', args.extensions); }
|
||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||
if (args.port) { argsArray.push('--port', args.port.toString()); }
|
||||
if (args.replaceEngineSettings) { argsArray.push('--replace-settings'); }
|
||||
if (args.workerEngineSettings) { argsArray.push('--worker-settings', args.workerEngineSettings); }
|
||||
if (args.workers !== undefined) { argsArray.push('--workers', args.workers.toString()); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public sql = {
|
||||
miarc: {
|
||||
delete: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
|
||||
return this.executeCommand<void>(['sql', 'mi-arc', 'delete', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.SqlMiListResult[]>> => {
|
||||
return this.executeCommand<azExt.SqlMiListResult[]>(['sql', 'mi-arc', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
show: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.SqlMiShowResult>> => {
|
||||
return this.executeCommand<azExt.SqlMiShowResult>(['sql', 'mi-arc', 'show', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
},
|
||||
namespace: string,
|
||||
additionalEnvVars?: azExt.AdditionalEnvVars
|
||||
): Promise<azExt.AzOutput<void>> => {
|
||||
const argsArray = ['sql', 'mi-arc', 'edit', '-n', name, '--k8s-namespace', namespace, '--use-k8s'];
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
|
||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the output of running '--version' command on the az tool.
|
||||
* It also updates the cachedVersion property based on the return value from the tool.
|
||||
*/
|
||||
public async version(): Promise<azExt.AzOutput<string>> {
|
||||
const output = await executeAzCommand(`"${this._path}"`, ['--version']);
|
||||
this._semVersion = new SemVer(parseVersion(output.stdout));
|
||||
return {
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr.split(os.EOL)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified az command.
|
||||
* @param args The args to pass to az
|
||||
* @param additionalEnvVars Additional environment variables to set for this execution
|
||||
*/
|
||||
public async executeCommand<R>(args: string[], additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<R>> {
|
||||
try {
|
||||
const result = await executeAzCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars);
|
||||
|
||||
let stdout = <unknown>result.stdout;
|
||||
let stderr = <unknown>result.stderr;
|
||||
|
||||
try {
|
||||
// Automatically try parsing the JSON. This is expected to fail for some az commands such as resource delete.
|
||||
stdout = JSON.parse(result.stdout);
|
||||
} catch (err) {
|
||||
// If the output was not pure JSON, catch the error and log it here.
|
||||
Logger.log(loc.azOutputParseErrorCaught(args.concat(['--output', 'json']).toString()));
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: <R>stdout,
|
||||
stderr: <string[]>stderr
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof ExitCodeError) {
|
||||
try {
|
||||
await fs.promises.access(this._path);
|
||||
//this.path exists
|
||||
} catch (e) {
|
||||
// this.path does not exist
|
||||
await vscode.commands.executeCommand('setContext', azFound, false);
|
||||
throw new NoAzureCLIError();
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the existing installation of Azure CLI, or throws an error if it can't find it
|
||||
* or encountered an unexpected error.
|
||||
* The promise is rejected when Azure CLI is not found.
|
||||
*/
|
||||
export async function findAz(): Promise<IAzTool> {
|
||||
Logger.log(loc.searchingForAz);
|
||||
try {
|
||||
const az = await findSpecificAz();
|
||||
Logger.log(loc.foundExistingAz(await az.getPath(), (await az.getSemVersion()).raw));
|
||||
return az;
|
||||
} catch (err) {
|
||||
Logger.log(loc.noAzureCLI);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out the Azure CLI version from the raw az version output
|
||||
* @param raw The raw version output from az --version
|
||||
*/
|
||||
function parseVersion(raw: string): string {
|
||||
// Currently the version is a multi-line string that contains other version information such
|
||||
// as the Python installation, with the first line holding the version of az itself.
|
||||
//
|
||||
// The output of az --version looks like:
|
||||
// azure-cli 2.26.1
|
||||
// ...
|
||||
const start = raw.search('azure-cli');
|
||||
const end = raw.search('core');
|
||||
raw = raw.slice(start, end).replace('azure-cli', '');
|
||||
return raw.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out the arcdata extension version from the raw az version output
|
||||
* @param raw The raw version output from az --version
|
||||
*/
|
||||
function parseArcExtensionVersion(raw: string): string {
|
||||
// Currently the version is a multi-line string that contains other version information such
|
||||
// as the Python installation and any extensions.
|
||||
//
|
||||
// The output of az --version looks like:
|
||||
// azure-cli 2.26.1
|
||||
// ...
|
||||
// Extensions:
|
||||
// arcdata 1.0.0
|
||||
// connectedk8s 1.1.5
|
||||
// ...
|
||||
const start = raw.search('arcdata');
|
||||
if (start === -1) {
|
||||
// Commented the install/update prompts out until DoNotAskAgain is implemented
|
||||
//throw new AzureCLIArcExtError();
|
||||
} else {
|
||||
raw = raw.slice(start + 7);
|
||||
raw = raw.split(os.EOL)[0].trim();
|
||||
}
|
||||
return raw.trim();
|
||||
}
|
||||
|
||||
async function executeAzCommand(command: string, args: string[], additionalEnvVars: azExt.AdditionalEnvVars = {}): Promise<ProcessOutput> {
|
||||
const debug = vscode.workspace.getConfiguration(azConfigSection).get(debugConfigKey);
|
||||
if (debug) {
|
||||
args.push('--debug');
|
||||
}
|
||||
return executeCommand(command, args, additionalEnvVars);
|
||||
}
|
||||
|
||||
// Commented the install/update prompts out until DoNotAskAgain is implemented
|
||||
// async function setConfig(key: string, value: string): Promise<void> {
|
||||
// const config = vscode.workspace.getConfiguration(azConfigSection);
|
||||
// await config.update(key, value, vscode.ConfigurationTarget.Global);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Find user's local Azure CLI. Execute az --version and parse out the version number.
|
||||
* If an update is needed, prompt the user to update via link. Return the AzTool.
|
||||
* Currently commented out because Don't Prompt Again is not properly implemented.
|
||||
*/
|
||||
async function findSpecificAz(): Promise<IAzTool> {
|
||||
const path = await ((process.platform === 'win32') ? searchForCmd('az.cmd') : searchForCmd('az'));
|
||||
const versionOutput = await executeAzCommand(`"${path}"`, ['--version']);
|
||||
const version = parseArcExtensionVersion(versionOutput.stdout);
|
||||
const semVersion = new SemVer(version);
|
||||
//let response: string | undefined;
|
||||
|
||||
if (LATEST_AZ_ARC_EXTENSION_VERSION.compare(semVersion) === 1) {
|
||||
// If there is a greater version of az arc extension available, prompt to update
|
||||
// Commented the install/update prompts out until DoNotAskAgain is implemented
|
||||
// const responses = [loc.askLater, loc.doNotAskAgain];
|
||||
// response = await vscode.window.showInformationMessage(loc.requiredArcDataVersionNotAvailable(latestAzArcExtensionVersion, version), ...responses);
|
||||
// if (response === loc.doNotAskAgain) {
|
||||
// await setConfig(azRequiredUpdateKey, AzDeployOption.dontPrompt);
|
||||
// }
|
||||
} else if (LATEST_AZ_ARC_EXTENSION_VERSION.compare(semVersion) === -1) {
|
||||
// Current version should not be greater than latest version
|
||||
// Commented the install/update prompts out until DoNotAskAgain is implemented
|
||||
// vscode.window.showErrorMessage(loc.unsupportedArcDataVersion(latestAzArcExtensionVersion, version));
|
||||
}
|
||||
return new AzTool(path, version);
|
||||
}
|
||||
@@ -1,712 +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 azdataExt from 'azdata-ext';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataReleaseInfo';
|
||||
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
||||
import { HttpClient } from './common/httpClient';
|
||||
import Logger from './common/logger';
|
||||
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
|
||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, azdatarequiredUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||
import * as loc from './localizedConstants';
|
||||
|
||||
/**
|
||||
* The minimum required azdata CLI version for this extension to function properly
|
||||
*/
|
||||
export const MIN_AZDATA_VERSION = new SemVer('20.3.4');
|
||||
|
||||
export const enum AzdataDeployOption {
|
||||
dontPrompt = 'dontPrompt',
|
||||
prompt = 'prompt'
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for an object to interact with the azdata tool installed on the box.
|
||||
*/
|
||||
export interface IAzdataTool extends azdataExt.IAzdataApi {
|
||||
/**
|
||||
* Executes azdata with the specified arguments (e.g. --version) and returns the result
|
||||
* @param args The args to pass to azdata
|
||||
* @param parseResult A function used to parse out the raw result into the desired shape
|
||||
*/
|
||||
executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>>
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to interact with the azdata tool installed on the box.
|
||||
*/
|
||||
export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
|
||||
private _semVersion: SemVer;
|
||||
|
||||
constructor(private _path: string, version: string) {
|
||||
this._semVersion = new SemVer(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* The semVersion corresponding to this installation of azdata. version() method should have been run
|
||||
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
|
||||
*/
|
||||
public async getSemVersion(): Promise<SemVer> {
|
||||
return this._semVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the path where azdata tool is installed
|
||||
*/
|
||||
public async getPath(): Promise<string> {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
public arc = {
|
||||
dc: {
|
||||
create: (
|
||||
namespace: string,
|
||||
name: string,
|
||||
connectivityMode: string,
|
||||
resourceGroup: string,
|
||||
location: string,
|
||||
subscription: string,
|
||||
profileName?: string,
|
||||
storageClass?: string,
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const args = ['arc', 'dc', 'create',
|
||||
'--namespace', namespace,
|
||||
'--name', name,
|
||||
'--connectivity-mode', connectivityMode,
|
||||
'--resource-group', resourceGroup,
|
||||
'--location', location,
|
||||
'--subscription', subscription];
|
||||
if (profileName) {
|
||||
args.push('--profile-name', profileName);
|
||||
}
|
||||
if (storageClass) {
|
||||
args.push('--storage-class', storageClass);
|
||||
}
|
||||
return this.executeCommand<void>(args, additionalEnvVars, azdataContext);
|
||||
},
|
||||
endpoint: {
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, azdataContext);
|
||||
}
|
||||
},
|
||||
config: {
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, azdataContext);
|
||||
},
|
||||
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, azdataContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
postgres: {
|
||||
server: {
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, azdataContext);
|
||||
},
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, azdataContext);
|
||||
},
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars, azdataContext);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean,
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
coordinatorEngineSettings?: string,
|
||||
engineSettings?: string,
|
||||
extensions?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
port?: number,
|
||||
replaceEngineSettings?: boolean,
|
||||
workerEngineSettings?: string,
|
||||
workers?: number
|
||||
},
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
|
||||
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
|
||||
if (args.coordinatorEngineSettings) { argsArray.push('--coordinator-engine-settings', args.coordinatorEngineSettings); }
|
||||
if (args.engineSettings) { argsArray.push('--engine-settings', args.engineSettings); }
|
||||
if (args.extensions) { argsArray.push('--extensions', args.extensions); }
|
||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||
if (args.port) { argsArray.push('--port', args.port.toString()); }
|
||||
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
|
||||
if (args.workerEngineSettings) { argsArray.push('--worker-engine-settings', args.workerEngineSettings); }
|
||||
if (args.workers !== undefined) { argsArray.push('--workers', args.workers.toString()); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars, azdataContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
mi: {
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, azdataContext);
|
||||
},
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, azdataContext);
|
||||
},
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars, azdataContext);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
},
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars
|
||||
): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
|
||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public async login(endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
|
||||
const args = ['login', '-u', username];
|
||||
if (endpointOrNamespace.endpoint) {
|
||||
args.push('-e', endpointOrNamespace.endpoint);
|
||||
} else if (endpointOrNamespace.namespace) {
|
||||
args.push('--namespace', endpointOrNamespace.namespace);
|
||||
} else {
|
||||
throw new Error(loc.endpointOrNamespaceRequired);
|
||||
}
|
||||
return this.executeCommand<void>(args, Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }), azdataContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output of running '--version' command on the azdata tool.
|
||||
* It also updates the cachedVersion property based on the return value from the tool.
|
||||
*/
|
||||
public async version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||
const output = await executeAzdataCommand(`"${this._path}"`, ['--version']);
|
||||
this._semVersion = new SemVer(parseVersion(output.stdout));
|
||||
return {
|
||||
logs: [],
|
||||
stdout: output.stdout.split(os.EOL),
|
||||
stderr: output.stderr.split(os.EOL),
|
||||
result: output.stdout
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified azdata command.
|
||||
* @param args The args to pass to azdata
|
||||
* @param additionalEnvVars Additional environment variables to set for this execution
|
||||
*/
|
||||
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>> {
|
||||
try {
|
||||
if (azdataContext) {
|
||||
args = args.concat('--controller-context', azdataContext);
|
||||
}
|
||||
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
||||
return {
|
||||
logs: <string[]>output.log,
|
||||
stdout: <string[]>output.stdout,
|
||||
stderr: <string[]>output.stderr,
|
||||
result: <R>output.result
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof ExitCodeError) {
|
||||
try {
|
||||
// For azdata internal errors the output is JSON and so we need to do some extra parsing here
|
||||
// to get the correct stderr out. The actual value we get is something like
|
||||
// ERROR: { stderr: '...' }
|
||||
// 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;
|
||||
} catch {
|
||||
// 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.
|
||||
try {
|
||||
await fs.promises.access(this._path);
|
||||
//this.path exists
|
||||
} catch (e) {
|
||||
// this.path does not exist
|
||||
await vscode.commands.executeCommand('setContext', azdataFound, false);
|
||||
throw new NoAzdataError();
|
||||
}
|
||||
throw err; // rethrow the original error
|
||||
}
|
||||
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type AzdataDarwinPackageVersionInfo = {
|
||||
versions: {
|
||||
stable: string,
|
||||
devel: string,
|
||||
head: string,
|
||||
bottle: boolean
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the existing installation of azdata, or throws an error if it couldn't find it
|
||||
* or encountered an unexpected error.
|
||||
* The promise is rejected when Azdata is not found.
|
||||
*/
|
||||
export async function findAzdata(): Promise<IAzdataTool> {
|
||||
Logger.log(loc.searchingForAzdata);
|
||||
try {
|
||||
const azdata = await findSpecificAzdata();
|
||||
await vscode.commands.executeCommand('setContext', azdataFound, true); // save a context key that azdata was found so that command for installing azdata is no longer available in commandPalette and that for updating it is.
|
||||
Logger.log(loc.foundExistingAzdata(await azdata.getPath(), (await azdata.getSemVersion()).raw));
|
||||
return azdata;
|
||||
} catch (err) {
|
||||
Logger.log(loc.couldNotFindAzdata(err));
|
||||
Logger.log(loc.noAzdata);
|
||||
await vscode.commands.executeCommand('setContext', azdataFound, false);// save a context key that azdata was not found so that command for installing azdata is available in commandPalette and that for updating it is no longer available.
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* runs the commands to install azdata, downloading the installation package if needed
|
||||
*/
|
||||
export async function installAzdata(): Promise<void> {
|
||||
Logger.show();
|
||||
Logger.log(loc.installingAzdata);
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.installingAzdata,
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await downloadAndInstallAzdataWin32();
|
||||
break;
|
||||
case 'darwin':
|
||||
await installAzdataDarwin();
|
||||
break;
|
||||
case 'linux':
|
||||
await installAzdataLinux();
|
||||
break;
|
||||
default:
|
||||
throw new Error(loc.platformUnsupported(process.platform));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the azdata using os appropriate method
|
||||
*/
|
||||
async function updateAzdata(version: string): Promise<boolean> {
|
||||
try {
|
||||
Logger.show();
|
||||
Logger.log(loc.updatingAzdata);
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingAzdata,
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await downloadAndInstallAzdataWin32();
|
||||
break;
|
||||
case 'darwin':
|
||||
await updateAzdataDarwin();
|
||||
break;
|
||||
case 'linux':
|
||||
await installAzdataLinux();
|
||||
break;
|
||||
default:
|
||||
throw new Error(loc.platformUnsupported(process.platform));
|
||||
}
|
||||
}
|
||||
);
|
||||
vscode.window.showInformationMessage(loc.azdataUpdated(version));
|
||||
Logger.log(loc.azdataUpdated(version));
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
|
||||
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
|
||||
vscode.window.showWarningMessage(loc.updateError(err));
|
||||
Logger.log(loc.updateError(err));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether azdata is installed - and if it is not then invokes the process of azdata installation.
|
||||
* @param userRequested true means that this operation by was requested by a user by executing an ads command.
|
||||
*/
|
||||
export async function checkAndInstallAzdata(userRequested: boolean = false): Promise<IAzdataTool | undefined> {
|
||||
try {
|
||||
return await findAzdata(); // find currently installed Azdata
|
||||
} catch (err) {
|
||||
// Calls will be made to handle azdata not being installed if user declines to install on the prompt
|
||||
if (await promptToInstallAzdata(userRequested)) {
|
||||
return await findAzdata();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a newer version of azdata is available - and if it is then invokes the process of azdata update.
|
||||
* @param currentAzdata The current version of azdata to check against
|
||||
* @param userRequested true means that this operation by was requested by a user by executing an ads command.
|
||||
* returns true if update was done and false otherwise.
|
||||
*/
|
||||
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
|
||||
if (currentAzdata !== undefined) {
|
||||
const newSemVersion = await discoverLatestAvailableAzdataVersion();
|
||||
const currentSemVersion = await currentAzdata.getSemVersion();
|
||||
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, currentSemVersion.raw));
|
||||
if (MIN_AZDATA_VERSION.compare(currentSemVersion) === 1) {
|
||||
if (newSemVersion.compare(MIN_AZDATA_VERSION) >= 0) {
|
||||
return await promptToUpdateAzdata(newSemVersion.raw, userRequested, true);
|
||||
} else {
|
||||
// This should never happen - it means that the currently available version to download
|
||||
// is < the version we require. If this was to happen it'd imply something is wrong with
|
||||
// the version JSON or the minimum required version.
|
||||
// Regardless, there's nothing we can do and so we just bail out at this point and tell the user
|
||||
// they have to install it manually (hopefully it's available and wasn't a publishing mistake)
|
||||
vscode.window.showInformationMessage(loc.requiredVersionNotAvailable(MIN_AZDATA_VERSION.raw, newSemVersion.raw));
|
||||
Logger.log(loc.requiredVersionNotAvailable(newSemVersion.raw, currentSemVersion.raw));
|
||||
}
|
||||
}
|
||||
else if (newSemVersion.compare(currentSemVersion) === 1) {
|
||||
return await promptToUpdateAzdata(newSemVersion.raw, userRequested);
|
||||
} else {
|
||||
Logger.log(loc.currentlyInstalledVersionIsLatest((await currentAzdata.getSemVersion()).raw));
|
||||
}
|
||||
} else {
|
||||
Logger.log(loc.updateCheckSkipped);
|
||||
Logger.log(loc.noAzdata);
|
||||
await vscode.commands.executeCommand('setContext', azdataFound, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* prompt user to install Azdata.
|
||||
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
|
||||
* returns true if installation was done and false otherwise.
|
||||
*/
|
||||
async function promptToInstallAzdata(userRequested: boolean = false): Promise<boolean> {
|
||||
let response: string | undefined = loc.yes;
|
||||
const config = <AzdataDeployOption>getConfig(azdataInstallKey);
|
||||
if (userRequested) {
|
||||
Logger.show();
|
||||
Logger.log(loc.userRequestedInstall);
|
||||
}
|
||||
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
|
||||
Logger.log(loc.skipInstall(config));
|
||||
return false;
|
||||
}
|
||||
const responses = userRequested
|
||||
? [loc.yes, loc.no]
|
||||
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
||||
if (config === AzdataDeployOption.prompt) {
|
||||
Logger.log(loc.promptForAzdataInstallLog);
|
||||
response = await vscode.window.showErrorMessage(loc.promptForAzdataInstall, ...responses);
|
||||
Logger.log(loc.userResponseToInstallPrompt(response));
|
||||
}
|
||||
if (response === loc.doNotAskAgain) {
|
||||
await setConfig(azdataInstallKey, AzdataDeployOption.dontPrompt);
|
||||
} else if (response === loc.yes) {
|
||||
try {
|
||||
await installAzdata();
|
||||
vscode.window.showInformationMessage(loc.azdataInstalled);
|
||||
Logger.log(loc.azdataInstalled);
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
|
||||
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
|
||||
vscode.window.showWarningMessage(loc.installError(err));
|
||||
Logger.log(loc.installError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* prompt user to update Azdata.
|
||||
* @param newVersion - provides the new version that the user will be prompted to update to
|
||||
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
|
||||
* returns true if update was done and false otherwise.
|
||||
* @param required - Whether this update is required. If true then we will always show the prompt and warn the user if they decline it
|
||||
*/
|
||||
async function promptToUpdateAzdata(newVersion: string, userRequested: boolean = false, required = false): Promise<boolean> {
|
||||
if (required) {
|
||||
let response: string | undefined = loc.yes;
|
||||
const config = <AzdataDeployOption>getConfig(azdatarequiredUpdateKey);
|
||||
if (userRequested) {
|
||||
Logger.show();
|
||||
Logger.log(loc.userRequestedUpdate);
|
||||
}
|
||||
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
|
||||
Logger.log(loc.skipRequiredUpdate(config));
|
||||
return false;
|
||||
}
|
||||
const responses = userRequested
|
||||
? [loc.yes, loc.no]
|
||||
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
||||
Logger.log(loc.promptForRequiredAzdataUpdateLog(MIN_AZDATA_VERSION.raw, newVersion));
|
||||
response = await vscode.window.showInformationMessage(loc.promptForRequiredAzdataUpdate(MIN_AZDATA_VERSION.raw, newVersion), ...responses);
|
||||
Logger.log(loc.userResponseToUpdatePrompt(response));
|
||||
if (response === loc.doNotAskAgain) {
|
||||
await setConfig(azdatarequiredUpdateKey, AzdataDeployOption.dontPrompt);
|
||||
} else if (response === loc.yes) {
|
||||
return updateAzdata(newVersion);
|
||||
} else {
|
||||
vscode.window.showWarningMessage(loc.missingRequiredVersion(MIN_AZDATA_VERSION.raw));
|
||||
}
|
||||
} else {
|
||||
let response: string | undefined = loc.yes;
|
||||
const config = <AzdataDeployOption>getConfig(azdataUpdateKey);
|
||||
if (userRequested) {
|
||||
Logger.show();
|
||||
Logger.log(loc.userRequestedUpdate);
|
||||
}
|
||||
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
|
||||
Logger.log(loc.skipUpdate(config));
|
||||
return false;
|
||||
}
|
||||
const responses = userRequested
|
||||
? [loc.yes, loc.no]
|
||||
: [loc.yes, loc.askLater, loc.doNotAskAgain];
|
||||
if (config === AzdataDeployOption.prompt) {
|
||||
Logger.log(loc.promptForAzdataUpdateLog(newVersion));
|
||||
response = await vscode.window.showInformationMessage(loc.promptForAzdataUpdate(newVersion), ...responses);
|
||||
Logger.log(loc.userResponseToUpdatePrompt(response));
|
||||
}
|
||||
if (response === loc.doNotAskAgain) {
|
||||
await setConfig(azdataUpdateKey, AzdataDeployOption.dontPrompt);
|
||||
} else if (response === loc.yes) {
|
||||
return updateAzdata(newVersion);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if Eula has been accepted.
|
||||
*
|
||||
* @param memento The memento that stores the eulaAccepted state
|
||||
*/
|
||||
export function isEulaAccepted(memento: vscode.Memento): boolean {
|
||||
return !!memento.get<boolean>(eulaAccepted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
|
||||
* @param memento - memento where the user response is stored.
|
||||
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
|
||||
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
|
||||
* pre-requisite, the calling code has to ensure that the eula has not yet been previously accepted by the user.
|
||||
* returns true if the user accepted the EULA.
|
||||
*/
|
||||
export async function promptForEula(memento: vscode.Memento, userRequested: boolean = false, requireUserAction: boolean = false): Promise<boolean> {
|
||||
let response: string | undefined = loc.no;
|
||||
const config = <AzdataDeployOption>getConfig(azdataAcceptEulaKey);
|
||||
if (userRequested) {
|
||||
Logger.show();
|
||||
Logger.log(loc.userRequestedAcceptEula);
|
||||
}
|
||||
const responses = userRequested
|
||||
? [loc.accept, loc.decline]
|
||||
: [loc.accept, loc.askLater, loc.doNotAskAgain];
|
||||
if (config === AzdataDeployOption.prompt || userRequested) {
|
||||
Logger.show();
|
||||
Logger.log(loc.promptForEulaLog(microsoftPrivacyStatementUrl, eulaUrl));
|
||||
response = requireUserAction
|
||||
? await vscode.window.showErrorMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses)
|
||||
: await vscode.window.showInformationMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses);
|
||||
Logger.log(loc.userResponseToEulaPrompt(response));
|
||||
}
|
||||
if (response === loc.doNotAskAgain) {
|
||||
await setConfig(azdataAcceptEulaKey, AzdataDeployOption.dontPrompt);
|
||||
} else if (response === loc.accept) {
|
||||
await memento.update(eulaAccepted, true); // save a memento that eula was accepted
|
||||
await vscode.commands.executeCommand('setContext', eulaAccepted, true); // save a context key that eula was accepted so that command for accepting eula is no longer available in commandPalette
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the Windows installer and runs it
|
||||
*/
|
||||
async function downloadAndInstallAzdataWin32(): Promise<void> {
|
||||
const downLoadLink = await getPlatformDownloadLink();
|
||||
const downloadFolder = os.tmpdir();
|
||||
const downloadLogs = path.join(downloadFolder, 'ads_azdata_install_logs.log');
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs commands to install azdata on MacOS
|
||||
*/
|
||||
async function installAzdataDarwin(): Promise<void> {
|
||||
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
|
||||
await executeCommand('brew', ['update']);
|
||||
await executeCommand('brew', ['install', 'azdata-cli']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs commands to update azdata on MacOS
|
||||
*/
|
||||
async function updateAzdataDarwin(): Promise<void> {
|
||||
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
|
||||
await executeCommand('brew', ['update']);
|
||||
await executeCommand('brew', ['upgrade', 'azdata-cli']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs commands to install azdata on Linux
|
||||
*/
|
||||
async function installAzdataLinux(): Promise<void> {
|
||||
// https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata-linux-package
|
||||
// Get packages needed for install process
|
||||
await executeSudoCommand('apt-get update');
|
||||
await executeSudoCommand('apt-get install gnupg ca-certificates curl wget software-properties-common apt-transport-https lsb-release -y');
|
||||
// Download and install the signing key
|
||||
await executeSudoCommand('curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null');
|
||||
// Add the azdata repository information
|
||||
const release = (await executeCommand('lsb_release', ['-rs'])).stdout.trim();
|
||||
await executeSudoCommand(`add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/${release}/mssql-server-2019.list)"`);
|
||||
// Update repository information and install azdata
|
||||
await executeSudoCommand('apt-get update');
|
||||
await executeSudoCommand('apt-get install -y azdata-cli');
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async function findSpecificAzdata(): Promise<IAzdataTool> {
|
||||
const path = await ((process.platform === 'win32') ? searchForCmd('azdata.cmd') : searchForCmd('azdata'));
|
||||
const versionOutput = await executeAzdataCommand(`"${path}"`, ['--version']);
|
||||
return new AzdataTool(path, parseVersion(versionOutput.stdout));
|
||||
}
|
||||
|
||||
function getConfig(key: string): AzdataDeployOption | undefined {
|
||||
const config = vscode.workspace.getConfiguration(azdataConfigSection);
|
||||
const value = <AzdataDeployOption>config.get<AzdataDeployOption>(key);
|
||||
Logger.log(loc.azdataUserSettingRead(key, value));
|
||||
return value;
|
||||
}
|
||||
|
||||
async function setConfig(key: string, value: string): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration(azdataConfigSection);
|
||||
await config.update(key, value, vscode.ConfigurationTarget.Global);
|
||||
Logger.log(loc.azdataUserSettingUpdated(key, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest azdata version available for a given platform
|
||||
*/
|
||||
export async function discoverLatestAvailableAzdataVersion(): Promise<SemVer> {
|
||||
Logger.log(loc.checkingLatestAzdataVersion);
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return await discoverLatestStableAzdataVersionDarwin();
|
||||
// case 'linux':
|
||||
// ideally we would not to discover linux package availability using the apt/apt-get/apt-cache package manager commands.
|
||||
// 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.
|
||||
default:
|
||||
return await getPlatformReleaseVersion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out the azdata version from the raw azdata version output
|
||||
* @param raw The raw version output from azdata --version
|
||||
*/
|
||||
function parseVersion(raw: string): string {
|
||||
// Currently the version is a multi-line string that contains other version information such
|
||||
// as the Python installation, with the first line being the version of azdata itself.
|
||||
const lines = raw.split(os.EOL);
|
||||
return lines[0].trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest azdata version for MacOs clients
|
||||
*/
|
||||
async function discoverLatestStableAzdataVersionDarwin(): Promise<SemVer> {
|
||||
// set brew tap to azdata-cli repository
|
||||
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
|
||||
await executeCommand('brew', ['update']);
|
||||
let brewInfoAzdataCliJson;
|
||||
// Get the package version 'info' about 'azdata-cli' from 'brew' as a json object
|
||||
const brewInfoOutput = (await executeCommand('brew', ['info', 'azdata-cli', '--json'])).stdout;
|
||||
try {
|
||||
brewInfoAzdataCliJson = JSON.parse(brewInfoOutput);
|
||||
} catch (e) {
|
||||
throw Error(`failed to parse the JSON contents output of: 'brew info azdata-cli --json', text being parsed: '${brewInfoOutput}', error:${getErrorMessage(e)}`);
|
||||
}
|
||||
// Get the 'info' about 'azdata-cli' from 'brew' as a json object
|
||||
const azdataPackageVersionInfo: AzdataDarwinPackageVersionInfo = brewInfoAzdataCliJson.shift();
|
||||
Logger.log(loc.latestAzdataVersionAvailable(azdataPackageVersionInfo.versions.stable));
|
||||
return new SemVer(azdataPackageVersionInfo.versions.stable);
|
||||
}
|
||||
|
||||
async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<ProcessOutput> {
|
||||
additionalEnvVars = Object.assign(additionalEnvVars, { 'ACCEPT_EULA': 'yes' });
|
||||
const debug = vscode.workspace.getConfiguration(azdataConfigSection).get(debugConfigKey);
|
||||
if (debug) {
|
||||
args.push('--debug');
|
||||
}
|
||||
return executeCommand(command, args, additionalEnvVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest azdata version for linux clients
|
||||
* This method requires sudo permission so not suitable to be run during startup.
|
||||
*/
|
||||
// async function discoverLatestStableAzdataVersionLinux(): Promise<SemVer> {
|
||||
// // Update repository information and install azdata
|
||||
// await executeSudoCommand('apt-get update');
|
||||
// const output = (await executeCommand('apt', ['list', 'azdata-cli', '--upgradeable'])).stdout;
|
||||
// // the packageName (with version) string is the second space delimited token on the 2nd line
|
||||
// const packageName = output.split('\n')[1].split(' ')[1];
|
||||
// // the version string is the first part of the package sting before '~'
|
||||
// const version = packageName.split('~')[0];
|
||||
// Logger.log(loc.latestAzdataVersionAvailable(version));
|
||||
// return new SemVer(version);
|
||||
// }
|
||||
@@ -1,76 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
export 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)}`);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AdditionalEnvVars } from 'azdata-ext';
|
||||
import { AdditionalEnvVars } from 'az-ext';
|
||||
import * as cp from 'child_process';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
@@ -10,7 +10,7 @@ export class Log {
|
||||
private _output: vscode.OutputChannel;
|
||||
|
||||
constructor() {
|
||||
this._output = vscode.window.createOutputChannel(loc.azdata);
|
||||
this._output = vscode.window.createOutputChannel(loc.az);
|
||||
}
|
||||
|
||||
log(msg: string): void {
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as which from 'which';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
|
||||
export class NoAzureCLIError extends Error implements azExt.ErrorWithLink {
|
||||
constructor() {
|
||||
super(loc.noAzdata);
|
||||
super(loc.noAzureCLI);
|
||||
}
|
||||
|
||||
public get messageWithLink(): string {
|
||||
return loc.noAzdataWithLink;
|
||||
return loc.noAzureCLI;
|
||||
}
|
||||
}
|
||||
|
||||
export class AzureCLIArcExtError extends Error implements azExt.ErrorWithLink {
|
||||
constructor() {
|
||||
super(loc.arcdataExtensionNotInstalled);
|
||||
}
|
||||
|
||||
public get messageWithLink(): string {
|
||||
return loc.arcdataExtensionNotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the first instance of the specified executable in the PATH environment variable
|
||||
* @param exe The executable to search for
|
||||
|
||||
@@ -4,21 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// config setting keys
|
||||
export const azdataConfigSection: string = 'azcli';
|
||||
export const azdataAcceptEulaKey: string = 'acceptEula';
|
||||
export const azdataInstallKey: string = 'install';
|
||||
export const azdataUpdateKey: string = 'update';
|
||||
export const azdatarequiredUpdateKey: string = 'requiredUpdate';
|
||||
export const azConfigSection: string = 'azcli';
|
||||
export const debugConfigKey = 'logDebugInfo';
|
||||
export const azRequiredUpdateKey: string = 'requiredUpdate';
|
||||
|
||||
|
||||
// context keys && memento keys
|
||||
export const eulaAccepted = 'azcli.eulaAccepted';
|
||||
export const azdataFound = 'azcli.found';
|
||||
export const azFound = 'az.found';
|
||||
|
||||
// other constants
|
||||
export const azdataHostname = 'https://aka.ms';
|
||||
export const azdataUri = 'azdata-msi';
|
||||
export const azdataReleaseJson = 'azdata/release.json';
|
||||
export const microsoftPrivacyStatementUrl = 'https://privacy.microsoft.com/privacystatement';
|
||||
export const eulaUrl = 'https://aka.ms/eula-azdata-en';
|
||||
export const latestAzArcExtensionVersion = '1.0.0';
|
||||
|
||||
@@ -3,70 +3,26 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as rd from 'resource-deployment';
|
||||
import * as vscode from 'vscode';
|
||||
import { getExtensionApi } from './api';
|
||||
import { checkAndInstallAzdata, checkAndUpdateAzdata, findAzdata, isEulaAccepted, promptForEula } from './azdata';
|
||||
import Logger from './common/logger';
|
||||
import * as constants from './constants';
|
||||
import * as loc from './localizedConstants';
|
||||
import { AzdataToolService } from './services/azdataToolService';
|
||||
import { findAz } from './az';
|
||||
import { ArcControllerConfigProfilesOptionsSource } from './providers/arcControllerConfigProfilesOptionsSource';
|
||||
import { AzToolService } from './services/azToolService';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<azdataExt.IExtension> {
|
||||
const azdataToolService = new AzdataToolService();
|
||||
let eulaAccepted: boolean = false;
|
||||
vscode.commands.registerCommand('azcli.acceptEula', async () => {
|
||||
await promptForEula(context.globalState, true /* userRequested */);
|
||||
});
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<azExt.IExtension> {
|
||||
const azToolService = new AzToolService();
|
||||
|
||||
vscode.commands.registerCommand('azcli.install', async () => {
|
||||
azdataToolService.localAzdata = await checkAndInstallAzdata(true /* userRequested */);
|
||||
});
|
||||
azToolService.localAz = await findAz();
|
||||
|
||||
vscode.commands.registerCommand('azcli.update', async () => {
|
||||
if (await checkAndUpdateAzdata(azdataToolService.localAzdata, true /* userRequested */)) { // if an update was performed
|
||||
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
Logger.log(loc.eulaAcceptedStateOnStartup(eulaAccepted));
|
||||
|
||||
// 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.
|
||||
.then(async azdataTool => {
|
||||
if (azdataTool !== undefined) {
|
||||
azdataToolService.localAzdata = azdataTool;
|
||||
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));
|
||||
}
|
||||
try {
|
||||
//update if available and user wants it.
|
||||
if (await checkAndUpdateAzdata(azdataToolService.localAzdata)) { // if an update was performed
|
||||
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
|
||||
}
|
||||
} catch (err) {
|
||||
vscode.window.showWarningMessage(loc.updateError(err));
|
||||
}
|
||||
}
|
||||
return azdataTool;
|
||||
});
|
||||
|
||||
const azdataApi = getExtensionApi(context.globalState, azdataToolService, localAzdataDiscovered);
|
||||
const azApi = getExtensionApi(azToolService);
|
||||
|
||||
// register option source(s)
|
||||
// TODO: Uncomment this once azdata extension is removed
|
||||
// const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
|
||||
// context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)));
|
||||
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
|
||||
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azApi)));
|
||||
|
||||
return azdataApi;
|
||||
return azApi;
|
||||
}
|
||||
|
||||
export function deactivate(): void { }
|
||||
|
||||
@@ -5,69 +5,31 @@
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getErrorMessage } from './common/utils';
|
||||
import { azdataConfigSection, azdataInstallKey, azdataUpdateKey, azdatarequiredUpdateKey } from './constants';
|
||||
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 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 downloadFinished = localize('azdata.downloadFinished', "Download finished");
|
||||
export const installingAzdata = localize('azdata.installingAzdata', "Installing Azure Data CLI...");
|
||||
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 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 no = localize('azdata.no', "No");
|
||||
export const accept = localize('azdata.accept', "Accept");
|
||||
export const decline = localize('azdata.decline', "Decline");
|
||||
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
||||
export const askLater = localize('azdata.askLater', "Ask Later");
|
||||
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 stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
||||
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
||||
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 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 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 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 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 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 promptForRequiredAzdataUpdate = (requiredVersion: string, latestVersion: string): string => localize('azdata.promptForRequiredAzdataUpdate', "This extension requires Azure Data CLI >= {0} to be installed, do you wish to update to the latest version ({1}) now? If you do not then some functionality may not work.", requiredVersion, latestVersion);
|
||||
export const requiredVersionNotAvailable = (requiredVersion: string, currentVersion: string): string => localize('azdata.requiredVersionNotAvailable', "This extension requires Azure Data CLI >= {0} to be installed, but the current version available is only {1}. Install the correct version manually from [here](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) and then restart Azure Data Studio.", requiredVersion, currentVersion);
|
||||
export const promptForAzdataUpdateLog = (version: string): string => promptLog(promptForAzdataUpdate(version));
|
||||
export const promptForRequiredAzdataUpdateLog = (requiredVersion: string, latestVersion: string): string => promptLog(promptForRequiredAzdataUpdate(requiredVersion, latestVersion));
|
||||
export const missingRequiredVersion = (requiredVersion: string): string => localize('azdata.missingRequiredVersion', "Azure Data CLI >= {0} is required for this feature. Run the 'Azure Data CLI: Check for Update' command to install this and then try again.", requiredVersion);
|
||||
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
||||
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 Azure Data CLI: {0}", err.message ?? err);
|
||||
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 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, 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 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 Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataUpdateKey, config);
|
||||
export const skipRequiredUpdate = (config: string): string => localize('azdata.skipRequiredUpdate', "Skipping required update of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdatarequiredUpdateKey, 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 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 Azure Data CLI: {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 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 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 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 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 endpointOrNamespaceRequired = localize('azdata.endpointOrNamespaceRequired', "Either an endpoint or a namespace must be specified");
|
||||
export const az = localize('az.az', "Azure CLI");
|
||||
export const searchingForAz = localize('az.searchingForAz', "Searching for existing Azure CLI installation...");
|
||||
export const foundExistingAz = (path: string, version: string): string => localize('az.foundExistingAz', "Found existing Azure CLI installation of version (v{0}) at path:{1}", version, path);
|
||||
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('az.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
|
||||
export const downloadFinished = localize('az.downloadFinished', "Download finished");
|
||||
export const downloadingTo = (name: string, url: string, location: string): string => localize('az.downloadingTo', "Downloading {0} from {1} to {2}", name, url, location);
|
||||
export const executingCommand = (command: string, args: string[]): string => localize('az.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
|
||||
export const stdoutOutput = (stdout: string): string => localize('az.stdoutOutput', "stdout: {0}", stdout);
|
||||
export const stderrOutput = (stderr: string): string => localize('az.stderrOutput', "stderr: {0}", stderr);
|
||||
export const gettingTextContentsOfUrl = (url: string): string => localize('az.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
|
||||
export const promptLog = (logEntry: string) => localize('az.promptLog', "Prompting the user to accept the following: {0}", logEntry);
|
||||
export const downloadError = localize('az.downloadError', "Error while downloading");
|
||||
export const platformUnsupported = (platform: string): string => localize('az.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
|
||||
export const unexpectedCommandError = (errMsg: string): string => localize('az.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
|
||||
export const unexpectedExitCode = (code: number, err: string): string => localize('az.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
|
||||
export const noReleaseVersion = (platform: string, releaseInfo: string): string => localize('az.noReleaseVersion', "No release version available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
|
||||
export const noDownloadLink = (platform: string, releaseInfo: string): string => localize('az.noDownloadLink', "No download link available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
|
||||
export const failedToParseReleaseInfo = (url: string, fileContents: string, err: any): string => localize('az.failedToParseReleaseInfo', "Failed to parse the JSON of contents at: {0}.\nFile contents:\n{1}\nError: {2}", url, fileContents, getErrorMessage(err));
|
||||
export const endpointOrNamespaceRequired = localize('az.endpointOrNamespaceRequired', "Either an endpoint or a namespace must be specified");
|
||||
export const arcdataExtensionNotInstalled = localize('az.arcdataExtensionNotInstalled', "This extension requires the Azure CLI extension 'arcdata' to be installed. Install the latest version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.");
|
||||
export const noAzureCLI = localize('az.noAzureCLI', "No Azure CLI is available. Install the latest version manually from [here](https://docs.microsoft.com/cli/azure/install-azure-cli) and then restart Azure Data Studio.");
|
||||
export const requiredArcDataVersionNotAvailable = (requiredVersion: string, currentVersion: string): string => localize('az.requiredVersionNotAvailable', "This extension requires the Azure CLI extension 'arcdata' version >= {0} to be installed, but the current version available is only {1}. Install the correct version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.", requiredVersion, currentVersion);
|
||||
export const unsupportedArcDataVersion = (requiredVersion: string, currentVersion: string): string => localize('az.unsupportedArcDataVersion', "Your downloaded version {1} of the Azure CLI extension 'arcdata' is not yet supported. The latest version is is {0}. Install the correct version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.", requiredVersion, currentVersion);
|
||||
export const doNotAskAgain = localize('az.doNotAskAgain', "Don't Ask Again");
|
||||
export const askLater = localize('az.askLater', "Ask Later");
|
||||
export const azOutputParseErrorCaught = (command: string): string => localize('az.azOutputParseErrorCaught', "An error occurred while parsing the output of az command: {0}. The output is not JSON.", command);
|
||||
|
||||
@@ -4,19 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as rd from 'resource-deployment';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azExt from 'az-ext';
|
||||
|
||||
/**
|
||||
* Class that provides options sources for an Arc Data Controller
|
||||
*/
|
||||
export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider {
|
||||
readonly id = 'arc.controller.config.profiles';
|
||||
constructor(private _azdataExtApi: azdataExt.IExtension) { }
|
||||
readonly id = 'azcli.arc.controller.config.profiles';
|
||||
constructor(private _azExtApi: azExt.IExtension) { }
|
||||
async getOptions(): Promise<string[]> {
|
||||
const isEulaAccepted = await this._azdataExtApi.isEulaAccepted();
|
||||
if (!isEulaAccepted) { // if eula has not yet be accepted then give user a chance to accept it
|
||||
await this._azdataExtApi.promptForEula();
|
||||
}
|
||||
return (await this._azdataExtApi.azdata.arc.dc.config.list()).result;
|
||||
return (await this._azExtApi.az.arcdata.dc.config.list()).stdout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,24 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAzdataTool } from '../azdata';
|
||||
import { IAzTool } from '../az';
|
||||
|
||||
export class AzdataToolService {
|
||||
private _localAzdata: IAzdataTool | undefined;
|
||||
export class AzToolService {
|
||||
private _localAz: IAzTool | undefined;
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the localAzdata that was last saved
|
||||
* Gets the localAz that was last saved
|
||||
*/
|
||||
get localAzdata(): IAzdataTool | undefined {
|
||||
return this._localAzdata;
|
||||
get localAz(): IAzTool | undefined {
|
||||
return this._localAz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the localAzdata object to be used for azdata operations
|
||||
* Sets the localAz object to be used for az operations
|
||||
*/
|
||||
set localAzdata(azdata: IAzdataTool | undefined) {
|
||||
this._localAzdata = azdata;
|
||||
set localAz(az: IAzTool | undefined) {
|
||||
this._localAz = az;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// /*---------------------------------------------------------------------------------------------
|
||||
// * 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';
|
||||
import * as childProcess from '../common/childProcess';
|
||||
import * as sinon from 'sinon';
|
||||
import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { getExtensionApi, throwIfNoAzdataOrEulaNotAccepted } from '../api';
|
||||
import { AzdataToolService } from '../services/azdataToolService';
|
||||
import { assertRejected } from './testUtils';
|
||||
import { AzdataTool, IAzdataTool, AzdataDeployOption } from '../azdata';
|
||||
// import * as azExt from 'az-ext';
|
||||
// import * as childProcess from '../common/childProcess';
|
||||
// import * as sinon from 'sinon';
|
||||
// import * as vscode from 'vscode';
|
||||
// import * as TypeMoq from 'typemoq';
|
||||
// import { getExtensionApi } from '../api';
|
||||
// import { AzToolService } from '../services/azToolService';
|
||||
// import { assertRejected } from './testUtils';
|
||||
// import { AzTool } from '../azdata';
|
||||
|
||||
describe('api', function (): void {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
describe('throwIfNoAzdataOrEulaNotAccepted', function (): void {
|
||||
it('throws when no azdata', function (): void {
|
||||
should(() => throwIfNoAzdataOrEulaNotAccepted(undefined, false)).throw();
|
||||
});
|
||||
it('throws when EULA not accepted', function (): void {
|
||||
should(() => throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, false)).throw();
|
||||
});
|
||||
it('passes with AzdataTool and EULA accepted', function (): void {
|
||||
throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, true);
|
||||
});
|
||||
});
|
||||
describe('getExtensionApi', function (): void {
|
||||
it('throws when no azdata', async function (): Promise<void> {
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const azdataToolService = new AzdataToolService();
|
||||
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(undefined));
|
||||
await assertRejected(api.isEulaAccepted(), 'isEulaAccepted');
|
||||
await assertApiCalls(api, assertRejected);
|
||||
});
|
||||
// describe('api', function (): void {
|
||||
// afterEach(function (): void {
|
||||
// sinon.restore();
|
||||
// });
|
||||
// describe('getExtensionApi', function (): void {
|
||||
// it('throws when no az', async function (): Promise<void> {
|
||||
// const azToolService = new AzToolService();
|
||||
// const api = getExtensionApi(azToolService);
|
||||
// await assertApiCalls(api, assertRejected);
|
||||
// });
|
||||
|
||||
it('throws when EULA not accepted', async function (): Promise<void> {
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => false);
|
||||
const azdataToolService = new AzdataToolService();
|
||||
// Not using a mock here because it'll hang when resolving mocked objects
|
||||
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
|
||||
should(await api.isEulaAccepted()).be.false('EULA should not be accepted');
|
||||
await assertApiCalls(api, assertRejected);
|
||||
});
|
||||
// it('succeed when az present and EULA accepted', async function (): Promise<void> {
|
||||
// const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
// mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
||||
// const azTool = new AzTool('', '99.0.0');
|
||||
// const azToolService = new AzToolService();
|
||||
// azToolService.localAz = azTool;
|
||||
// // Not using a mock here because it'll hang when resolving mocked objects
|
||||
// const api = getExtensionApi(azToolService);
|
||||
// sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
|
||||
// // Version needs to be valid so it can be parsed correctly
|
||||
// if (args[0] === '--version') {
|
||||
// return { stdout: `99.0.0`, stderr: '' };
|
||||
// }
|
||||
// console.log(args[0]);
|
||||
// return { stdout: `{ }`, stderr: '' };
|
||||
// });
|
||||
// await assertApiCalls(api, async (promise, message) => {
|
||||
// try {
|
||||
// await promise;
|
||||
// } catch (err) {
|
||||
// throw new Error(`API call to ${message} should have succeeded. ${err}`);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
it('succeed when azdata present and EULA accepted', async function (): Promise<void> {
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
||||
const azdataTool = new AzdataTool('', '99.0.0');
|
||||
const azdataToolService = new AzdataToolService();
|
||||
azdataToolService.localAzdata = azdataTool;
|
||||
// Not using a mock here because it'll hang when resolving mocked objects
|
||||
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(azdataTool));
|
||||
should(await api.isEulaAccepted()).be.true('EULA should be accepted');
|
||||
sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
|
||||
// Version needs to be valid so it can be parsed correctly
|
||||
if (args[0] === '--version') {
|
||||
return { stdout: `99.0.0`, stderr: '' };
|
||||
}
|
||||
console.log(args[0]);
|
||||
return { stdout: `{ }`, stderr: '' };
|
||||
});
|
||||
await assertApiCalls(api, async (promise, message) => {
|
||||
try {
|
||||
await promise;
|
||||
} catch (err) {
|
||||
throw new Error(`API call to ${message} should have succeeded. ${err}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
// /**
|
||||
// * Asserts that calls to the Az API behave as expected
|
||||
// * @param api The API object to test the calls with
|
||||
// * @param assertCallback The function to assert that the results are as expected
|
||||
// */
|
||||
// async function assertApiCalls(api: azExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
|
||||
// await assertCallback(api.az.getPath(), 'getPath');
|
||||
// await assertCallback(api.az.getSemVersion(), 'getSemVersion');
|
||||
// await assertCallback(api.az.version(), 'version');
|
||||
|
||||
it('promptForEula', async function (): Promise<void> {
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
||||
const azdataToolService = new AzdataToolService();
|
||||
// Not using a mock here because it'll hang when resolving mocked objects
|
||||
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => AzdataDeployOption.dontPrompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage');
|
||||
should(await api.promptForEula()).be.false();
|
||||
should(showErrorMessageStub.called).be.true('User should have been prompted to accept');
|
||||
});
|
||||
// await assertCallback(api.az.arcdata.dc.config.list(), 'arc dc config list');
|
||||
// await assertCallback(api.az.arcdata.dc.config.show(), 'arc dc config show');
|
||||
|
||||
/**
|
||||
* Asserts that calls to the Azdata API behave as expected
|
||||
* @param api The API object to test the calls with
|
||||
* @param assertCallback The function to assert that the results are as expected
|
||||
*/
|
||||
async function assertApiCalls(api: azdataExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
|
||||
await assertCallback(api.azdata.getPath(), 'getPath');
|
||||
await assertCallback(api.azdata.getSemVersion(), 'getSemVersion');
|
||||
await assertCallback(api.azdata.login({ endpoint: 'https://127.0.0.1' }, '', ''), 'login');
|
||||
await assertCallback(api.azdata.login({ namespace: 'namespace' }, '', ''), 'login');
|
||||
await assertCallback(api.azdata.version(), 'version');
|
||||
|
||||
await assertCallback(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
|
||||
|
||||
await assertCallback(api.azdata.arc.dc.config.list(), 'arc dc config list');
|
||||
await assertCallback(api.azdata.arc.dc.config.show(), 'arc dc config show');
|
||||
|
||||
await assertCallback(api.azdata.arc.dc.endpoint.list(), 'arc dc endpoint list');
|
||||
|
||||
await assertCallback(api.azdata.arc.sql.mi.list(), 'arc sql mi list');
|
||||
await assertCallback(api.azdata.arc.sql.mi.delete(''), 'arc sql mi delete');
|
||||
await assertCallback(api.azdata.arc.sql.mi.show(''), 'arc sql mi show');
|
||||
await assertCallback(api.azdata.arc.sql.mi.edit('', {}), 'arc sql mi edit');
|
||||
await assertCallback(api.azdata.arc.postgres.server.list(), 'arc sql postgres server list');
|
||||
await assertCallback(api.azdata.arc.postgres.server.delete(''), 'arc sql postgres server delete');
|
||||
await assertCallback(api.azdata.arc.postgres.server.show(''), 'arc sql postgres server show');
|
||||
await assertCallback(api.azdata.arc.postgres.server.edit('', {}), 'arc sql postgres server edit');
|
||||
}
|
||||
});
|
||||
});
|
||||
// await assertCallback(api.az.arcdata.dc.endpoint.list(), 'arc dc endpoint list');
|
||||
|
||||
// await assertCallback(api.az.sql.miarc.list(), 'arc sql mi list');
|
||||
// await assertCallback(api.az.sql.miarc.delete(''), 'arc sql mi delete');
|
||||
// await assertCallback(api.az.sql.miarc.show(''), 'arc sql mi show');
|
||||
// await assertCallback(api.az.sql.miarc.edit('', {}), 'arc sql mi edit');
|
||||
// await assertCallback(api.az.postgres.arcserver.list(), 'arc sql postgres server list');
|
||||
// await assertCallback(api.az.postgres.arcserver.delete(''), 'arc sql postgres server delete');
|
||||
// await assertCallback(api.az.postgres.arcserver.show(''), 'arc sql postgres server show');
|
||||
// await assertCallback(api.az.postgres.arcserver.edit('', {}), 'arc sql postgres server edit');
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -5,228 +5,128 @@
|
||||
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from '../azdata';
|
||||
import * as childProcess from '../common/childProcess';
|
||||
import { HttpClient } from '../common/httpClient';
|
||||
import * as utils from '../common/utils';
|
||||
import * as loc from '../localizedConstants';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { AzdataReleaseInfo } from '../azdataReleaseInfo';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { eulaAccepted } from '../constants';
|
||||
import * as azdata from '../az';
|
||||
|
||||
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', azdata.MIN_AZDATA_VERSION.raw);
|
||||
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
|
||||
|
||||
/**
|
||||
* 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: AzdataReleaseInfo = {
|
||||
win32: {
|
||||
'version': '9999.999.999',
|
||||
'link': 'https://download.com/azdata-20.0.1.msi'
|
||||
},
|
||||
darwin: {
|
||||
'version': '9999.999.999'
|
||||
},
|
||||
linux: {
|
||||
'version': '9999.999.999'
|
||||
}
|
||||
};
|
||||
let executeSudoCommandStub: sinon.SinonStub;
|
||||
|
||||
describe('azdata', function () {
|
||||
describe('az', function () {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
describe('azdataTool', function (): void {
|
||||
const azdataTool = new azdata.AzdataTool(os.tmpdir(), '1.0.0');
|
||||
describe('azTool', function (): void {
|
||||
const azTool = new azdata.AzTool('C:/Program Files (x86)/Microsoft SDKs/Azure/CLI2/wbin/az.cmd', '2.26.0');
|
||||
let executeCommandStub: sinon.SinonStub;
|
||||
const namespace = 'myNamespace';
|
||||
const name = 'myName';
|
||||
const connectivityMode = 'myConnectivityMode';
|
||||
const resourceGroup = 'myResourceGroup';
|
||||
const location = 'myLocation';
|
||||
const subscription = 'mySubscription';
|
||||
const profileName = 'myProfileName';
|
||||
const storageClass = 'myStorageClass';
|
||||
const namespace = 'arc4';
|
||||
const name = 'cy-dc-4';
|
||||
|
||||
beforeEach(function (): void {
|
||||
executeCommandStub = sinon.stub(childProcess, 'executeCommand').resolves({ stdout: '{}', stderr: '' });
|
||||
});
|
||||
|
||||
describe('arc', function (): void {
|
||||
describe('arcdata', function (): void {
|
||||
describe('dc', function (): void {
|
||||
it('create', async function (): Promise<void> {
|
||||
await azdataTool.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
|
||||
verifyExecuteCommandCalledWithArgs([
|
||||
'arc', 'dc', 'create',
|
||||
namespace,
|
||||
name,
|
||||
connectivityMode,
|
||||
resourceGroup,
|
||||
location,
|
||||
subscription,
|
||||
profileName,
|
||||
storageClass]);
|
||||
});
|
||||
describe('endpoint', async function (): Promise<void> {
|
||||
it('list', async function (): Promise<void> {
|
||||
await azdataTool.arc.dc.endpoint.list();
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'endpoint', 'list']);
|
||||
await azTool.arcdata.dc.endpoint.list(namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'endpoint', 'list', '--k8s-namespace', namespace, '--use-k8s']);
|
||||
});
|
||||
});
|
||||
describe('config', async function (): Promise<void> {
|
||||
it('list', async function (): Promise<void> {
|
||||
await azdataTool.arc.dc.config.list();
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'list']);
|
||||
await azTool.arcdata.dc.config.list();
|
||||
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'config', 'list']);
|
||||
});
|
||||
it('show', async function (): Promise<void> {
|
||||
await azdataTool.arc.dc.config.show();
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show']);
|
||||
await azTool.arcdata.dc.config.show(namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'config', 'show', '--k8s-namespace', namespace, '--use-k8s']);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('postgres', function (): void {
|
||||
describe('server', function (): void {
|
||||
it('delete', async function (): Promise<void> {
|
||||
await azdataTool.arc.postgres.server.delete(name);
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'delete', name]);
|
||||
});
|
||||
it('list', async function (): Promise<void> {
|
||||
await azdataTool.arc.postgres.server.list();
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'list']);
|
||||
});
|
||||
it('show', async function (): Promise<void> {
|
||||
await azdataTool.arc.postgres.server.show(name);
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'show', name]);
|
||||
});
|
||||
it('edit', async function (): Promise<void> {
|
||||
const args = {
|
||||
adminPassword: true,
|
||||
coresLimit: 'myCoresLimit',
|
||||
coresRequest: 'myCoresRequest',
|
||||
engineSettings: 'myEngineSettings',
|
||||
extensions: 'myExtensions',
|
||||
memoryLimit: 'myMemoryLimit',
|
||||
memoryRequest: 'myMemoryRequest',
|
||||
noWait: true,
|
||||
port: 1337,
|
||||
replaceEngineSettings: true,
|
||||
workers: 2
|
||||
};
|
||||
await azdataTool.arc.postgres.server.edit(name, args);
|
||||
verifyExecuteCommandCalledWithArgs([
|
||||
'arc', 'postgres', 'server', 'edit',
|
||||
name,
|
||||
'--admin-password',
|
||||
args.coresLimit,
|
||||
args.coresRequest,
|
||||
args.engineSettings,
|
||||
args.extensions,
|
||||
args.memoryLimit,
|
||||
args.memoryRequest,
|
||||
'--no-wait',
|
||||
args.port.toString(),
|
||||
'--replace-engine-settings',
|
||||
args.workers.toString()]);
|
||||
});
|
||||
it('edit no optional args', async function (): Promise<void> {
|
||||
await azdataTool.arc.postgres.server.edit(name, {});
|
||||
verifyExecuteCommandCalledWithArgs([
|
||||
'arc', 'postgres', 'server', 'edit',
|
||||
name]);
|
||||
verifyExecuteCommandCalledWithoutArgs([
|
||||
'--admin-password',
|
||||
'--cores-limit',
|
||||
'--cores-request',
|
||||
'--engine-settings',
|
||||
'--extensions',
|
||||
'--memory-limit',
|
||||
'--memory-request',
|
||||
'--no-wait',
|
||||
'--port',
|
||||
'--replace-engine-settings',
|
||||
'--workers']);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('sql', function (): void {
|
||||
describe('mi', function (): void {
|
||||
it('delete', async function (): Promise<void> {
|
||||
await azdataTool.arc.sql.mi.delete(name);
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'delete', name]);
|
||||
});
|
||||
it('list', async function (): Promise<void> {
|
||||
await azdataTool.arc.sql.mi.list();
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list']);
|
||||
});
|
||||
it('show', async function (): Promise<void> {
|
||||
await azdataTool.arc.sql.mi.show(name);
|
||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'show', name]);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('general error throws', async function (): Promise<void> {
|
||||
const err = new Error();
|
||||
executeCommandStub.throws(err);
|
||||
try {
|
||||
await azdataTool.arc.dc.endpoint.list();
|
||||
throw new Error('command should have failed');
|
||||
} catch (error) {
|
||||
should(error).equal(err);
|
||||
}
|
||||
});
|
||||
it('ExitCodeError handled and parsed correctly', async function (): Promise<void> {
|
||||
const errorInnerText = 'my error text';
|
||||
const err = new childProcess.ExitCodeError(1, `ERROR { "stderr": "${errorInnerText}"}`);
|
||||
executeCommandStub.throws(err);
|
||||
try {
|
||||
await azdataTool.arc.dc.endpoint.list();
|
||||
throw new Error('command should have failed');
|
||||
} catch (error) {
|
||||
should(error).equal(err);
|
||||
should((error as childProcess.ExitCodeError).stderr).equal(errorInnerText);
|
||||
}
|
||||
});
|
||||
it('ExitCodeError general error with azdata tool existing rethrows original error', async function (): Promise<void> {
|
||||
sinon.stub(fs.promises, 'access').resolves();
|
||||
const err = new childProcess.ExitCodeError(1, 'some other error');
|
||||
executeCommandStub.throws(err);
|
||||
try {
|
||||
await azdataTool.arc.dc.endpoint.list();
|
||||
throw new Error('command should have failed');
|
||||
} catch (error) {
|
||||
should(error).equal(err);
|
||||
}
|
||||
});
|
||||
it('ExitCodeError general error with azdata tool not existing throws NoAzdataError', async function (): Promise<void> {
|
||||
sinon.stub(fs.promises, 'access').throws(new Error('not found'));
|
||||
const err = new childProcess.ExitCodeError(1, 'some other error');
|
||||
executeCommandStub.throws(err);
|
||||
try {
|
||||
await azdataTool.arc.dc.endpoint.list();
|
||||
throw new Error('command should have failed');
|
||||
} catch (error) {
|
||||
should(error instanceof utils.NoAzdataError).be.true('error should have been instance of NoAzdataError');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('login', async function (): Promise<void> {
|
||||
const endpoint = 'myEndpoint';
|
||||
const username = 'myUsername';
|
||||
const password = 'myPassword';
|
||||
await azdataTool.login({ endpoint: endpoint }, username, password);
|
||||
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
|
||||
describe('postgres', function (): void {
|
||||
describe('arc-server', function (): void {
|
||||
it('delete', async function (): Promise<void> {
|
||||
await azTool.postgres.arcserver.delete(name, namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'delete', name, '--k8s-namespace', namespace]);
|
||||
});
|
||||
it('list', async function (): Promise<void> {
|
||||
await azTool.postgres.arcserver.list(namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'list', '--k8s-namespace', namespace]);
|
||||
});
|
||||
it('show', async function (): Promise<void> {
|
||||
await azTool.postgres.arcserver.show(name, namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'show', name, '--k8s-namespace', namespace]);
|
||||
});
|
||||
it('edit', async function (): Promise<void> {
|
||||
const args = {
|
||||
adminPassword: true,
|
||||
coresLimit: 'myCoresLimit',
|
||||
coresRequest: 'myCoresRequest',
|
||||
engineSettings: 'myEngineSettings',
|
||||
extensions: 'myExtensions',
|
||||
memoryLimit: 'myMemoryLimit',
|
||||
memoryRequest: 'myMemoryRequest',
|
||||
noWait: true,
|
||||
port: 1337,
|
||||
replaceEngineSettings: true,
|
||||
workers: 2
|
||||
};
|
||||
await azTool.postgres.arcserver.edit(name, args, namespace);
|
||||
verifyExecuteCommandCalledWithArgs([
|
||||
'postgres', 'arc-server', 'edit',
|
||||
name,
|
||||
'--admin-password',
|
||||
args.coresLimit,
|
||||
args.coresRequest,
|
||||
args.engineSettings,
|
||||
args.extensions,
|
||||
args.memoryLimit,
|
||||
args.memoryRequest,
|
||||
'--no-wait',
|
||||
args.port.toString(),
|
||||
'--replace-engine-settings',
|
||||
args.workers.toString()]);
|
||||
});
|
||||
it('edit no optional args', async function (): Promise<void> {
|
||||
await azTool.postgres.arcserver.edit(name, {}, namespace);
|
||||
verifyExecuteCommandCalledWithArgs([
|
||||
'postgres', 'arc-server', 'edit',
|
||||
name]);
|
||||
verifyExecuteCommandCalledWithoutArgs([
|
||||
'--admin-password',
|
||||
'--cores-limit',
|
||||
'--cores-request',
|
||||
'--engine-settings',
|
||||
'--extensions',
|
||||
'--memory-limit',
|
||||
'--memory-request',
|
||||
'--no-wait',
|
||||
'--port',
|
||||
'--replace-engine-settings',
|
||||
'--workers']);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('sql', function (): void {
|
||||
describe('mi-arc', function (): void {
|
||||
it('delete', async function (): Promise<void> {
|
||||
await azTool.sql.miarc.delete(name, namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'delete', name, '--k8s-namespace', namespace, '--use-k8s']);
|
||||
});
|
||||
it('list', async function (): Promise<void> {
|
||||
await azTool.sql.miarc.list(namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'list', '--k8s-namespace', namespace, '--use-k8s']);
|
||||
});
|
||||
it('show', async function (): Promise<void> {
|
||||
await azTool.sql.miarc.show(name, namespace);
|
||||
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'show', name, '--k8s-namespace', namespace, '--use-k8s']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('version', async function (): Promise<void> {
|
||||
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
|
||||
await azdataTool.version();
|
||||
await azTool.version();
|
||||
verifyExecuteCommandCalledWithArgs(['--version']);
|
||||
});
|
||||
|
||||
@@ -249,557 +149,4 @@ describe('azdata', function () {
|
||||
}
|
||||
});
|
||||
|
||||
describe('findAzdata', function (): void {
|
||||
it('successful', async function (): Promise<void> {
|
||||
// Mock searchForCmd to return a path to azdata.cmd
|
||||
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
|
||||
// Mock call to --version to simulate azdata being installed
|
||||
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '1.0.0', stderr: '' }));
|
||||
await should(azdata.findAzdata()).not.be.rejected();
|
||||
});
|
||||
it('unsuccessful', async function (): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
// Mock searchForCmd to return a failure to find azdata.cmd
|
||||
sinon.stub(utils, 'searchForCmd').returns(Promise.reject(new Error('Could not find azdata')));
|
||||
} else {
|
||||
// Mock call to executeCommand to simulate azdata --version returning error
|
||||
sinon.stub(childProcess, 'executeCommand').returns(Promise.reject({ stdout: '', stderr: 'command not found: azdata' }));
|
||||
}
|
||||
await should(azdata.findAzdata()).be.rejected();
|
||||
});
|
||||
});
|
||||
|
||||
describe('installAzdata', function (): void {
|
||||
|
||||
let errorMessageStub: sinon.SinonStub;
|
||||
beforeEach(function (): void {
|
||||
errorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(<any>loc.yes));
|
||||
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> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SuccessfulInstall();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSuccessfulInstall();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSuccessfulInstall();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('skipped install - dont prompt config', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => 'dontPrompt');
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SkippedInstall();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSkippedInstall();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSkippedInstall();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('skipped install - user chose not to prompt', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
errorMessageStub.resolves(<any>loc.doNotAskAgain);
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SkippedInstall();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSkippedInstall();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSkippedInstall();
|
||||
break;
|
||||
}
|
||||
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
it('unsuccessful download - win32', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'downloadFile').rejects();
|
||||
sinon.stub(childProcess, 'executeCommand')
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
it('unsuccessful install', async function (): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32UnsuccessfulInstall();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinUnsuccessfulInstall();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxUnsuccessfulInstall();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAzdata', function (): void {
|
||||
let showInformationMessageStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(function (): void {
|
||||
showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
||||
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(releaseJson));
|
||||
});
|
||||
|
||||
it('successful update', async function (): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SuccessfulUpdate();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSuccessfulUpdate();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSuccessfulUpdate();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('successful update - always prompt if user requested', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SuccessfulUpdate(true);
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSuccessfulUpdate(true);
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSuccessfulUpdate(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('skipped update - config set not to prompt', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SkippedUpdateDontPrompt();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSkippedUpdateDontPrompt();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSkippedUpdateDontPrompt();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('skipped update - user chose to never prompt again', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
showInformationMessageStub.resolves(<any>loc.doNotAskAgain);
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SkippedUpdateDontPrompt();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSkippedUpdateDontPrompt();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSkippedUpdateDontPrompt();
|
||||
break;
|
||||
}
|
||||
// Config should have been updated since user chose never to prompt again
|
||||
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('skipped update - no new version', async function (): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SkippedUpdate();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinSkippedUpdate();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSkippedUpdate();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it('skipped update - no azdata', async function (): Promise<void> {
|
||||
const result = await azdata.checkAndUpdateAzdata();
|
||||
should(result).be.false();
|
||||
});
|
||||
|
||||
it('unsuccessful update', async function (): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32UnsuccessfulUpdate();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinUnsuccessfulUpdate();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxUnsuccessfulUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
||||
it('finds latest available version of azdata successfully', async function (): Promise<void> {
|
||||
await azdata.discoverLatestAvailableAzdataVersion();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('promptForEula', function (): void {
|
||||
it('skipped because of config', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const result = await azdata.promptForEula(mementoMock.object);
|
||||
should(result).be.false();
|
||||
});
|
||||
|
||||
it('always prompt if user requested', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
|
||||
const result = await azdata.promptForEula(mementoMock.object, true);
|
||||
should(result).be.false();
|
||||
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
|
||||
});
|
||||
|
||||
it('prompt if config set to do so', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
|
||||
const result = await azdata.promptForEula(mementoMock.object);
|
||||
should(result).be.false();
|
||||
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
|
||||
});
|
||||
|
||||
it('update config if user chooses not to prompt', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.doNotAskAgain);
|
||||
const result = await azdata.promptForEula(mementoMock.object);
|
||||
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
should(result).be.false('EULA should not have been accepted');
|
||||
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
|
||||
});
|
||||
|
||||
it('user accepted EULA', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.accept);
|
||||
const result = await azdata.promptForEula(mementoMock.object);
|
||||
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
|
||||
should(result).be.true('EULA should have been accepted');
|
||||
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
|
||||
});
|
||||
|
||||
it('user accepted EULA - require user action', async function (): Promise<void> {
|
||||
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
|
||||
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
|
||||
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
const showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.accept);
|
||||
const result = await azdata.promptForEula(mementoMock.object, true, true);
|
||||
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
|
||||
should(result).be.true('EULA should have been accepted');
|
||||
should(showErrorMessage.calledOnce).be.true('showErrorMessage should have been called to prompt user');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEulaAccepted', function (): void {
|
||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
||||
should(azdata.isEulaAccepted(mementoMock.object)).be.true();
|
||||
});
|
||||
});
|
||||
|
||||
async function testLinuxUnsuccessfulUpdate() {
|
||||
executeSudoCommandStub.rejects();
|
||||
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(updateDone).be.false();
|
||||
should(executeSudoCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinUnsuccessfulUpdate() {
|
||||
const brewInfoOutput = [{
|
||||
name: 'azdata-cli',
|
||||
full_name: 'microsoft/azdata-cli-release/azdata-cli',
|
||||
versions: {
|
||||
'stable': '9999.999.999',
|
||||
'devel': null,
|
||||
'head': null,
|
||||
'bottle': true
|
||||
}
|
||||
}];
|
||||
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.
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.resolve({
|
||||
stderr: '',
|
||||
stdout: JSON.stringify(brewInfoOutput)
|
||||
});
|
||||
})
|
||||
.onCall(5) //6th call is the first one to do actual update, the call number are 0 indexed
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.reject(new Error('not Found'));
|
||||
})
|
||||
.callsFake(async (_command: string, _args: string[]) => { // by default return success
|
||||
return Promise.resolve({ stderr: '', stdout: 'success' });
|
||||
});
|
||||
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(updateDone).be.false();
|
||||
should(executeCommandStub.callCount).equal(6);
|
||||
}
|
||||
|
||||
async function testWin32UnsuccessfulUpdate() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
executeSudoCommandStub.rejects();
|
||||
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(updateDone).be.false('Update should not have been successful');
|
||||
should(executeSudoCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testLinuxSuccessfulUpdate(userRequested = false) {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinSuccessfulUpdate(userRequested = false) {
|
||||
const brewInfoOutput = [{
|
||||
name: 'azdata-cli',
|
||||
full_name: 'microsoft/azdata-cli-release/azdata-cli',
|
||||
versions: {
|
||||
'stable': '9999.999.999',
|
||||
'devel': null,
|
||||
'head': null,
|
||||
'bottle': true
|
||||
}
|
||||
}];
|
||||
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.
|
||||
.resolves({
|
||||
stderr: '',
|
||||
stdout: JSON.stringify(brewInfoOutput)
|
||||
})
|
||||
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
||||
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(userRequested = false) {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
||||
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
|
||||
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
|
||||
}
|
||||
|
||||
async function testLinuxSkippedUpdate() {
|
||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
||||
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
|
||||
}
|
||||
|
||||
async function testDarwinSkippedUpdateDontPrompt() {
|
||||
const brewInfoOutput = [{
|
||||
name: 'azdata-cli',
|
||||
full_name: 'microsoft/azdata-cli-release/azdata-cli',
|
||||
versions: {
|
||||
'stable': '9999.999.999',
|
||||
'devel': null,
|
||||
'head': null,
|
||||
'bottle': true
|
||||
}
|
||||
}];
|
||||
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.
|
||||
.resolves({
|
||||
stderr: '',
|
||||
stdout: JSON.stringify(brewInfoOutput)
|
||||
})
|
||||
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(executeCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
|
||||
}
|
||||
|
||||
async function testWin32SkippedUpdateDontPrompt() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(executeSudoCommandStub.notCalled).be.true(`executeSudoCommand should not have been called ${executeSudoCommandStub.getCalls().join(os.EOL)}`);
|
||||
}
|
||||
|
||||
async function testLinuxSkippedUpdateDontPrompt() {
|
||||
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
|
||||
}
|
||||
|
||||
async function testDarwinSkippedUpdate() {
|
||||
const brewInfoOutput = [{
|
||||
name: 'azdata-cli',
|
||||
full_name: 'microsoft/azdata-cli-release/azdata-cli',
|
||||
versions: {
|
||||
'stable': '9999.999.999',
|
||||
'devel': null,
|
||||
'head': null,
|
||||
'bottle': true
|
||||
}
|
||||
}];
|
||||
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.
|
||||
.resolves({
|
||||
stderr: '',
|
||||
stdout: JSON.stringify(brewInfoOutput)
|
||||
})
|
||||
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
||||
should(executeCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
|
||||
}
|
||||
|
||||
async function testWin32SkippedUpdate() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
||||
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
|
||||
}
|
||||
|
||||
async function testDarwinSkippedInstall() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.reject(new Error('not Found'));
|
||||
})
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
|
||||
});
|
||||
const result = await azdata.checkAndInstallAzdata();
|
||||
should(result).equal(undefined, 'result should be undefined');
|
||||
should(executeCommandStub.callCount).be.equal(0);
|
||||
}
|
||||
|
||||
async function testLinuxSkippedInstall() {
|
||||
sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.rejects(new Error('not Found'))
|
||||
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
executeSudoCommandStub
|
||||
.resolves({ stdout: 'success', stderr: '' });
|
||||
const result = await azdata.checkAndInstallAzdata();
|
||||
should(result).equal(undefined, 'result should be undefined');
|
||||
should(executeSudoCommandStub.callCount).be.equal(0);
|
||||
}
|
||||
|
||||
async function testWin32SkippedInstall() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.rejects(new Error('not Found')) // First call mock the tool not being found
|
||||
.resolves({ stdout: '1.0.0', stderr: '' });
|
||||
executeSudoCommandStub
|
||||
.returns({ stdout: '', stderr: '' });
|
||||
const result = await azdata.checkAndInstallAzdata();
|
||||
should(result).equal(undefined, 'result should be undefined');
|
||||
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
|
||||
}
|
||||
|
||||
async function testWin32SuccessfulInstall() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.rejects(new Error('not Found')) // First call mock the tool not being found
|
||||
.resolves({ stdout: '1.0.0', stderr: '' });
|
||||
executeSudoCommandStub
|
||||
.returns({ stdout: '', stderr: '' });
|
||||
await azdata.checkAndInstallAzdata();
|
||||
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() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.reject(new Error('not Found'));
|
||||
})
|
||||
.callsFake(async (_command: string, _args: string[]) => {
|
||||
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
|
||||
});
|
||||
await azdata.checkAndInstallAzdata();
|
||||
should(executeCommandStub.callCount).be.equal(5);
|
||||
}
|
||||
|
||||
async function testLinuxSuccessfulInstall() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||
.onFirstCall()
|
||||
.rejects(new Error('not Found'))
|
||||
.resolves({ stdout: '0.0.0', stderr: '' });
|
||||
executeSudoCommandStub
|
||||
.resolves({ stdout: 'success', stderr: '' });
|
||||
await azdata.checkAndInstallAzdata();
|
||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.calledThrice).be.true();
|
||||
}
|
||||
|
||||
async function testLinuxUnsuccessfulInstall() {
|
||||
executeSudoCommandStub.rejects();
|
||||
const downloadPromise = azdata.installAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
should(executeSudoCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinUnsuccessfulInstall() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
|
||||
const downloadPromise = azdata.installAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testWin32UnsuccessfulInstall() {
|
||||
executeSudoCommandStub.rejects();
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
const downloadPromise = azdata.installAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
should(executeSudoCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
@@ -1,75 +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 should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import { HttpClient } from '../common/httpClient';
|
||||
import { getPlatformReleaseVersion, getPlatformDownloadLink, AzdataReleaseInfo } from '../azdataReleaseInfo';
|
||||
|
||||
const emptyReleaseJson = {
|
||||
win32: {},
|
||||
darwin: {},
|
||||
linux: {}
|
||||
};
|
||||
|
||||
const releaseVersion = '999.999.999';
|
||||
const releaseLink = 'https://microsoft.com';
|
||||
|
||||
const validReleaseJson: AzdataReleaseInfo = {
|
||||
win32: {
|
||||
version: releaseVersion,
|
||||
link: releaseLink
|
||||
},
|
||||
darwin: {
|
||||
version: releaseVersion,
|
||||
link: releaseLink
|
||||
},
|
||||
linux: {
|
||||
version: releaseVersion,
|
||||
link: releaseLink
|
||||
}
|
||||
};
|
||||
|
||||
describe('azdataReleaseInfo', function (): void {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('getPlatformReleaseVersion', function(): void {
|
||||
it('gets version successfully', async function(): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
|
||||
const version = await getPlatformReleaseVersion();
|
||||
should(version.format()).equal(releaseVersion);
|
||||
});
|
||||
|
||||
it('throws with invalid JSON', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
|
||||
await should(getPlatformReleaseVersion()).be.rejected();
|
||||
});
|
||||
|
||||
it('throws when no version', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
|
||||
await should(getPlatformReleaseVersion()).be.rejected();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPlatformDownloadLink', function(): void {
|
||||
it('gets link successfully', async function(): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
|
||||
const link = await getPlatformDownloadLink();
|
||||
should(link).equal(releaseLink);
|
||||
});
|
||||
|
||||
it('throws with invalid JSON', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
|
||||
await should(getPlatformDownloadLink()).be.rejected();
|
||||
});
|
||||
|
||||
it('throws when no version', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
|
||||
await should(getPlatformDownloadLink()).be.rejected();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,25 +2,17 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as should from 'should';
|
||||
import { NoAzdataError, searchForCmd as searchForExe } from '../../common/utils';
|
||||
// import * as should from 'should';
|
||||
// import { searchForCmd as searchForExe } from '../../common/utils';
|
||||
|
||||
describe('utils', function () {
|
||||
describe('searchForExe', function (): void {
|
||||
it('finds exe successfully', async function (): Promise<void> {
|
||||
await searchForExe('node');
|
||||
});
|
||||
it('throws for non-existent exe', async function (): Promise<void> {
|
||||
await should(searchForExe('someFakeExe')).be.rejected();
|
||||
});
|
||||
});
|
||||
// describe('utils', function () {
|
||||
// describe('searchForExe', function (): void {
|
||||
// it('finds exe successfully', async function (): Promise<void> {
|
||||
// await searchForExe('node');
|
||||
// });
|
||||
// it('throws for non-existent exe', async function (): Promise<void> {
|
||||
// await should(searchForExe('someFakeExe')).be.rejected();
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('NoAzdataError', function (): void {
|
||||
it('error contains message with and without links', function (): void {
|
||||
const error = new NoAzdataError();
|
||||
should(error.message).not.be.empty();
|
||||
should(error.messageWithLink).not.be.empty();
|
||||
should(error.message).not.equal(error.messageWithLink, 'Messages should not be equal');
|
||||
});
|
||||
});
|
||||
});
|
||||
// });
|
||||
|
||||
@@ -1,38 +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 azdataExt from 'azdata-ext';
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import { ArcControllerConfigProfilesOptionsSource } from '../../providers/arcControllerConfigProfilesOptionsSource';
|
||||
|
||||
describe('arcControllerConfigProfilesOptionsSource', async function (): Promise<void> {
|
||||
afterEach(function(): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('eula accepted returns list', async function (): Promise<void> {
|
||||
const options = ['option1', 'option2'];
|
||||
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
|
||||
sinon.stub(api, 'isEulaAccepted').resolves(true);
|
||||
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
|
||||
const source = new ArcControllerConfigProfilesOptionsSource(api);
|
||||
const result = await source.getOptions();
|
||||
should(result).deepEqual(options);
|
||||
});
|
||||
|
||||
it('eula not accepted prompts for acceptance', async function (): Promise<void> {
|
||||
const options = ['option1', 'option2'];
|
||||
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
|
||||
sinon.stub(api, 'isEulaAccepted').resolves(false);
|
||||
const promptStub = sinon.stub(api, 'promptForEula').resolves(true);
|
||||
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
|
||||
const source = new ArcControllerConfigProfilesOptionsSource(api);
|
||||
const result = await source.getOptions();
|
||||
should(result).deepEqual(options);
|
||||
should(promptStub.calledOnce).be.true('promptForEula should have been called');
|
||||
});
|
||||
});
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as should from 'should';
|
||||
import { AzdataTool } from '../../azdata';
|
||||
import { AzdataToolService } from '../../services/azdataToolService';
|
||||
import { AzTool } from '../../az';
|
||||
import { AzToolService } from '../../services/azToolService';
|
||||
|
||||
describe('azdataToolService', function (): void {
|
||||
describe('azToolService', function (): void {
|
||||
it('Tool should be set correctly', async function (): Promise<void> {
|
||||
const service = new AzdataToolService();
|
||||
should(service.localAzdata).be.undefined();
|
||||
service.localAzdata = new AzdataTool('my path', '1.0.0');
|
||||
const service = new AzToolService();
|
||||
should(service.localAz).be.undefined();
|
||||
service.localAz = new AzTool('my path', '1.0.0');
|
||||
should(service).not.be.undefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'azdata-ext' {
|
||||
declare module 'az-ext' {
|
||||
import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Covers defining what the azdata extension exports to other extensions
|
||||
* Covers defining what the az extension exports to other extensions
|
||||
*
|
||||
* IMPORTANT: THIS IS NOT A HARD DEFINITION unlike vscode; therefore no enums or classes should be defined here
|
||||
* (const enums get evaluated when typescript -> javascript so those are fine)
|
||||
*/
|
||||
export const enum extension {
|
||||
name = 'Microsoft.azdata'
|
||||
name = 'Microsoft.azcli'
|
||||
}
|
||||
|
||||
export type AdditionalEnvVars = { [key: string]: string };
|
||||
@@ -258,11 +257,9 @@ declare module 'azdata-ext' {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AzdataOutput<R> {
|
||||
logs: string[],
|
||||
result: R,
|
||||
export interface AzOutput<R> {
|
||||
stdout: R,
|
||||
stderr: string[],
|
||||
stdout: string[],
|
||||
code?: number
|
||||
}
|
||||
|
||||
@@ -270,92 +267,75 @@ declare module 'azdata-ext' {
|
||||
endpoint?: string,
|
||||
namespace?: string
|
||||
}
|
||||
export interface IAzdataApi {
|
||||
arc: {
|
||||
export interface IAzApi {
|
||||
arcdata: {
|
||||
dc: {
|
||||
create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||
endpoint: {
|
||||
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcEndpointListResult[]>>
|
||||
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcEndpointListResult[]>>
|
||||
},
|
||||
config: {
|
||||
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigListResult[]>>,
|
||||
show(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigShowResult>>
|
||||
}
|
||||
},
|
||||
postgres: {
|
||||
server: {
|
||||
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerListResult[]>>,
|
||||
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerShowResult>>,
|
||||
edit(
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean,
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
coordinatorEngineSettings?: string,
|
||||
engineSettings?: string,
|
||||
extensions?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
port?: number,
|
||||
replaceEngineSettings?: boolean,
|
||||
workerEngineSettings?: string,
|
||||
workers?: number
|
||||
},
|
||||
additionalEnvVars?: AdditionalEnvVars,
|
||||
azdataContext?: string
|
||||
): Promise<AzdataOutput<void>>
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
mi: {
|
||||
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiListResult[]>>,
|
||||
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiShowResult>>,
|
||||
edit(
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
},
|
||||
additionalEnvVars?: AdditionalEnvVars,
|
||||
azdataContext?: string
|
||||
): Promise<AzdataOutput<void>>
|
||||
list(additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcConfigListResult[]>>,
|
||||
show(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcConfigShowResult>>
|
||||
}
|
||||
}
|
||||
},
|
||||
postgres: {
|
||||
arcserver: {
|
||||
delete(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<void>>,
|
||||
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<PostgresServerListResult[]>>,
|
||||
show(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<PostgresServerShowResult>>,
|
||||
edit(
|
||||
name: string,
|
||||
args: {
|
||||
adminPassword?: boolean,
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
coordinatorEngineSettings?: string,
|
||||
engineSettings?: string,
|
||||
extensions?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
port?: number,
|
||||
replaceEngineSettings?: boolean,
|
||||
workerEngineSettings?: string,
|
||||
workers?: number
|
||||
},
|
||||
namespace?: string,
|
||||
additionalEnvVars?: AdditionalEnvVars
|
||||
): Promise<AzOutput<void>>
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
miarc: {
|
||||
delete(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<void>>,
|
||||
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<SqlMiListResult[]>>,
|
||||
show(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<SqlMiShowResult>>,
|
||||
edit(
|
||||
name: string,
|
||||
args: {
|
||||
coresLimit?: string,
|
||||
coresRequest?: string,
|
||||
memoryLimit?: string,
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
},
|
||||
namespace?: string,
|
||||
additionalEnvVars?: AdditionalEnvVars
|
||||
): Promise<AzOutput<void>>
|
||||
}
|
||||
},
|
||||
getPath(): Promise<string>,
|
||||
login(endpointOrNamespace: EndpointOrNamespace, username: string, password: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||
/**
|
||||
* The semVersion corresponding to this installation of azdata. version() method should have been run
|
||||
* The semVersion corresponding to this installation of az. version() method should have been run
|
||||
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
|
||||
* Az has gotten reinstalled in the background after this IAzApi object was constructed.
|
||||
*/
|
||||
getSemVersion(): Promise<SemVer>,
|
||||
version(): Promise<AzdataOutput<string>>
|
||||
version(): Promise<AzOutput<string>>
|
||||
}
|
||||
|
||||
export interface IExtension {
|
||||
azdata: IAzdataApi;
|
||||
|
||||
/**
|
||||
* returns true if AZDATA CLI EULA has been previously accepted by the user.
|
||||
*/
|
||||
isEulaAccepted(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
|
||||
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
|
||||
*
|
||||
* pre-requisite, the calling code has to ensure that the EULA has not yet been previously accepted by the user. The code can use @see isEulaAccepted() call to ascertain this.
|
||||
* returns true if the user accepted the EULA.
|
||||
*/
|
||||
promptForEula(requireUserAction?: boolean): Promise<boolean>;
|
||||
|
||||
az: IAzApi;
|
||||
}
|
||||
}
|
||||
@@ -185,10 +185,10 @@ declare module 'azdata-ext' {
|
||||
},
|
||||
spec: {
|
||||
engine: {
|
||||
extensions: {
|
||||
extensions?: {
|
||||
name: string // "citus"
|
||||
}[],
|
||||
settings: {
|
||||
settings?: {
|
||||
default: { [key: string]: string }, // { "max_connections": "101", "work_mem": "4MB" }
|
||||
roles: {
|
||||
coordinator: { [key: string]: string },
|
||||
@@ -208,7 +208,7 @@ declare module 'azdata-ext' {
|
||||
limits: SchedulingOptions
|
||||
}
|
||||
},
|
||||
roles: {
|
||||
roles?: {
|
||||
coordinator: {
|
||||
resources: {
|
||||
requests: SchedulingOptions,
|
||||
|
||||
@@ -17,7 +17,8 @@ const externals = {
|
||||
'utf-8-validate': 'commonjs utf-8-validate',
|
||||
'keytar': 'commonjs keytar',
|
||||
'@azure/arm-subscriptions': 'commonjs @azure/arm-subscriptions',
|
||||
'@azure/arm-resourcegraph': 'commonjs @azure/arm-resourcegraph'
|
||||
'@azure/arm-resourcegraph': 'commonjs @azure/arm-resourcegraph',
|
||||
'@azure/storage-blob': 'commonjs @azure/storage-blob'
|
||||
};
|
||||
|
||||
// conditionally add ws if we are going to be running in a node environment
|
||||
|
||||
@@ -332,6 +332,7 @@
|
||||
"dependencies": {
|
||||
"@azure/arm-resourcegraph": "^4.0.0",
|
||||
"@azure/arm-subscriptions": "^3.0.0",
|
||||
"@azure/storage-blob": "^12.6.0",
|
||||
"axios": "^0.21.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"qs": "^6.9.1",
|
||||
|
||||
@@ -18,7 +18,8 @@ const enum SettingIds {
|
||||
ossrdbms = 'ossrdbms',
|
||||
vault = 'vault',
|
||||
ado = 'ado',
|
||||
ala = 'ala'
|
||||
ala = 'ala',
|
||||
storage = 'storage'
|
||||
}
|
||||
|
||||
const publicAzureSettings: ProviderSettings = {
|
||||
@@ -74,6 +75,12 @@ const publicAzureSettings: ProviderSettings = {
|
||||
endpoint: 'https://api.loganalytics.io',
|
||||
azureResourceId: AzureResource.AzureLogAnalytics,
|
||||
},
|
||||
azureStorageResource: {
|
||||
id: SettingIds.storage,
|
||||
endpoint: '',
|
||||
endpointSuffix: '.core.windows.net',
|
||||
azureResourceId: AzureResource.AzureStorage
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
|
||||
scopes: [
|
||||
'openid', 'email', 'profile', 'offline_access',
|
||||
@@ -128,6 +135,12 @@ const usGovAzureSettings: ProviderSettings = {
|
||||
endpoint: 'https://api.loganalytics.us',
|
||||
azureResourceId: AzureResource.AzureLogAnalytics,
|
||||
},
|
||||
azureStorageResource: {
|
||||
id: SettingIds.storage,
|
||||
endpoint: '',
|
||||
endpointSuffix: '.core.usgovcloudapi.net',
|
||||
azureResourceId: AzureResource.AzureStorage
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
|
||||
scopes: [
|
||||
'openid', 'email', 'profile', 'offline_access',
|
||||
@@ -181,6 +194,12 @@ const usNatAzureSettings: ProviderSettings = {
|
||||
endpoint: 'https://api.loganalytics.azure.eaglex.ic.gov',
|
||||
azureResourceId: AzureResource.AzureLogAnalytics,
|
||||
},
|
||||
azureStorageResource: {
|
||||
id: SettingIds.storage,
|
||||
endpoint: '',
|
||||
endpointSuffix: '.core.eaglex.ic.gov',
|
||||
azureResourceId: AzureResource.AzureStorage
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
|
||||
scopes: [
|
||||
'openid', 'email', 'profile', 'offline_access',
|
||||
@@ -220,7 +239,18 @@ const germanyAzureSettings: ProviderSettings = {
|
||||
endpoint: 'https://vault.microsoftazure.de',
|
||||
azureResourceId: AzureResource.AzureKeyVault
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/'
|
||||
azureStorageResource: {
|
||||
id: SettingIds.storage,
|
||||
endpoint: '',
|
||||
endpointSuffix: '.core.cloudapi.de',
|
||||
azureResourceId: AzureResource.AzureStorage
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
|
||||
scopes: [
|
||||
'openid', 'email', 'profile', 'offline_access',
|
||||
'https://management.microsoftazure.de/user_impersonation'
|
||||
],
|
||||
portalEndpoint: 'https://portal.microsoftazure.de/'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -231,8 +261,13 @@ const chinaAzureSettings: ProviderSettings = {
|
||||
displayName: localize('chinaCloudDisplayName', "Azure (China)"),
|
||||
id: 'azure_chinaCloud',
|
||||
settings: {
|
||||
host: 'https://login.chinacloudapi.cn/',
|
||||
host: 'https://login.partner.microsoftonline.cn/',
|
||||
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
|
||||
microsoftResource: {
|
||||
id: SettingIds.marm,
|
||||
endpoint: 'https://management.core.chinacloudapi.cn/',
|
||||
azureResourceId: AzureResource.MicrosoftResourceManagement
|
||||
},
|
||||
graphResource: {
|
||||
id: SettingIds.graph,
|
||||
endpoint: 'https://graph.chinacloudapi.cn',
|
||||
@@ -245,9 +280,14 @@ const chinaAzureSettings: ProviderSettings = {
|
||||
},
|
||||
armResource: {
|
||||
id: SettingIds.arm,
|
||||
endpoint: 'https://managemement.chinacloudapi.net',
|
||||
endpoint: 'https://management.chinacloudapi.cn',
|
||||
azureResourceId: AzureResource.ResourceManagement
|
||||
},
|
||||
sqlResource: {
|
||||
id: SettingIds.sql,
|
||||
endpoint: 'https://database.chinacloudapi.cn/',
|
||||
azureResourceId: AzureResource.Sql
|
||||
},
|
||||
azureKeyVaultResource: {
|
||||
id: SettingIds.vault,
|
||||
endpoint: 'https://vault.azure.cn',
|
||||
@@ -258,8 +298,18 @@ const chinaAzureSettings: ProviderSettings = {
|
||||
endpoint: 'https://api.loganalytics.azure.cn',
|
||||
azureResourceId: AzureResource.AzureLogAnalytics,
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/'
|
||||
|
||||
azureStorageResource: {
|
||||
id: SettingIds.storage,
|
||||
endpoint: '',
|
||||
endpointSuffix: '.core.chinacloudapi.cn',
|
||||
azureResourceId: AzureResource.AzureStorage
|
||||
},
|
||||
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
|
||||
scopes: [
|
||||
'openid', 'email', 'profile', 'offline_access',
|
||||
'https://management.chinacloudapi.cn/user_impersonation'
|
||||
],
|
||||
portalEndpoint: 'https://portal.azure.cn/'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
declare module 'azureResource' {
|
||||
import { TreeDataProvider } from 'vscode';
|
||||
import { DataProvider, Account, TreeItem } from 'azdata';
|
||||
import { BlobItem } from '@azure/storage-blob';
|
||||
|
||||
export namespace azureResource {
|
||||
|
||||
/**
|
||||
@@ -149,5 +151,7 @@ declare module 'azureResource' {
|
||||
export interface BlobContainer extends AzureResource { }
|
||||
|
||||
export interface FileShare extends AzureResource { }
|
||||
|
||||
export interface Blob extends BlobItem { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { azureResource } from 'azureResource';
|
||||
|
||||
export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabaseServer> {
|
||||
private static readonly containerId = 'azure.resource.providers.AzureMonitorContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Azure Monitor Workspace");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Log Analytics workspace");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
|
||||
@@ -50,7 +50,10 @@ export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<a
|
||||
providerName: 'LOGANALYTICS',
|
||||
saveProfile: false,
|
||||
options: {},
|
||||
azureAccount: account.key.accountId
|
||||
azureAccount: account.key.accountId,
|
||||
azureTenantId: databaseServer.tenant,
|
||||
azureResourceId: databaseServer.id,
|
||||
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
|
||||
},
|
||||
childProvider: 'LOGANALYTICS',
|
||||
type: ExtensionNodeType.Server
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user