mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-02 18:47:32 -05:00
Compare commits
345 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13e3627627 | ||
|
|
e2111fe493 | ||
|
|
7b46269b44 | ||
|
|
69b9a19634 | ||
|
|
ebe835ec99 | ||
|
|
d4e25f4d89 | ||
|
|
809d4de862 | ||
|
|
e1aae951a3 | ||
|
|
bdd99bd0d8 | ||
|
|
c06bfad916 | ||
|
|
1c91f7971e | ||
|
|
8c9037fbdf | ||
|
|
d029bb9602 | ||
|
|
f0558714a4 | ||
|
|
49147305e8 | ||
|
|
519012c690 | ||
|
|
ff05bc2b03 | ||
|
|
b54beb6e7a | ||
|
|
dbdcc3d20a | ||
|
|
cf48776710 | ||
|
|
309f750b92 | ||
|
|
8c92af3016 | ||
|
|
9b117da9cb | ||
|
|
d12a7b81fd | ||
|
|
3eb705e77b | ||
|
|
b421b19b73 | ||
|
|
124f7ca887 | ||
|
|
c2d2cf5a82 | ||
|
|
13ad4c9497 | ||
|
|
684dfc9760 | ||
|
|
fde5caa9a4 | ||
|
|
0d3112ef35 | ||
|
|
e7be1daf5c | ||
|
|
30a2b76faf | ||
|
|
ce6ea8af41 | ||
|
|
a00ffa0469 | ||
|
|
37af88e265 | ||
|
|
4c2969d4ca | ||
|
|
97ce7b9b67 | ||
|
|
ef8396f783 | ||
|
|
fc13fb2200 | ||
|
|
2164bc0bb9 | ||
|
|
5151f337bc | ||
|
|
6ab9844909 | ||
|
|
d6e0a679f7 | ||
|
|
eb5405d116 | ||
|
|
0fa9689345 | ||
|
|
1ce10cd3f3 | ||
|
|
f7b9ca775a | ||
|
|
e762f19815 | ||
|
|
00d2fadb7d | ||
|
|
cb619f55c7 | ||
|
|
28577baa87 | ||
|
|
8aa222d1c8 | ||
|
|
b774f09b6c | ||
|
|
4df77c73bf | ||
|
|
f4e1f85e0f | ||
|
|
af4ad1fcb1 | ||
|
|
d3f9277139 | ||
|
|
7dfafe1393 | ||
|
|
a13e924a14 | ||
|
|
a231d2aa82 | ||
|
|
69361b5c97 | ||
|
|
896d0b0ea1 | ||
|
|
bdbe4fb6de | ||
|
|
4c5a4215f8 | ||
|
|
dd5adad772 | ||
|
|
ca19f08582 | ||
|
|
bbee4a1f38 | ||
|
|
39a47b0053 | ||
|
|
e080770c19 | ||
|
|
4d78aefe57 | ||
|
|
e0f24cc268 | ||
|
|
4b5aac57d3 | ||
|
|
5317d9ae0b | ||
|
|
b7e982e78a | ||
|
|
15f7b12849 | ||
|
|
94c0795fc7 | ||
|
|
5db6857c49 | ||
|
|
edac96a624 | ||
|
|
f61288d29e | ||
|
|
f12c8cd5d3 | ||
|
|
2b7535f377 | ||
|
|
f59e9b5695 | ||
|
|
130a439abc | ||
|
|
dee5720676 | ||
|
|
92e9a423a0 | ||
|
|
0fb01b5b34 | ||
|
|
b148b91a9c | ||
|
|
689144a526 | ||
|
|
a3681dc0f9 | ||
|
|
4f255647ac | ||
|
|
e6a81d01cc | ||
|
|
7d94e247f6 | ||
|
|
de494f97ef | ||
|
|
be9d18428f | ||
|
|
6c54059f89 | ||
|
|
5c67f3dbed | ||
|
|
e2b6972ba9 | ||
|
|
f125e014fa | ||
|
|
8d0131b624 | ||
|
|
2d43cca3c8 | ||
|
|
0d951e1ef1 | ||
|
|
339d908d1d | ||
|
|
780ca84f9a | ||
|
|
8fb54710fb | ||
|
|
1a74d0f3d4 | ||
|
|
72295d46c2 | ||
|
|
98ba49304e | ||
|
|
6ee321a719 | ||
|
|
45fd89d6da | ||
|
|
128d382c91 | ||
|
|
2086ca6039 | ||
|
|
a5d2870344 | ||
|
|
d658af153d | ||
|
|
ce39b4bd19 | ||
|
|
69d593007e | ||
|
|
8aa8cb2cc6 | ||
|
|
adc0f3e96d | ||
|
|
8068de5938 | ||
|
|
c65c856d2f | ||
|
|
31ce58a8fc | ||
|
|
af19f93b78 | ||
|
|
172f428789 | ||
|
|
a0a97d1611 | ||
|
|
11d4ea3232 | ||
|
|
5da4690be8 | ||
|
|
88b7960d01 | ||
|
|
03d2ac7206 | ||
|
|
07bd534453 | ||
|
|
947633f244 | ||
|
|
de91969b14 | ||
|
|
c7cca7e9f0 | ||
|
|
5917f869ef | ||
|
|
f4d305ec16 | ||
|
|
3d78fe02a4 | ||
|
|
04d9b54c49 | ||
|
|
a12f85dd02 | ||
|
|
7ffa7a9b23 | ||
|
|
4425b2afd3 | ||
|
|
a4fae7c1d8 | ||
|
|
150dec7211 | ||
|
|
89e5ee3c80 | ||
|
|
784d76b886 | ||
|
|
ff766a8a14 | ||
|
|
95fa61ee4e | ||
|
|
0ecc053377 | ||
|
|
97813b4c20 | ||
|
|
318559dcd7 | ||
|
|
7a4852b047 | ||
|
|
9beb7804e6 | ||
|
|
0209b55ea2 | ||
|
|
05d88f4971 | ||
|
|
4140d0da79 | ||
|
|
b3e141a150 | ||
|
|
9df7c6c1aa | ||
|
|
7a9de9e708 | ||
|
|
42347b4681 | ||
|
|
63828dcc15 | ||
|
|
98828aa67d | ||
|
|
d6cd3270a8 | ||
|
|
90a5d9d817 | ||
|
|
138fa3feb6 | ||
|
|
7ea09d3d17 | ||
|
|
8acc28e344 | ||
|
|
f83925ce38 | ||
|
|
b670ffc7e6 | ||
|
|
0277054b1f | ||
|
|
1c671676bf | ||
|
|
063953f743 | ||
|
|
bba3b93c6e | ||
|
|
cf862854c5 | ||
|
|
6c877a4e20 | ||
|
|
29c02e5746 | ||
|
|
50ac3b0fdf | ||
|
|
46f805a8be | ||
|
|
e97429cb74 | ||
|
|
03a0d71486 | ||
|
|
ee2988f5fd | ||
|
|
6389a5b0b0 | ||
|
|
bfa8ec0301 | ||
|
|
c05ba883c4 | ||
|
|
c33116c625 | ||
|
|
973f151e07 | ||
|
|
4b462a41ab | ||
|
|
0906030aa2 | ||
|
|
46dd49d69a | ||
|
|
a4297a2922 | ||
|
|
447e573911 | ||
|
|
b5e66a715f | ||
|
|
e2a5859155 | ||
|
|
bbdc324f17 | ||
|
|
0bea923c0a | ||
|
|
809d35f173 | ||
|
|
16556b8316 | ||
|
|
9d1db372f0 | ||
|
|
dba5880f35 | ||
|
|
cecc83c89d | ||
|
|
972b649beb | ||
|
|
0ef99ab42a | ||
|
|
f125b9b2c7 | ||
|
|
733c3628a1 | ||
|
|
1d60287795 | ||
|
|
63c59ed920 | ||
|
|
df06afa2ab | ||
|
|
89c3207c94 | ||
|
|
a17a4a585e | ||
|
|
b82942a030 | ||
|
|
f6f45bc3f9 | ||
|
|
d2faf9075d | ||
|
|
21019f7452 | ||
|
|
1167e247af | ||
|
|
eef8f0e2ba | ||
|
|
4e1c7decde | ||
|
|
8e07a6f239 | ||
|
|
99d46917e8 | ||
|
|
9cfba8e8e0 | ||
|
|
69a35b38b2 | ||
|
|
0141db80bc | ||
|
|
ccde5123fa | ||
|
|
9c886dd80a | ||
|
|
8c04266ff4 | ||
|
|
8e2fd2ac84 | ||
|
|
74629c951d | ||
|
|
2620d060dc | ||
|
|
42fba14d88 | ||
|
|
6ecacd6faa | ||
|
|
ae55da3c35 | ||
|
|
cea55560e8 | ||
|
|
625c4520da | ||
|
|
56dcd4ba50 | ||
|
|
3f0ca8b714 | ||
|
|
36e228ebf7 | ||
|
|
7940714d00 | ||
|
|
f2ae5419bb | ||
|
|
1e67388653 | ||
|
|
18bdb0f37d | ||
|
|
3c38e0cc8b | ||
|
|
8b34e034fc | ||
|
|
9e02123330 | ||
|
|
cf08963fc1 | ||
|
|
7d53e1c185 | ||
|
|
9273572d5a | ||
|
|
ff508b1bf4 | ||
|
|
af3373bc4b | ||
|
|
14b9628b52 | ||
|
|
9432330c46 | ||
|
|
48a63e1f50 | ||
|
|
8ce32215ba | ||
|
|
5d07a3272e | ||
|
|
ad045de1f0 | ||
|
|
ac9a2dcf68 | ||
|
|
b10626d6f4 | ||
|
|
c6c65000ca | ||
|
|
862d0d88fa | ||
|
|
b304fadfde | ||
|
|
a378a52b33 | ||
|
|
013ecc4ddc | ||
|
|
ab114376aa | ||
|
|
dbc655a8f5 | ||
|
|
e761eb12ef | ||
|
|
8db8027b5a | ||
|
|
9e02cf86a4 | ||
|
|
4053666bef | ||
|
|
2a125ee43b | ||
|
|
b28e845506 | ||
|
|
dbb6b71908 | ||
|
|
afa2256467 | ||
|
|
f461d2aa14 | ||
|
|
00feb955d9 | ||
|
|
71cbe3fbf0 | ||
|
|
3d5ff25d13 | ||
|
|
48c456709e | ||
|
|
4b40d66bca | ||
|
|
587ac45418 | ||
|
|
9d827869a1 | ||
|
|
94f7b329d6 | ||
|
|
c05cece683 | ||
|
|
d5385f66d3 | ||
|
|
0108da2a24 | ||
|
|
561242a0d9 | ||
|
|
948bb5bc34 | ||
|
|
e266bbd562 | ||
|
|
eb58cd74e7 | ||
|
|
2e1fe9a266 | ||
|
|
9148aa1ed5 | ||
|
|
d21ee4dc9e | ||
|
|
9daaa1c58b | ||
|
|
5ece9b968a | ||
|
|
551eb76a42 | ||
|
|
f62889002d | ||
|
|
9087a9fbb1 | ||
|
|
cd4024625f | ||
|
|
138d3f97ba | ||
|
|
18b93cf212 | ||
|
|
289dd3ba65 | ||
|
|
6ca777ad31 | ||
|
|
83da03a728 | ||
|
|
f528ffea9b | ||
|
|
eb61af2f12 | ||
|
|
8727a8e97a | ||
|
|
fcd099dd64 | ||
|
|
ce18341284 | ||
|
|
aff9adf730 | ||
|
|
b719099ad3 | ||
|
|
30f55be67d | ||
|
|
87a9ae0975 | ||
|
|
272c20bb38 | ||
|
|
d159a1eb50 | ||
|
|
05f97411fa | ||
|
|
56a6557c6e | ||
|
|
c84e092473 | ||
|
|
21fae18a13 | ||
|
|
0c1b16d4fb | ||
|
|
f4cf506290 | ||
|
|
54facabde3 | ||
|
|
0f748a60b3 | ||
|
|
238a0c60d9 | ||
|
|
415689de9f | ||
|
|
612c385725 | ||
|
|
6a09d8387e | ||
|
|
35c8a20eb3 | ||
|
|
c82e80e61f | ||
|
|
6eba54d819 | ||
|
|
56d2f6b497 | ||
|
|
c456b81071 | ||
|
|
767a197780 | ||
|
|
692336e6a7 | ||
|
|
e8d02dbc44 | ||
|
|
75cda19504 | ||
|
|
b5479d0246 | ||
|
|
7739f25f7f | ||
|
|
756454efa9 | ||
|
|
c511d1a78f | ||
|
|
5e168b0960 | ||
|
|
d8438bd720 | ||
|
|
49a989ac14 | ||
|
|
e4c5af2663 | ||
|
|
6aafc252e6 | ||
|
|
e65a4ac13d | ||
|
|
45adb7d8d7 | ||
|
|
bb29ae00c9 | ||
|
|
800cd2fa89 | ||
|
|
61f9da39fa | ||
|
|
ce69ecebc4 |
106
.github/workflows/ci.yml
vendored
106
.github/workflows/ci.yml
vendored
@@ -11,57 +11,57 @@ on:
|
|||||||
- release/*
|
- release/*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# linux:
|
linux:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# env:
|
env:
|
||||||
# CHILD_CONCURRENCY: "1"
|
CHILD_CONCURRENCY: "1"
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# steps:
|
steps:
|
||||||
# - uses: actions/checkout@v2.2.0
|
- uses: actions/checkout@v2.2.0
|
||||||
# # TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
# TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
||||||
# - run: |
|
- run: |
|
||||||
# sudo apt-get update
|
sudo apt-get update
|
||||||
# sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libkrb5-dev # {{SQL CARBON EDIT}} add kerberos dep
|
sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libkrb5-dev # {{SQL CARBON EDIT}} add kerberos dep
|
||||||
# sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||||
# sudo chmod +x /etc/init.d/xvfb
|
sudo chmod +x /etc/init.d/xvfb
|
||||||
# sudo update-rc.d xvfb defaults
|
sudo update-rc.d xvfb defaults
|
||||||
# sudo service xvfb start
|
sudo service xvfb start
|
||||||
# name: Setup Build Environment
|
name: Setup Build Environment
|
||||||
# - uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
# with:
|
with:
|
||||||
# node-version: 10
|
node-version: 12
|
||||||
# # TODO: cache node modules
|
# TODO: cache node modules
|
||||||
# # Increase timeout to get around latency issues when fetching certain packages
|
# Increase timeout to get around latency issues when fetching certain packages
|
||||||
# - run: |
|
- run: |
|
||||||
# yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
# yarn --frozen-lockfile
|
yarn --frozen-lockfile
|
||||||
# name: Install Dependencies
|
name: Install Dependencies
|
||||||
# - run: yarn electron x64
|
- run: yarn electron x64
|
||||||
# name: Download Electron
|
name: Download Electron
|
||||||
# - run: yarn gulp hygiene
|
- run: yarn gulp hygiene
|
||||||
# name: Run Hygiene Checks
|
name: Run Hygiene Checks
|
||||||
# - run: yarn strict-vscode # {{SQL CARBON EDIT}} add step
|
- run: yarn strict-vscode # {{SQL CARBON EDIT}} add step
|
||||||
# name: Run Strict Compile Options
|
name: Run Strict Compile Options
|
||||||
# # - run: yarn monaco-compile-check {{SQL CARBON EDIT}} remove step
|
# - run: yarn monaco-compile-check {{SQL CARBON EDIT}} remove step
|
||||||
# # name: Run Monaco Editor Checks
|
# name: Run Monaco Editor Checks
|
||||||
# - run: yarn valid-layers-check
|
- run: yarn valid-layers-check
|
||||||
# name: Run Valid Layers Checks
|
name: Run Valid Layers Checks
|
||||||
# - run: yarn compile
|
- run: yarn compile
|
||||||
# name: Compile Sources
|
name: Compile Sources
|
||||||
# # - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step
|
# - run: yarn download-builtin-extensions {{SQL CARBON EDIT}} remove step
|
||||||
# # name: Download Built-in Extensions
|
# name: Download Built-in Extensions
|
||||||
# - run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" --coverage --runGlob "**/sql/**/*.test.js"
|
- run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" --coverage --runGlob "**/sql/**/*.test.js"
|
||||||
# name: Run Unit Tests (Electron)
|
name: Run Unit Tests (Electron)
|
||||||
# - run: DISPLAY=:10 ./scripts/test-extensions-unit.sh
|
- run: DISPLAY=:10 ./scripts/test-extensions-unit.sh
|
||||||
# name: Run Extension Unit Tests (Electron)
|
name: Run Extension Unit Tests (Electron)
|
||||||
# # {{SQL CARBON EDIT}} Add coveralls. We merge first to get around issue where parallel builds weren't being combined correctly
|
# {{SQL CARBON EDIT}} Add coveralls. We merge first to get around issue where parallel builds weren't being combined correctly
|
||||||
# - run: node test/combineCoverage
|
- run: node test/combineCoverage
|
||||||
# name: Combine code coverage files
|
name: Combine code coverage files
|
||||||
# - name: Upload Code Coverage
|
- name: Upload Code Coverage
|
||||||
# uses: coverallsapp/github-action@v1.1.1
|
uses: coverallsapp/github-action@v1.1.1
|
||||||
# with:
|
with:
|
||||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# path-to-lcov: "test/coverage/lcov.info"
|
path-to-lcov: "test/coverage/lcov.info"
|
||||||
|
|
||||||
# Fails with cryptic error (e.g. https://github.com/microsoft/vscode/pull/90292/checks?check_run_id=433681926#step:13:9)
|
# Fails with cryptic error (e.g. https://github.com/microsoft/vscode/pull/90292/checks?check_run_id=433681926#step:13:9)
|
||||||
# - run: DISPLAY=:10 yarn test-browser --browser chromium
|
# - run: DISPLAY=:10 yarn test-browser --browser chromium
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2.2.0
|
- uses: actions/checkout@v2.2.0
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10
|
node-version: 12
|
||||||
- uses: actions/setup-python@v1
|
- uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: "2.x"
|
python-version: "2.x"
|
||||||
@@ -117,7 +117,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2.2.0
|
- uses: actions/checkout@v2.2.0
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 10
|
node-version: 12
|
||||||
# Increase timeout to get around latency issues when fetching certain packages
|
# Increase timeout to get around latency issues when fetching certain packages
|
||||||
- run: |
|
- run: |
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,5 +1,50 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## Version 1.27.0
|
||||||
|
* Release date: March 17, 2021
|
||||||
|
* Release status: General Availability
|
||||||
|
* New Notebook Features:
|
||||||
|
* Added create book dialog
|
||||||
|
* Extension Updates:
|
||||||
|
* Import
|
||||||
|
* Dacpac
|
||||||
|
* Machine Learning
|
||||||
|
* SQL Assessment
|
||||||
|
* Arc
|
||||||
|
* SQL Database Projects
|
||||||
|
* ASDE Deployment
|
||||||
|
* Bux Fixes
|
||||||
|
|
||||||
|
## Version 1.26.1
|
||||||
|
* Release date: February 25, 2021
|
||||||
|
* Release status: General Availability
|
||||||
|
* Fixes https://github.com/microsoft/azuredatastudio/issues/14382
|
||||||
|
|
||||||
|
## Version 1.26.0
|
||||||
|
* Release date: February 22, 2021
|
||||||
|
* Release status: General Availability
|
||||||
|
* Added edit Jupyter book UI support
|
||||||
|
* Improved Jupyter server start-up time by 50% on windows
|
||||||
|
* Extension Updates:
|
||||||
|
* Azure Arc
|
||||||
|
* PG dashboard enhancements
|
||||||
|
* Multi-controller support
|
||||||
|
* MIAA Dashboard will no longer prompt for SQL Server connection immediately upon opening
|
||||||
|
* Azure Data CLI
|
||||||
|
* Kusto
|
||||||
|
* Machine Learning
|
||||||
|
* Profiler
|
||||||
|
* Server Reports
|
||||||
|
* Schema Compare
|
||||||
|
* SQL Server Dacpac
|
||||||
|
* SQL Database Projects
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
## Version 1.25.3
|
||||||
|
* Release date: February 10, 2021
|
||||||
|
* Release status: General Availability
|
||||||
|
* Update Electron to 9.4.3 to incorporate critical upstream fixes
|
||||||
|
|
||||||
## Version 1.25.2
|
## Version 1.25.2
|
||||||
* Release date: January 22, 2021
|
* Release date: January 22, 2021
|
||||||
* Release status: General Availability
|
* Release status: General Availability
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -131,10 +131,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
|
|||||||
|
|
||||||
Licensed under the [Source EULA](LICENSE.txt).
|
Licensed under the [Source EULA](LICENSE.txt).
|
||||||
|
|
||||||
[win-user]: https://go.microsoft.com/fwlink/?linkid=2150927
|
[win-user]: https://go.microsoft.com/fwlink/?linkid=2157460
|
||||||
[win-system]: https://go.microsoft.com/fwlink/?linkid=2150928
|
[win-system]: https://go.microsoft.com/fwlink/?linkid=2157459
|
||||||
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2151312
|
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2157458
|
||||||
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2151311
|
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2157456
|
||||||
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2151508
|
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2157353
|
||||||
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2151407
|
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2157248
|
||||||
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2151506
|
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2157352
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
|
|||||||
angular2-grid: https://github.com/BTMorton/angular2-grid
|
angular2-grid: https://github.com/BTMorton/angular2-grid
|
||||||
angular2-slickgrid: https://github.com/Microsoft/angular2-slickgrid
|
angular2-slickgrid: https://github.com/Microsoft/angular2-slickgrid
|
||||||
applicationinsights: https://github.com/Microsoft/ApplicationInsights-node.js
|
applicationinsights: https://github.com/Microsoft/ApplicationInsights-node.js
|
||||||
axios: https://github.com/axios/axios
|
axios: https://github.com/axios/axios
|
||||||
bootstrap: https://github.com/twbs/bootstrap
|
bootstrap: https://github.com/twbs/bootstrap
|
||||||
chart.js: https://github.com/Timer/chartjs
|
chart.js: https://github.com/Timer/chartjs
|
||||||
chokidar: https://github.com/paulmillr/chokidar
|
chokidar: https://github.com/paulmillr/chokidar
|
||||||
@@ -39,7 +39,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
|
|||||||
jschardet: https://github.com/aadsm/jschardet
|
jschardet: https://github.com/aadsm/jschardet
|
||||||
jupyter-powershell: https://github.com/vors/jupyter-powershell
|
jupyter-powershell: https://github.com/vors/jupyter-powershell
|
||||||
JupyterLab: https://github.com/jupyterlab/jupyterlab
|
JupyterLab: https://github.com/jupyterlab/jupyterlab
|
||||||
keytar: https://github.com/atom/node-keytar
|
keytar: https://github.com/atom/node-keytar
|
||||||
make-error: https://github.com/JsCommunity/make-error
|
make-error: https://github.com/JsCommunity/make-error
|
||||||
mark.js: https://github.com/julmot/mark.js
|
mark.js: https://github.com/julmot/mark.js
|
||||||
minimist: https://github.com/substack/minimist
|
minimist: https://github.com/substack/minimist
|
||||||
@@ -54,7 +54,8 @@ expressly granted herein, whether by implication, estoppel or otherwise.
|
|||||||
primeng: https://github.com/primefaces/primeng
|
primeng: https://github.com/primefaces/primeng
|
||||||
process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args
|
process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args
|
||||||
pty.js: https://github.com/chjj/pty.js
|
pty.js: https://github.com/chjj/pty.js
|
||||||
qs: https://github.com/ljharb/qs
|
pyzmq: https://github.com/zeromq/pyzmq
|
||||||
|
qs: https://github.com/ljharb/qs
|
||||||
reflect-metadata: https://github.com/rbuckton/reflect-metadata
|
reflect-metadata: https://github.com/rbuckton/reflect-metadata
|
||||||
request: https://github.com/request/request
|
request: https://github.com/request/request
|
||||||
rxjs: https://github.com/ReactiveX/RxJS
|
rxjs: https://github.com/ReactiveX/RxJS
|
||||||
@@ -1548,6 +1549,40 @@ THE SOFTWARE.
|
|||||||
=========================================
|
=========================================
|
||||||
END OF pty.js NOTICES AND INFORMATION
|
END OF pty.js NOTICES AND INFORMATION
|
||||||
|
|
||||||
|
%% PyZMQ NOTICES AND INFORMATION BEGIN HERE
|
||||||
|
=========================================
|
||||||
|
Copyright (c) 2009-2012, Brian Granger, Min Ragan-Kelley
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
Neither the name of PyZMQ nor the names of its contributors may be used to
|
||||||
|
endorse or promote products derived from this software without specific prior
|
||||||
|
written permission.
|
||||||
|
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
=========================================
|
||||||
|
END OF pyzmq NOTICES AND INFORMATION
|
||||||
|
|
||||||
%% reflect-metadata NOTICES AND INFORMATION BEGIN HERE
|
%% reflect-metadata NOTICES AND INFORMATION BEGIN HERE
|
||||||
=========================================
|
=========================================
|
||||||
Apache License
|
Apache License
|
||||||
|
|||||||
@@ -122,14 +122,14 @@ steps:
|
|||||||
displayName: Run integration tests (Electron)
|
displayName: Run integration tests (Electron)
|
||||||
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
|
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
|
||||||
|
|
||||||
# - script: |
|
- script: |
|
||||||
# set -e
|
set -e
|
||||||
# APP_ROOT=$(agent.builddirectory)/azuredatastudio-darwin-x64
|
APP_ROOT=$(agent.builddirectory)/azuredatastudio-darwin-x64
|
||||||
# APP_NAME="`ls $APP_ROOT | head -n 1`"
|
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||||
# yarn smoketest --build "$APP_ROOT/$APP_NAME" --screenshots "$(build.artifactstagingdirectory)/smokeshots" --log "$(build.artifactstagingdirectory)/logs/darwin/smoke.log"
|
yarn smoketest --build "$APP_ROOT/$APP_NAME" --screenshots "$(build.artifactstagingdirectory)/smokeshots" --log "$(build.artifactstagingdirectory)/logs/darwin/smoke.log"
|
||||||
# displayName: Run smoke tests (Electron)
|
displayName: Run smoke tests (Electron)
|
||||||
# continueOnError: true
|
continueOnError: true
|
||||||
# condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
|
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
|
||||||
|
|
||||||
# - script: |
|
# - script: |
|
||||||
# set -e
|
# set -e
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- template: linux/sql-product-build-linux.yml
|
- template: linux/sql-product-build-linux.yml
|
||||||
parameters:
|
parameters:
|
||||||
# extensionsToUnitTest: ["admin-tool-ext-win", "agent", "azdata", "azurecore", "cms", "dacpac", "import", "schema-compare", "notebook", "resource-deployment", "machine-learning", "sql-database-projects", "data-workspace"]
|
extensionsToUnitTest: ["admin-tool-ext-win", "agent", "azdata", "azurecore", "cms", "dacpac", "import", "schema-compare", "notebook", "resource-deployment", "machine-learning", "sql-database-projects", "data-workspace"]
|
||||||
extensionsToUnitTest: ["admin-tool-ext-win", "agent", "azdata", "azurecore", "cms", "dacpac", "import", "schema-compare", "resource-deployment", "machine-learning", "sql-database-projects", "data-workspace"]
|
|
||||||
timeoutInMinutes: 70
|
timeoutInMinutes: 70
|
||||||
|
|
||||||
- job: LinuxWeb
|
- job: LinuxWeb
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ steps:
|
|||||||
inputs:
|
inputs:
|
||||||
ConnectedServiceName: 'Code Signing'
|
ConnectedServiceName: 'Code Signing'
|
||||||
FolderPath: '$(agent.builddirectory)/azuredatastudio-win32-x64'
|
FolderPath: '$(agent.builddirectory)/azuredatastudio-win32-x64'
|
||||||
Pattern: '*.exe,*.node,resources/app/node_modules.asar.unpacked/*.dll,swiftshader/*.dll,d3dcompiler_47.dll,libGLESv2.dll,ffmpeg.dll,libEGL.dll,Microsoft.SqlTools.Hosting.dll,Microsoft.SqlTools.ResourceProvider.Core.dll,Microsoft.SqlTools.ResourceProvider.DefaultImpl.dll,MicrosoftSqlToolsCredentials.dll,MicrosoftSqlToolsServiceLayer.dll,Newtonsoft.Json.dll,SqlSerializationService.dll,SqlToolsResourceProviderService.dll,Microsoft.SqlServer.*.dll,Microsoft.Data.Tools.Sql.BatchParser.dll'
|
Pattern: '*.exe,*.node,resources/app/node_modules.asar.unpacked/*.dll,swiftshader/*.dll,d3dcompiler_47.dll,vulkan-1.dll,libGLESv2.dll,ffmpeg.dll,libEGL.dll,Microsoft.SqlTools.Hosting.dll,Microsoft.SqlTools.ResourceProvider.Core.dll,Microsoft.SqlTools.ResourceProvider.DefaultImpl.dll,MicrosoftSqlToolsCredentials.dll,MicrosoftSqlToolsServiceLayer.dll,Newtonsoft.Json.dll,SqlSerializationService.dll,SqlToolsResourceProviderService.dll,Microsoft.SqlServer.*.dll,Microsoft.Data.Tools.Sql.BatchParser.dll'
|
||||||
signConfigType: inlineSignParams
|
signConfigType: inlineSignParams
|
||||||
inlineOperation: |
|
inlineOperation: |
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const all = [
|
|||||||
'test/**/*',
|
'test/**/*',
|
||||||
'!test/**/out/**',
|
'!test/**/out/**',
|
||||||
'!**/node_modules/**',
|
'!**/node_modules/**',
|
||||||
'!build/actions/**/*.js', // {{ SQL CARBON EDIT }}
|
'!build/actions/**/*.js', // {{SQL CARBON EDIT}}
|
||||||
'!build/**/*' // {{SQL CARBON EDIT}}
|
'!build/**/*' // {{SQL CARBON EDIT}}
|
||||||
];
|
];
|
||||||
module.exports.all = all;
|
module.exports.all = all;
|
||||||
@@ -114,6 +114,9 @@ const indentationFilter = [
|
|||||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts',
|
'!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts',
|
||||||
'!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts',
|
'!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts',
|
||||||
'!resources/linux/snap/electron-launch',
|
'!resources/linux/snap/electron-launch',
|
||||||
|
'!extensions/markdown-language-features/media/*.js',
|
||||||
|
'!extensions/simple-browser/media/*.js',
|
||||||
|
'!resources/xlf/LocProject.json', // {{SQL CARBON EDIT}}
|
||||||
'!build/**/*' // {{SQL CARBON EDIT}}
|
'!build/**/*' // {{SQL CARBON EDIT}}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -3235,9 +3235,9 @@ string_decoder@~0.10.x:
|
|||||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||||
|
|
||||||
stringstream@~0.0.4, stringstream@~0.0.5:
|
stringstream@~0.0.4, stringstream@~0.0.5:
|
||||||
version "0.0.5"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
|
||||||
integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=
|
integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
|
||||||
|
|
||||||
strip-ansi@^3.0.0:
|
strip-ansi@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
@@ -3751,9 +3751,9 @@ xtend@~4.0.1:
|
|||||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
|
||||||
|
|
||||||
yallist@^4.0.0:
|
yallist@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
|
|||||||
@@ -60,12 +60,12 @@
|
|||||||
"git": {
|
"git": {
|
||||||
"name": "electron",
|
"name": "electron",
|
||||||
"repositoryUrl": "https://github.com/electron/electron",
|
"repositoryUrl": "https://github.com/electron/electron",
|
||||||
"commitHash": "415c1f9e9b35d9599b1a8ad1200476afa47a3323"
|
"commitHash": "ca82414364002efa665ffa7427e267adf76ed1f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"isOnlyProductionDependency": true,
|
"isOnlyProductionDependency": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "9.3.5"
|
"version": "9.4.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": {
|
"component": {
|
||||||
|
|||||||
17
extensions/arc/images/gear-colored-blue.svg
Normal file
17
extensions/arc/images/gear-colored-blue.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:url(#SVGID_1_);}
|
||||||
|
</style>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9" y1="2" x2="9" y2="20" gradientTransform="matrix(1 0 0 -1 0 20)">
|
||||||
|
<stop offset="0" style="stop-color:#32BEDD"/>
|
||||||
|
<stop offset="0.576" style="stop-color:#32CEEF"/>
|
||||||
|
<stop offset="1" style="stop-color:#32D4F5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st0" d="M18,10V7.9l-0.3-0.1l-2.2-0.7l-0.6-1.4l1.1-2.4l-1.4-1.4L14.3,2l-2,1l-1.4-0.6L10,0H7.9L7.8,0.3L7.1,2.5
|
||||||
|
L5.7,3.1L3.3,1.9L1.9,3.4l0.1,0.3l1,2.1L2.5,7.1L0,8v2.1l0.3,0.1l2.2,0.7l0.6,1.4l-1.1,2.4l1.4,1.4L3.7,16l2.1-1l1.4,0.6L8,18h2.1
|
||||||
|
l0.1-0.3l0.7-2.2l1.4-0.6l2.4,1.1l1.4-1.4L16,14.3l-1-2l0.6-1.4L18,10z M9,12.9c-2.2,0-4-1.8-4-3.9s1.8-4,4-4s3.9,1.8,3.9,4l0,0
|
||||||
|
C13,11.2,11.2,12.9,9,12.9C9,13,9,13,9,12.9z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
16
extensions/arc/images/gear-colored-gray.svg
Normal file
16
extensions/arc/images/gear-colored-gray.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:url(#SVGID_1_);}
|
||||||
|
</style>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9" y1="2" x2="9" y2="20" gradientTransform="matrix(1 0 0 -1 0 20)">
|
||||||
|
<stop offset="1.000000e-03" style="stop-color:#767676"/>
|
||||||
|
<stop offset="1" style="stop-color:#D9D9D9"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st0" d="M18,10V7.9l-0.3-0.1l-2.2-0.7l-0.6-1.4l1.1-2.4l-1.4-1.4L14.3,2l-2,1l-1.4-0.6L10,0H7.9L7.8,0.3L7.1,2.5
|
||||||
|
L5.7,3.1L3.3,1.9L1.9,3.4l0.1,0.3l1,2.1L2.5,7.1L0,8v2.1l0.3,0.1l2.2,0.7l0.6,1.4l-1.1,2.4l1.4,1.4L3.7,16l2.1-1l1.4,0.6L8,18h2.1
|
||||||
|
l0.1-0.3l0.7-2.2l1.4-0.6l2.4,1.1l1.4-1.4L16,14.3l-1-2l0.6-1.4L18,10z M9,12.9c-2.2,0-4-1.8-4-3.9s1.8-4,4-4s3.9,1.8,3.9,4l0,0
|
||||||
|
C13,11.2,11.2,12.9,9,12.9C9,13,9,13,9,12.9z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
3
extensions/arc/images/gear-monoline-black.svg
Normal file
3
extensions/arc/images/gear-monoline-black.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M13.95,7.75h0v.3h0l2,1.3-1.2,3-2.4-.3-.3.3.5,2.4-3,1.2-1.3-2h-.5l-1.3,2-3-1.2.6-2.4-.4-.3-2.4.5-1.2-3,2-1.3h0v-.4h0l-2-1.4,1.2-3,2.4.6a.8.8,0,0,1,.4-.4l-.6-2.4,3-1.2,1.3,2h.5l1.3-2,3,1.2-.5,2.4.3.4,2.4-.6,1.2,3Zm-.9,1V7.25l1.8-1.1-.7-1.7-2.1.5a1.205,1.205,0,0,0-.5-.5,1.205,1.205,0,0,0-.5-.5l.5-2.1-1.7-.7-1.1,1.8H7.25l-1.1-1.8-1.7.7.5,2.1a1.205,1.205,0,0,0-.5.5,1.205,1.205,0,0,0-.5.5l-2.1-.5-.7,1.7,1.8,1.1v1.5l-1.8,1.1.7,1.7,2.1-.5.5.5.5.5-.5,2.1,1.7.7,1.1-1.8h1.5l1.1,1.8,1.7-.7-.5-2.1.5-.5.5-.5,2.1.5.7-1.7Zm-5-3.7,1.1.2a2.2,2.2,0,0,1,.9.7,3.1,3.1,0,0,1,.7.9,4.3,4.3,0,0,1,.2,1.2,4.328,4.328,0,0,1-.2,1.1,2.2,2.2,0,0,1-.7.9,2.2,2.2,0,0,1-.9.7l-1.1.2-1.2-.2a3.1,3.1,0,0,1-.9-.7,2.2,2.2,0,0,1-.7-.9,4.328,4.328,0,0,1-.2-1.1,4.3,4.3,0,0,1,.2-1.2,3.1,3.1,0,0,1,.7-.9,3.1,3.1,0,0,1,.9-.7Zm0,5h.8l.6-.5.5-.6a2.489,2.489,0,0,0,.1-.9,2.793,2.793,0,0,0-.1-.9l-.5-.6-.6-.4a1.3,1.3,0,0,0-.8-.2,1.507,1.507,0,0,0-.9.2l-.6.4-.4.6a1.507,1.507,0,0,0-.2.9,1.3,1.3,0,0,0,.2.8l.4.6.6.5Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
3
extensions/arc/images/gear-monoline-blue.svg
Normal file
3
extensions/arc/images/gear-monoline-blue.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M13.95,7.75h0v.3h0l2,1.3-1.2,3-2.4-.3-.3.3.5,2.4-3,1.2-1.3-2h-.5l-1.3,2-3-1.2.6-2.4-.4-.3-2.4.5-1.2-3,2-1.3h0v-.4h0l-2-1.4,1.2-3,2.4.6a.8.8,0,0,1,.4-.4l-.6-2.4,3-1.2,1.3,2h.5l1.3-2,3,1.2-.5,2.4.3.4,2.4-.6,1.2,3Zm-.9,1V7.25l1.8-1.1-.7-1.7-2.1.5a1.205,1.205,0,0,0-.5-.5,1.205,1.205,0,0,0-.5-.5l.5-2.1-1.7-.7-1.1,1.8H7.25l-1.1-1.8-1.7.7.5,2.1a1.205,1.205,0,0,0-.5.5,1.205,1.205,0,0,0-.5.5l-2.1-.5-.7,1.7,1.8,1.1v1.5l-1.8,1.1.7,1.7,2.1-.5.5.5.5.5-.5,2.1,1.7.7,1.1-1.8h1.5l1.1,1.8,1.7-.7-.5-2.1.5-.5.5-.5,2.1.5.7-1.7Zm-5-3.7,1.1.2a2.2,2.2,0,0,1,.9.7,3.1,3.1,0,0,1,.7.9,4.3,4.3,0,0,1,.2,1.2,4.328,4.328,0,0,1-.2,1.1,2.2,2.2,0,0,1-.7.9,2.2,2.2,0,0,1-.9.7l-1.1.2-1.2-.2a3.1,3.1,0,0,1-.9-.7,2.2,2.2,0,0,1-.7-.9,4.328,4.328,0,0,1-.2-1.1,4.3,4.3,0,0,1,.2-1.2,3.1,3.1,0,0,1,.7-.9,3.1,3.1,0,0,1,.9-.7Zm0,5h.8l.6-.5.5-.6a2.489,2.489,0,0,0,.1-.9,2.793,2.793,0,0,0-.1-.9l-.5-.6-.6-.4a1.3,1.3,0,0,0-.8-.2,1.507,1.507,0,0,0-.9.2l-.6.4-.4.6a1.507,1.507,0,0,0-.2.9,1.3,1.3,0,0,0,.2.8l.4.6.6.5Z" fill="#0078D4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
3
extensions/arc/images/gear-monoline-white.svg
Normal file
3
extensions/arc/images/gear-monoline-white.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.9297 7.71875C13.9297 7.76562 13.9297 7.8125 13.9297 7.85938C13.9349 7.90625 13.9375 7.95312 13.9375 8C13.9375 8.04688 13.9349 8.09375 13.9297 8.14062C13.9297 8.1875 13.9297 8.23438 13.9297 8.28125L15.9531 9.53906L14.7109 12.5312L12.3906 12C12.2656 12.1354 12.1354 12.2656 12 12.3906L12.5312 14.7109L9.53906 15.9531L8.28125 13.9297C8.23438 13.9297 8.1875 13.9323 8.14062 13.9375C8.09375 13.9375 8.04688 13.9375 8 13.9375C7.95312 13.9375 7.90625 13.9375 7.85938 13.9375C7.8125 13.9323 7.76562 13.9297 7.71875 13.9297L6.46094 15.9531L3.46875 14.7109L4 12.3906C3.86458 12.2656 3.73438 12.1354 3.60938 12L1.28906 12.5312L0.046875 9.53906L2.07031 8.28125C2.07031 8.23438 2.06771 8.1875 2.0625 8.14062C2.0625 8.09375 2.0625 8.04688 2.0625 8C2.0625 7.95312 2.0625 7.90625 2.0625 7.85938C2.06771 7.8125 2.07031 7.76562 2.07031 7.71875L0.046875 6.46094L1.28906 3.46875L3.60938 4C3.73438 3.86458 3.86458 3.73438 4 3.60938L3.46875 1.28906L6.46094 0.046875L7.71875 2.07031C7.76562 2.07031 7.8125 2.07031 7.85938 2.07031C7.90625 2.0651 7.95312 2.0625 8 2.0625C8.04688 2.0625 8.09375 2.0651 8.14062 2.07031C8.1875 2.07031 8.23438 2.07031 8.28125 2.07031L9.53906 0.046875L12.5312 1.28906L12 3.60938C12.1354 3.73438 12.2656 3.86458 12.3906 4L14.7109 3.46875L15.9531 6.46094L13.9297 7.71875ZM13.0156 8.73438C13.026 8.60938 13.0365 8.48698 13.0469 8.36719C13.0573 8.24219 13.0625 8.11719 13.0625 7.99219C13.0625 7.8724 13.0573 7.75 13.0469 7.625C13.0365 7.5 13.026 7.3776 13.0156 7.25781L14.8594 6.10938L14.1875 4.48438L12.0703 4.97656C11.9089 4.77865 11.7422 4.59635 11.5703 4.42969C11.4036 4.26302 11.2214 4.09635 11.0234 3.92969L11.5156 1.8125L9.89062 1.14062L8.73438 2.98438C8.61458 2.97396 8.49219 2.96354 8.36719 2.95312C8.24219 2.94271 8.11979 2.9375 8 2.9375C7.875 2.9375 7.75 2.94271 7.625 2.95312C7.50521 2.96354 7.38281 2.97396 7.25781 2.98438L6.10938 1.14062L4.48438 1.8125L4.97656 3.92969C4.77865 4.09115 4.59635 4.25781 4.42969 4.42969C4.26302 4.59635 4.09635 4.77865 3.92969 4.97656L1.8125 4.48438L1.14062 6.10938L2.98438 7.26562C2.97396 7.39062 2.96354 7.51562 2.95312 7.64062C2.94271 7.76042 2.9375 7.88281 2.9375 8.00781C2.9375 8.1276 2.94271 8.25 2.95312 8.375C2.96354 8.5 2.97396 8.6224 2.98438 8.74219L1.14062 9.89062L1.8125 11.5156L3.92969 11.0234C4.09115 11.2214 4.25521 11.4036 4.42188 11.5703C4.59375 11.737 4.77865 11.9036 4.97656 12.0703L4.48438 14.1875L6.10938 14.8594L7.26562 13.0156C7.38542 13.026 7.50781 13.0365 7.63281 13.0469C7.75781 13.0573 7.88021 13.0625 8 13.0625C8.125 13.0625 8.2474 13.0573 8.36719 13.0469C8.49219 13.0365 8.61719 13.026 8.74219 13.0156L9.89062 14.8594L11.5156 14.1875L11.0234 12.0703C11.2214 11.9089 11.4036 11.7448 11.5703 11.5781C11.737 11.4062 11.9036 11.2214 12.0703 11.0234L14.1875 11.5156L14.8594 9.89062L13.0156 8.73438ZM8 5.0625C8.40625 5.0625 8.78646 5.14062 9.14062 5.29688C9.5 5.44792 9.8125 5.65625 10.0781 5.92188C10.3438 6.1875 10.5521 6.5 10.7031 6.85938C10.8594 7.21354 10.9375 7.59375 10.9375 8C10.9375 8.40625 10.8594 8.78906 10.7031 9.14844C10.5521 9.5026 10.3438 9.8125 10.0781 10.0781C9.8125 10.3438 9.5 10.5547 9.14062 10.7109C8.78646 10.862 8.40625 10.9375 8 10.9375C7.59375 10.9375 7.21094 10.862 6.85156 10.7109C6.4974 10.5547 6.1875 10.3438 5.92188 10.0781C5.65625 9.8125 5.44531 9.5026 5.28906 9.14844C5.13802 8.78906 5.0625 8.40625 5.0625 8C5.0625 7.59375 5.13802 7.21354 5.28906 6.85938C5.44531 6.5 5.65625 6.1875 5.92188 5.92188C6.1875 5.65625 6.4974 5.44792 6.85156 5.29688C7.21094 5.14062 7.59375 5.0625 8 5.0625ZM8 10.0625C8.28646 10.0625 8.55469 10.0104 8.80469 9.90625C9.05469 9.79688 9.27344 9.64844 9.46094 9.46094C9.64844 9.27344 9.79427 9.05469 9.89844 8.80469C10.0078 8.55469 10.0625 8.28646 10.0625 8C10.0625 7.71354 10.0078 7.44531 9.89844 7.19531C9.79427 6.94531 9.64844 6.72656 9.46094 6.53906C9.27344 6.35156 9.05469 6.20573 8.80469 6.10156C8.55469 5.99219 8.28646 5.9375 8 5.9375C7.71354 5.9375 7.44531 5.99219 7.19531 6.10156C6.94531 6.20573 6.72656 6.35156 6.53906 6.53906C6.35156 6.72656 6.20312 6.94531 6.09375 7.19531C5.98958 7.44531 5.9375 7.71354 5.9375 8C5.9375 8.28646 5.98958 8.55469 6.09375 8.80469C6.20312 9.05469 6.35156 9.27344 6.53906 9.46094C6.72656 9.64844 6.94531 9.79688 7.19531 9.90625C7.44531 10.0104 7.71354 10.0625 8 10.0625Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<svg id="a2f0dd32-c564-48d6-97d7-86323bfba35b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="b863127b-2eb8-42a1-a46b-989a6a8d258c" x1="9" y1="18" x2="9" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop offset="0" stop-color="#32bedd" />
|
|
||||||
<stop offset="0.576" stop-color="#32ceef" />
|
|
||||||
<stop offset="1" stop-color="#32d4f5" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path d="M18,9.972V7.92l-.288-.108-2.2-.72-.576-1.4,1.116-2.376-1.44-1.44-.288.144L12.276,3.06l-1.4-.576L9.972,0H7.92L7.812.288l-.72,2.2-1.4.576L3.348,1.944l-1.44,1.44.144.288L3.1,5.724l-.576,1.4L0,8.028V10.08l.288.108,2.2.72.576,1.4L1.944,14.688l1.44,1.44.288-.144L5.724,14.94l1.4.576L8.028,18H10.08l.108-.288.72-2.2,1.4-.576,2.376,1.116,1.44-1.44-.144-.288L14.94,12.276l.576-1.4ZM9,12.95A3.95,3.95,0,1,1,12.95,9,3.947,3.947,0,0,1,9,12.95Z" fill="url(#b863127b-2eb8-42a1-a46b-989a6a8d258c)" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 909 B |
@@ -1,2 +1 @@
|
|||||||
title: Azure Arc Data Services
|
title: Azure Arc Data Services
|
||||||
description: A collection of notebooks to support Azure Arc Data Services.
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
- title: Welcome
|
- title: Welcome
|
||||||
url: /readme
|
url: /readme
|
||||||
not_numbered: true
|
not_numbered: true
|
||||||
- title: Search
|
|
||||||
search: true
|
|
||||||
- title: Postgres
|
|
||||||
url: /postgres/readme
|
|
||||||
not_numbered: true
|
|
||||||
expand_sections: true
|
|
||||||
sections:
|
sections:
|
||||||
- title: TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter
|
- title: Postgres
|
||||||
url: postgres/tsg100-troubleshoot-postgres
|
url: /postgres/readme
|
||||||
|
not_numbered: true
|
||||||
|
sections:
|
||||||
|
- title: TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter
|
||||||
|
url: postgres/tsg100-troubleshoot-postgres
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
- This chapter contains notebooks for troubleshooting Postgres on Azure Arc
|
- This chapter contains notebooks for troubleshooting Postgres on Azure Arc
|
||||||
|
|
||||||
## Notebooks in this Chapter
|
|
||||||
- [TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter](tsg100-troubleshoot-postgres.ipynb)
|
|
||||||
|
|
||||||
|
|
||||||
|
[Home](../readme.md)
|
||||||
|
|
||||||
|
## Notebooks in this Chapter
|
||||||
|
|
||||||
|
- [TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter](../postgres/tsg100-troubleshoot-postgres.ipynb)
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
- title: Postgres
|
|
||||||
url: /postgres/readme
|
|
||||||
not_numbered: true
|
|
||||||
expand_sections: true
|
|
||||||
sections:
|
|
||||||
- title: TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter
|
|
||||||
url: postgres/tsg100-troubleshoot-postgres
|
|
||||||
@@ -2,7 +2,11 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter\n",
|
"TSG100 - The Azure Arc enabled PostgreSQL Hyperscale troubleshooter\n",
|
||||||
"===================================================================\n",
|
"===================================================================\n",
|
||||||
@@ -35,14 +39,17 @@
|
|||||||
"# the user will be prompted to select a server.\n",
|
"# the user will be prompted to select a server.\n",
|
||||||
"namespace = os.environ.get('POSTGRES_SERVER_NAMESPACE')\n",
|
"namespace = os.environ.get('POSTGRES_SERVER_NAMESPACE')\n",
|
||||||
"name = os.environ.get('POSTGRES_SERVER_NAME')\n",
|
"name = os.environ.get('POSTGRES_SERVER_NAME')\n",
|
||||||
"version = os.environ.get('POSTGRES_SERVER_VERSION')\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"tail_lines = 50"
|
"tail_lines = 50"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Common functions\n",
|
"### Common functions\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -63,7 +70,6 @@
|
|||||||
"import sys\n",
|
"import sys\n",
|
||||||
"import os\n",
|
"import os\n",
|
||||||
"import re\n",
|
"import re\n",
|
||||||
"import json\n",
|
|
||||||
"import platform\n",
|
"import platform\n",
|
||||||
"import shlex\n",
|
"import shlex\n",
|
||||||
"import shutil\n",
|
"import shutil\n",
|
||||||
@@ -76,11 +82,7 @@
|
|||||||
"error_hints = {} # Output in stderr where a known SOP/TSG exists which will be HINTed for further help\n",
|
"error_hints = {} # Output in stderr where a known SOP/TSG exists which will be HINTed for further help\n",
|
||||||
"install_hint = {} # The SOP to help install the executable if it cannot be found\n",
|
"install_hint = {} # The SOP to help install the executable if it cannot be found\n",
|
||||||
"\n",
|
"\n",
|
||||||
"first_run = True\n",
|
"def run(cmd, return_output=False, no_output=False, retry_count=0, base64_decode=False, return_as_json=False):\n",
|
||||||
"rules = None\n",
|
|
||||||
"debug_logging = False\n",
|
|
||||||
"\n",
|
|
||||||
"def run(cmd, return_output=False, no_output=False, retry_count=0):\n",
|
|
||||||
" \"\"\"Run shell command, stream stdout, print stderr and optionally return output\n",
|
" \"\"\"Run shell command, stream stdout, print stderr and optionally return output\n",
|
||||||
"\n",
|
"\n",
|
||||||
" NOTES:\n",
|
" NOTES:\n",
|
||||||
@@ -103,13 +105,6 @@
|
|||||||
" output = \"\"\n",
|
" output = \"\"\n",
|
||||||
" retry = False\n",
|
" retry = False\n",
|
||||||
"\n",
|
"\n",
|
||||||
" global first_run\n",
|
|
||||||
" global rules\n",
|
|
||||||
"\n",
|
|
||||||
" if first_run:\n",
|
|
||||||
" first_run = False\n",
|
|
||||||
" rules = load_rules()\n",
|
|
||||||
"\n",
|
|
||||||
" # When running `azdata sql query` on Windows, replace any \\n in \"\"\" strings, with \" \", otherwise we see:\n",
|
" # When running `azdata sql query` on Windows, replace any \\n in \"\"\" strings, with \" \", otherwise we see:\n",
|
||||||
" #\n",
|
" #\n",
|
||||||
" # ('HY090', '[HY090] [Microsoft][ODBC Driver Manager] Invalid string or buffer length (0) (SQLExecDirectW)')\n",
|
" # ('HY090', '[HY090] [Microsoft][ODBC Driver Manager] Invalid string or buffer length (0) (SQLExecDirectW)')\n",
|
||||||
@@ -172,7 +167,12 @@
|
|||||||
" if which_binary == None:\n",
|
" if which_binary == None:\n",
|
||||||
" which_binary = shutil.which(cmd_actual[0])\n",
|
" which_binary = shutil.which(cmd_actual[0])\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" # Display an install HINT, so the user can click on a SOP to install the missing binary\n",
|
||||||
|
" #\n",
|
||||||
" if which_binary == None:\n",
|
" if which_binary == None:\n",
|
||||||
|
" print(f\"The path used to search for '{cmd_actual[0]}' was:\")\n",
|
||||||
|
" print(sys.path)\n",
|
||||||
|
"\n",
|
||||||
" if user_provided_exe_name in install_hint and install_hint[user_provided_exe_name] is not None:\n",
|
" if user_provided_exe_name in install_hint and install_hint[user_provided_exe_name] is not None:\n",
|
||||||
" display(Markdown(f'HINT: Use [{install_hint[user_provided_exe_name][0]}]({install_hint[user_provided_exe_name][1]}) to resolve this issue.'))\n",
|
" display(Markdown(f'HINT: Use [{install_hint[user_provided_exe_name][0]}]({install_hint[user_provided_exe_name][1]}) to resolve this issue.'))\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -219,8 +219,6 @@
|
|||||||
" break # otherwise infinite hang, have not worked out why yet.\n",
|
" break # otherwise infinite hang, have not worked out why yet.\n",
|
||||||
" else:\n",
|
" else:\n",
|
||||||
" print(line, end='')\n",
|
" print(line, end='')\n",
|
||||||
" if rules is not None:\n",
|
|
||||||
" apply_expert_rules(line)\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
" if wait:\n",
|
" if wait:\n",
|
||||||
" p.wait()\n",
|
" p.wait()\n",
|
||||||
@@ -276,25 +274,22 @@
|
|||||||
" if line_decoded.find(error_hint[0]) != -1:\n",
|
" if line_decoded.find(error_hint[0]) != -1:\n",
|
||||||
" display(Markdown(f'HINT: Use [{error_hint[1]}]({error_hint[2]}) to resolve this issue.'))\n",
|
" display(Markdown(f'HINT: Use [{error_hint[1]}]({error_hint[2]}) to resolve this issue.'))\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # apply expert rules (to run follow-on notebooks), based on output\n",
|
|
||||||
" #\n",
|
|
||||||
" if rules is not None:\n",
|
|
||||||
" apply_expert_rules(line_decoded)\n",
|
|
||||||
"\n",
|
|
||||||
" # Verify if a transient error, if so automatically retry (recursive)\n",
|
" # Verify if a transient error, if so automatically retry (recursive)\n",
|
||||||
" #\n",
|
" #\n",
|
||||||
" if user_provided_exe_name in retry_hints:\n",
|
" if user_provided_exe_name in retry_hints:\n",
|
||||||
" for retry_hint in retry_hints[user_provided_exe_name]:\n",
|
" for retry_hint in retry_hints[user_provided_exe_name]:\n",
|
||||||
" if line_decoded.find(retry_hint) != -1:\n",
|
" if line_decoded.find(retry_hint) != -1:\n",
|
||||||
" if retry_count < MAX_RETRIES:\n",
|
" if retry_count \u003c MAX_RETRIES:\n",
|
||||||
" print(f\"RETRY: {retry_count} (due to: {retry_hint})\")\n",
|
" print(f\"RETRY: {retry_count} (due to: {retry_hint})\")\n",
|
||||||
" retry_count = retry_count + 1\n",
|
" retry_count = retry_count + 1\n",
|
||||||
" output = run(cmd, return_output=return_output, retry_count=retry_count)\n",
|
" output = run(cmd, return_output=return_output, retry_count=retry_count)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" if return_output:\n",
|
" if return_output:\n",
|
||||||
" return output\n",
|
" if base64_decode:\n",
|
||||||
" else:\n",
|
" import base64\n",
|
||||||
" return\n",
|
" return base64.b64decode(output).decode('utf-8')\n",
|
||||||
|
" else:\n",
|
||||||
|
" return output\n",
|
||||||
"\n",
|
"\n",
|
||||||
" elapsed = datetime.datetime.now().replace(microsecond=0) - start_time\n",
|
" elapsed = datetime.datetime.now().replace(microsecond=0) - start_time\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -311,78 +306,31 @@
|
|||||||
" print(f'\\nSUCCESS: {elapsed}s elapsed.\\n')\n",
|
" print(f'\\nSUCCESS: {elapsed}s elapsed.\\n')\n",
|
||||||
"\n",
|
"\n",
|
||||||
" if return_output:\n",
|
" if return_output:\n",
|
||||||
" return output\n",
|
" if base64_decode:\n",
|
||||||
"\n",
|
" import base64\n",
|
||||||
"def load_json(filename):\n",
|
" return base64.b64decode(output).decode('utf-8')\n",
|
||||||
" \"\"\"Load a json file from disk and return the contents\"\"\"\n",
|
" else:\n",
|
||||||
"\n",
|
" return output\n",
|
||||||
" with open(filename, encoding=\"utf8\") as json_file:\n",
|
|
||||||
" return json.load(json_file)\n",
|
|
||||||
"\n",
|
|
||||||
"def load_rules():\n",
|
|
||||||
" \"\"\"Load any 'expert rules' from the metadata of this notebook (.ipynb) that should be applied to the stderr of the running executable\"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" # Load this notebook as json to get access to the expert rules in the notebook metadata.\n",
|
|
||||||
" #\n",
|
|
||||||
" try:\n",
|
|
||||||
" j = load_json(\"tsg100-troubleshoot-postgres.ipynb\")\n",
|
|
||||||
" except:\n",
|
|
||||||
" pass # If the user has renamed the book, we can't load ourself. NOTE: Is there a way in Jupyter, to know your own filename?\n",
|
|
||||||
" else:\n",
|
|
||||||
" if \"metadata\" in j and \\\n",
|
|
||||||
" \"azdata\" in j[\"metadata\"] and \\\n",
|
|
||||||
" \"expert\" in j[\"metadata\"][\"azdata\"] and \\\n",
|
|
||||||
" \"expanded_rules\" in j[\"metadata\"][\"azdata\"][\"expert\"]:\n",
|
|
||||||
"\n",
|
|
||||||
" rules = j[\"metadata\"][\"azdata\"][\"expert\"][\"expanded_rules\"]\n",
|
|
||||||
"\n",
|
|
||||||
" rules.sort() # Sort rules, so they run in priority order (the [0] element). Lowest value first.\n",
|
|
||||||
"\n",
|
|
||||||
" # print (f\"EXPERT: There are {len(rules)} rules to evaluate.\")\n",
|
|
||||||
"\n",
|
|
||||||
" return rules\n",
|
|
||||||
"\n",
|
|
||||||
"def apply_expert_rules(line):\n",
|
|
||||||
" \"\"\"Determine if the stderr line passed in, matches the regular expressions for any of the 'expert rules', if so\n",
|
|
||||||
" inject a 'HINT' to the follow-on SOP/TSG to run\"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" global rules\n",
|
|
||||||
"\n",
|
|
||||||
" for rule in rules:\n",
|
|
||||||
" notebook = rule[1]\n",
|
|
||||||
" cell_type = rule[2]\n",
|
|
||||||
" output_type = rule[3] # i.e. stream or error\n",
|
|
||||||
" output_type_name = rule[4] # i.e. ename or name \n",
|
|
||||||
" output_type_value = rule[5] # i.e. SystemExit or stdout\n",
|
|
||||||
" details_name = rule[6] # i.e. evalue or text \n",
|
|
||||||
" expression = rule[7].replace(\"\\\\*\", \"*\") # Something escaped *, and put a \\ in front of it!\n",
|
|
||||||
"\n",
|
|
||||||
" if debug_logging:\n",
|
|
||||||
" print(f\"EXPERT: If rule '{expression}' satisfied', run '{notebook}'.\")\n",
|
|
||||||
"\n",
|
|
||||||
" if re.match(expression, line, re.DOTALL):\n",
|
|
||||||
"\n",
|
|
||||||
" if debug_logging:\n",
|
|
||||||
" print(\"EXPERT: MATCH: name = value: '{0}' = '{1}' matched expression '{2}', therefore HINT '{4}'\".format(output_type_name, output_type_value, expression, notebook))\n",
|
|
||||||
"\n",
|
|
||||||
" match_found = True\n",
|
|
||||||
"\n",
|
|
||||||
" display(Markdown(f'HINT: Use [{notebook}]({notebook}) to resolve this issue.'))\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print('Common functions defined successfully.')\n",
|
"# Hints for tool retry (on transient fault), known errors and install guide\n",
|
||||||
"\n",
|
|
||||||
"# Hints for binary (transient fault) retry, (known) error and install guide\n",
|
|
||||||
"#\n",
|
"#\n",
|
||||||
"retry_hints = {'kubectl': ['A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond']}\n",
|
"retry_hints = {}\n",
|
||||||
"error_hints = {'kubectl': [['no such host', 'TSG010 - Get configuration contexts', '../monitor-k8s/tsg010-get-kubernetes-contexts.ipynb'], ['No connection could be made because the target machine actively refused it', 'TSG056 - Kubectl fails with No connection could be made because the target machine actively refused it', '../repair/tsg056-kubectl-no-connection-could-be-made.ipynb']]}\n",
|
"error_hints = {}\n",
|
||||||
"install_hint = {'kubectl': ['SOP036 - Install kubectl command line interface', '../install/sop036-install-kubectl.ipynb']}"
|
"install_hint = {}\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"print('Common functions defined successfully.')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Get Postgres server"
|
"### Get Postgres server"
|
||||||
]
|
]
|
||||||
@@ -400,10 +348,11 @@
|
|||||||
"# Sets the 'server' variable to the spec of the Postgres server\n",
|
"# Sets the 'server' variable to the spec of the Postgres server\n",
|
||||||
"\n",
|
"\n",
|
||||||
"import math\n",
|
"import math\n",
|
||||||
|
"import json\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# If a server was provided, get it\n",
|
"# If a server was provided, get it\n",
|
||||||
"if namespace and name and version:\n",
|
"if namespace and name:\n",
|
||||||
" server = json.loads(run(f'kubectl get postgresql-{version} -n {namespace} {name} -o json', return_output=True))\n",
|
" server = json.loads(run(f'kubectl get postgresqls -n {namespace} {name} -o json', return_output=True))\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" # Otherwise prompt the user to select a server\n",
|
" # Otherwise prompt the user to select a server\n",
|
||||||
" servers = json.loads(run(f'kubectl get postgresqls --all-namespaces -o json', return_output=True))['items']\n",
|
" servers = json.loads(run(f'kubectl get postgresqls --all-namespaces -o json', return_output=True))['items']\n",
|
||||||
@@ -415,19 +364,18 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" pad = math.floor(math.log10(len(servers)) + 1) + 3\n",
|
" pad = math.floor(math.log10(len(servers)) + 1) + 3\n",
|
||||||
" for i, s in enumerate(servers):\n",
|
" for i, s in enumerate(servers):\n",
|
||||||
" print(f'{f\"[{i+1}]\":<{pad}}{full_name(s)}')\n",
|
" print(f'{f\"[{i+1}]\":\u003c{pad}}{full_name(s)}')\n",
|
||||||
"\n",
|
"\n",
|
||||||
" while True:\n",
|
" while True:\n",
|
||||||
" try:\n",
|
" try:\n",
|
||||||
" i = int(input('Enter the index of a server to troubleshoot: '))\n",
|
" i = int(input('Enter the index of a server'))\n",
|
||||||
" except ValueError:\n",
|
" except ValueError:\n",
|
||||||
" continue\n",
|
" continue\n",
|
||||||
"\n",
|
"\n",
|
||||||
" if i >= 1 and i <= len(servers):\n",
|
" if i \u003e= 1 and i \u003c= len(servers):\n",
|
||||||
" server = servers[i-1]\n",
|
" server = servers[i-1]\n",
|
||||||
" namespace = server['metadata']['namespace']\n",
|
" namespace = server['metadata']['namespace']\n",
|
||||||
" name = server['metadata']['name']\n",
|
" name = server['metadata']['name']\n",
|
||||||
" version = server['kind'][len('postgresql-'):]\n",
|
|
||||||
" break\n",
|
" break\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Got server {namespace}.{name}'))"
|
"display(Markdown(f'#### Got server {namespace}.{name}'))"
|
||||||
@@ -435,7 +383,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Summarize all resources"
|
"### Summarize all resources"
|
||||||
]
|
]
|
||||||
@@ -443,13 +395,15 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"uid = server['metadata']['uid']\n",
|
"uid = server['metadata']['uid']\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Server summary'))\n",
|
"display(Markdown(f'#### Server summary'))\n",
|
||||||
"run(f'kubectl get postgresql-{version} -n {namespace} {name}')\n",
|
"run(f'kubectl get postgresqls -n {namespace} {name}')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"display(Markdown(f'#### Resource summary'))\n",
|
"display(Markdown(f'#### Resource summary'))\n",
|
||||||
"run(f'kubectl get sts,pods,pvc,svc,ep -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid}')"
|
"run(f'kubectl get sts,pods,pvc,svc,ep -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid}')"
|
||||||
@@ -457,7 +411,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Troubleshoot the server"
|
"### Troubleshoot the server"
|
||||||
]
|
]
|
||||||
@@ -465,16 +423,22 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"display(Markdown(f'#### Troubleshooting server {namespace}.{name}'))\n",
|
"display(Markdown(f'#### Troubleshooting server {namespace}.{name}'))\n",
|
||||||
"run(f'kubectl describe postgresql-{version} -n {namespace} {name}')"
|
"run(f'kubectl describe postgresqls -n {namespace} {name}')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Troubleshoot the pods"
|
"### Troubleshoot the pods"
|
||||||
]
|
]
|
||||||
@@ -482,7 +446,9 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"pods = json.loads(run(f'kubectl get pods -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid} -o json', return_output=True))['items']\n",
|
"pods = json.loads(run(f'kubectl get pods -n {namespace} -l postgresqls.arcdata.microsoft.com/cluster-id={uid} -o json', return_output=True))['items']\n",
|
||||||
@@ -505,7 +471,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Troubleshoot the containers"
|
"### Troubleshoot the containers"
|
||||||
]
|
]
|
||||||
@@ -513,7 +483,9 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# Summarize and get logs from each container\n",
|
"# Summarize and get logs from each container\n",
|
||||||
@@ -521,7 +493,7 @@
|
|||||||
" pod_name = pod['metadata']['name']\n",
|
" pod_name = pod['metadata']['name']\n",
|
||||||
" cons = pod['spec']['containers']\n",
|
" cons = pod['spec']['containers']\n",
|
||||||
" con_statuses = pod['status'].get('containerStatuses', [])\n",
|
" con_statuses = pod['status'].get('containerStatuses', [])\n",
|
||||||
" display(Markdown(f'#### Troubleshooting {len(cons)} container{\"\" if len(cons) < 2 else \"s\"} '\n",
|
" display(Markdown(f'#### Troubleshooting {len(cons)} container{\"\" if len(cons) \u003c 2 else \"s\"} '\n",
|
||||||
" f'containers for pod {namespace}.{pod_name}'))\n",
|
" f'containers for pod {namespace}.{pod_name}'))\n",
|
||||||
"\n",
|
"\n",
|
||||||
" for i, con in enumerate(cons):\n",
|
" for i, con in enumerate(cons):\n",
|
||||||
@@ -537,14 +509,18 @@
|
|||||||
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines}')\n",
|
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines}')\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Get logs from the previous terminated container if one exists\n",
|
" # Get logs from the previous terminated container if one exists\n",
|
||||||
" if con_restarts > 0:\n",
|
" if con_restarts \u003e 0:\n",
|
||||||
" display(Markdown(f'#### Logs from previous terminated container {namespace}.{pod_name}/{con_name}'))\n",
|
" display(Markdown(f'#### Logs from previous terminated container {namespace}.{pod_name}/{con_name}'))\n",
|
||||||
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines} --previous')"
|
" run(f'kubectl logs -n {namespace} {pod_name} {con_name} --tail {tail_lines} --previous')"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"### Troubleshoot the PersistentVolumeClaims"
|
"### Troubleshoot the PersistentVolumeClaims"
|
||||||
]
|
]
|
||||||
@@ -552,7 +528,9 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"display(Markdown(f'#### Troubleshooting PersistentVolumeClaims'))\n",
|
"display(Markdown(f'#### Troubleshooting PersistentVolumeClaims'))\n",
|
||||||
@@ -562,10 +540,12 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"print('Notebook execution complete.')"
|
"print(\"Notebook execution is complete.\")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -576,20 +556,36 @@
|
|||||||
"name": "python3",
|
"name": "python3",
|
||||||
"display_name": "Python 3"
|
"display_name": "Python 3"
|
||||||
},
|
},
|
||||||
"azdata": {
|
"pansop": {
|
||||||
|
"related": "",
|
||||||
"test": {
|
"test": {
|
||||||
"ci": false,
|
"strategy": "",
|
||||||
"gci": false
|
"types": null,
|
||||||
},
|
"disable": {
|
||||||
"contract": {
|
"reason": "",
|
||||||
"requires": {
|
"workitems": null,
|
||||||
"kubectl": {
|
"types": null
|
||||||
"installed": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"side_effects": false
|
"target": {
|
||||||
}
|
"current": "public",
|
||||||
|
"final": "public"
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"parameters": null,
|
||||||
|
"symlink": false
|
||||||
|
},
|
||||||
|
"timeout": "0"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": "{ Name: \"\", Version: \"\"}",
|
||||||
|
"file_extension": "",
|
||||||
|
"mimetype": "",
|
||||||
|
"name": "",
|
||||||
|
"nbconvert_exporter": "",
|
||||||
|
"pygments_lexer": "",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"widgets": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Azure Arc Data Services Jupyter Book
|
|
||||||
|
|
||||||
## Chapters
|
|
||||||
|
|
||||||
1. [Postgres](postgres/readme.md) - notebooks for troubleshooting Postgres on Azure Arc.
|
|
||||||
@@ -132,13 +132,7 @@
|
|||||||
" sys.exit(f'Password is required.')\n",
|
" sys.exit(f'Password is required.')\n",
|
||||||
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
" confirm_password = getpass.getpass(prompt = 'Confirm password')\n",
|
||||||
" if arc_admin_password != confirm_password:\n",
|
" if arc_admin_password != confirm_password:\n",
|
||||||
" sys.exit(f'Passwords do not match.')\n",
|
" sys.exit(f'Passwords do not match.')"
|
||||||
"\n",
|
|
||||||
"os.environ[\"SPN_CLIENT_ID\"] = sp_client_id\n",
|
|
||||||
"os.environ[\"SPN_TENANT_ID\"] = sp_tenant_id\n",
|
|
||||||
"if \"AZDATA_NB_VAR_SP_CLIENT_SECRET\" in os.environ:\n",
|
|
||||||
" os.environ[\"SPN_CLIENT_SECRET\"] = os.environ[\"AZDATA_NB_VAR_SP_CLIENT_SECRET\"]\n",
|
|
||||||
"os.environ[\"SPN_AUTHORITY\"] = \"https://login.microsoftonline.com\""
|
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
|
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
|
||||||
@@ -188,7 +182,7 @@
|
|||||||
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
|
||||||
"if os.name == 'nt':\n",
|
"if os.name == 'nt':\n",
|
||||||
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t {os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {arc_data_controller_namespace}')\n",
|
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t {os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {arc_data_controller_namespace}')\n",
|
||||||
"run_command(f'azdata arc dc create --connectivity-mode {arc_data_controller_connectivity_mode} -n {arc_data_controller_name} -ns {arc_data_controller_namespace} -s {arc_subscription} -g {arc_resource_group} -l {arc_data_controller_location} -sc {arc_data_controller_storage_class} --profile-name {arc_profile}')\n",
|
"run_command(f'azdata arc dc create --connectivity-mode Indirect -n {arc_data_controller_name} -ns {arc_data_controller_namespace} -s {arc_subscription} -g {arc_resource_group} -l {arc_data_controller_location} -sc {arc_data_controller_storage_class} --profile-name {arc_profile}')\n",
|
||||||
"print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') "
|
"print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') "
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"os.environ[\"AZDATA_USERNAME\"] = sql_username\n",
|
"os.environ[\"AZDATA_USERNAME\"] = sql_username\n",
|
||||||
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
||||||
"cmd = f'azdata arc sql mi create -n {sql_instance_name} -scd {sql_storage_class_data} -scl {sql_storage_class_logs}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
|
"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",
|
||||||
"out=run_command()"
|
"out=run_command()"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"name": "arc",
|
"name": "arc",
|
||||||
"displayName": "%arc.displayName%",
|
"displayName": "%arc.displayName%",
|
||||||
"description": "%arc.description%",
|
"description": "%arc.description%",
|
||||||
"version": "0.7.2",
|
"version": "0.9.3",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
"icon": "images/extension.png",
|
"icon": "images/extension.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "*",
|
"vscode": "*",
|
||||||
"azdata": ">=1.26.0"
|
"azdata": ">=1.28.0"
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCommand:arc.connectToController",
|
"onCommand:arc.connectToController",
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
],
|
],
|
||||||
"resourceDeploymentTypes": [
|
"resourceDeploymentTypes": [
|
||||||
{
|
{
|
||||||
"name": "arc.control.create",
|
"name": "arc-controller",
|
||||||
"displayName": "%resource.type.azure.arc.display.name%",
|
"displayName": "%resource.type.azure.arc.display.name%",
|
||||||
"description": "%resource.type.azure.arc.description%",
|
"description": "%resource.type.azure.arc.description%",
|
||||||
"platforms": "*",
|
"platforms": "*",
|
||||||
@@ -144,6 +144,7 @@
|
|||||||
],
|
],
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
|
"name": "arc-controller",
|
||||||
"notebookWizard": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.arc.data.controller.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.arc.data.controller.ipynb",
|
||||||
"type": "new-arc-control-plane",
|
"type": "new-arc-control-plane",
|
||||||
@@ -240,99 +241,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "%arc.data.controller.connectivitymode%",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"type": "readonly_text",
|
|
||||||
"label": "%arc.data.controller.connectivitymode.description%",
|
|
||||||
"labelWidth": "600px"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "options",
|
|
||||||
"label": "%arc.data.controller.connectivitymode%",
|
|
||||||
"required": true,
|
|
||||||
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
|
||||||
"options": {
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"name": "indirect",
|
|
||||||
"displayName": "%arc.data.controller.indirect%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "direct",
|
|
||||||
"displayName": "%arc.data.controller.direct%"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"defaultValue": "%arc.data.controller.indirect%",
|
|
||||||
"optionsType": "radio"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "readonly_text",
|
|
||||||
"label": "%arc.data.controller.serviceprincipal.description%",
|
|
||||||
"labelWidth": "600px",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"text": "%arc.data.controller.readmore%",
|
|
||||||
"url": "https://docs.microsoft.com/azure/azure-arc/data/upload-metrics"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.data.controller.spclientid%",
|
|
||||||
"description": "%arc.data.controller.spclientid.description%",
|
|
||||||
"variableName": "AZDATA_NB_VAR_SP_CLIENT_ID",
|
|
||||||
"type": "text",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": "",
|
|
||||||
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
||||||
"enabled": {
|
|
||||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
|
||||||
"value": "direct"
|
|
||||||
},
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex_match",
|
|
||||||
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
|
||||||
"description": "%arc.data.controller.spclientid.validation.description%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.data.controller.spclientsecret%",
|
|
||||||
"description": "%arc.data.controller.spclientsecret.description%",
|
|
||||||
"variableName": "AZDATA_NB_VAR_SP_CLIENT_SECRET",
|
|
||||||
"type": "password",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": "",
|
|
||||||
"enabled": {
|
|
||||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
|
||||||
"value": "direct"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.data.controller.sptenantid%",
|
|
||||||
"description": "%arc.data.controller.sptenantid.description%",
|
|
||||||
"variableName": "AZDATA_NB_VAR_SP_TENANT_ID",
|
|
||||||
"type": "text",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": "",
|
|
||||||
"enabled": false,
|
|
||||||
"valueProvider": {
|
|
||||||
"providerId": "subscription-id-to-tenant-id",
|
|
||||||
"triggerField": "AZDATA_NB_VAR_ARC_SUBSCRIPTION"
|
|
||||||
},
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex_match",
|
|
||||||
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
|
||||||
"description": "%arc.data.controller.sptenantid.validation.description%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -599,12 +507,6 @@
|
|||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
|
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "%arc.data.controller.connectivitymode%",
|
|
||||||
"type": "readonly_text",
|
|
||||||
"isEvaluated": true,
|
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE)"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -618,7 +520,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.2.6"
|
"version": "20.3.2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": true
|
"when": true
|
||||||
@@ -626,7 +528,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "arc.postgres",
|
"name": "arc-postgres",
|
||||||
"displayName": "%resource.type.arc.postgres.display.name%",
|
"displayName": "%resource.type.arc.postgres.display.name%",
|
||||||
"description": "%resource.type.arc.postgres.description%",
|
"description": "%resource.type.arc.postgres.description%",
|
||||||
"platforms": "*",
|
"platforms": "*",
|
||||||
@@ -637,6 +539,7 @@
|
|||||||
],
|
],
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
|
"name": "arc-postgres",
|
||||||
"notebookWizard": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb",
|
||||||
"doneAction": {
|
"doneAction": {
|
||||||
@@ -661,7 +564,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.controller%",
|
"label": "%arc.controller%",
|
||||||
"variableName": "",
|
"variableName": "CONTROLLER_NAME",
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"editable": false,
|
"editable": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -869,7 +772,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.2.6"
|
"version": "20.3.2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "true"
|
"when": "true"
|
||||||
@@ -912,6 +815,7 @@
|
|||||||
"SQL Server"
|
"SQL Server"
|
||||||
],
|
],
|
||||||
"provider": {
|
"provider": {
|
||||||
|
"name": "azure-sql-mi_arc-mi",
|
||||||
"notebookWizard": {
|
"notebookWizard": {
|
||||||
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
|
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
|
||||||
"doneAction": {
|
"doneAction": {
|
||||||
@@ -936,7 +840,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"label": "%arc.controller%",
|
"label": "%arc.controller%",
|
||||||
"variableName": "",
|
"variableName": "CONTROLLER_NAME",
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"editable": false,
|
"editable": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -996,6 +900,21 @@
|
|||||||
{
|
{
|
||||||
"title": "%arc.sql.instance.settings.section.title%",
|
"title": "%arc.sql.instance.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"type": "options",
|
||||||
|
"label": "%arc.sql.replicas.label%",
|
||||||
|
"description": "%arc.sql.replicas.description%",
|
||||||
|
"required": true,
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_REPLICAS",
|
||||||
|
"options": {
|
||||||
|
"values": [
|
||||||
|
"1",
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"defaultValue": "1",
|
||||||
|
"optionsType": "radio"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.storage-class.data.label%",
|
"label": "%arc.storage-class.data.label%",
|
||||||
"description": "%arc.sql.storage-class.data.description%",
|
"description": "%arc.sql.storage-class.data.description%",
|
||||||
@@ -1082,7 +1001,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"version": "20.2.6"
|
"version": "20.3.2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "mi-type=arc-mi"
|
"when": "mi-type=arc-mi"
|
||||||
@@ -1112,6 +1031,11 @@
|
|||||||
"when": "mi-type=arc-mi"
|
"when": "mi-type=arc-mi"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"resourceDeploymentOptionsSources": [
|
||||||
|
{
|
||||||
|
"id": "arc.controllers"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1127,7 +1051,7 @@
|
|||||||
"@types/sinon": "^9.0.4",
|
"@types/sinon": "^9.0.4",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/yamljs": "^0.2.31",
|
"@types/yamljs": "^0.2.31",
|
||||||
"@microsoft/azdata-test": "^1.4.0",
|
"@microsoft/azdata-test": "^1.5.0",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"mocha-junit-reporter": "^1.17.0",
|
"mocha-junit-reporter": "^1.17.0",
|
||||||
"mocha-multi-reporters": "^1.1.7",
|
"mocha-multi-reporters": "^1.1.7",
|
||||||
|
|||||||
@@ -22,8 +22,7 @@
|
|||||||
"arc.data.controller.cluster.config.profile": "Config profile",
|
"arc.data.controller.cluster.config.profile": "Config profile",
|
||||||
"arc.data.controller.cluster.config.profile.loading": "Loading config profiles",
|
"arc.data.controller.cluster.config.profile.loading": "Loading config profiles",
|
||||||
"arc.data.controller.cluster.config.profile.loadingcompleted": "Loading config profiles complete",
|
"arc.data.controller.cluster.config.profile.loadingcompleted": "Loading config profiles complete",
|
||||||
"arc.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
|
"arc.data.controller.create.azureconfig.title": "Azure Configuration",
|
||||||
"arc.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
|
|
||||||
"arc.data.controller.create.controllerconfig.title": "Controller Configuration",
|
"arc.data.controller.create.controllerconfig.title": "Controller Configuration",
|
||||||
"arc.data.controller.project.details.title": "Azure details",
|
"arc.data.controller.project.details.title": "Azure details",
|
||||||
"arc.data.controller.project.details.description": "Select the subscription to manage deployed resources and costs. Use resource groups like folders to organize and manage all your resources.",
|
"arc.data.controller.project.details.description": "Select the subscription to manage deployed resources and costs. Use resource groups like folders to organize and manage all your resources.",
|
||||||
@@ -38,18 +37,6 @@
|
|||||||
"arc.data.controller.admin.account.name": "Data controller login",
|
"arc.data.controller.admin.account.name": "Data controller login",
|
||||||
"arc.data.controller.admin.account.password": "Password",
|
"arc.data.controller.admin.account.password": "Password",
|
||||||
"arc.data.controller.admin.account.confirm.password": "Confirm password",
|
"arc.data.controller.admin.account.confirm.password": "Confirm password",
|
||||||
"arc.data.controller.connectivitymode": "Connectivity Mode",
|
|
||||||
"arc.data.controller.direct": "Direct",
|
|
||||||
"arc.data.controller.indirect": "Indirect",
|
|
||||||
"arc.data.controller.serviceprincipal.description": "When deploying a controller in direct connected mode a Service Principal is required for connecting to Azure. {0} about how to create this Service Principal and assign it the correct roles.",
|
|
||||||
"arc.data.controller.spclientid": "Service Principal Client ID",
|
|
||||||
"arc.data.controller.spclientid.description": "The Client (application) ID of the created Service Principal",
|
|
||||||
"arc.data.controller.spclientid.validation.description": "The Client ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
||||||
"arc.data.controller.spclientsecret": "Service Principal Client Secret",
|
|
||||||
"arc.data.controller.spclientsecret.description": "The secret (password) of the Service Principal",
|
|
||||||
"arc.data.controller.sptenantid": "Service Principal Tenant ID",
|
|
||||||
"arc.data.controller.sptenantid.description": "The Tenant ID of the Service Principal. This must be the same as the Tenant ID of the subscription selected to create this controller for.",
|
|
||||||
"arc.data.controller.sptenantid.validation.description": "The tenant ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
||||||
"arc.data.controller.create.summary.title": "Review your configuration",
|
"arc.data.controller.create.summary.title": "Review your configuration",
|
||||||
"arc.data.controller.summary.arc.data.controller": "Azure Arc data controller",
|
"arc.data.controller.summary.arc.data.controller": "Azure Arc data controller",
|
||||||
"arc.data.controller.summary.estimated.cost.per.month": "Estimated cost per month",
|
"arc.data.controller.summary.estimated.cost.per.month": "Estimated cost per month",
|
||||||
@@ -97,6 +84,8 @@
|
|||||||
"arc.sql.invalid.instance.name": "Instance name must consist of lower case alphanumeric characters or '-', start with a letter, end with an alphanumeric character, and be 13 characters or fewer in length.",
|
"arc.sql.invalid.instance.name": "Instance name must consist of lower case alphanumeric characters or '-', start with a letter, end with an alphanumeric character, and be 13 characters or fewer in length.",
|
||||||
"arc.storage-class.dc.label": "Storage Class",
|
"arc.storage-class.dc.label": "Storage Class",
|
||||||
"arc.sql.storage-class.dc.description": "The storage class to be used for all data and logs persistent volumes for all data controller pods that require them.",
|
"arc.sql.storage-class.dc.description": "The storage class to be used for all data and logs persistent volumes for all data controller pods that require them.",
|
||||||
|
"arc.sql.replicas.label": "Replicas",
|
||||||
|
"arc.sql.replicas.description": "The number of SQL Managed Instance replicas that will be deployed in your Kubernetes cluster for high availability purposes",
|
||||||
"arc.storage-class.data.label": "Storage Class (Data)",
|
"arc.storage-class.data.label": "Storage Class (Data)",
|
||||||
"arc.sql.storage-class.data.description": "The storage class to be used for data (.mdf)",
|
"arc.sql.storage-class.data.description": "The storage class to be used for data (.mdf)",
|
||||||
"arc.postgres.storage-class.data.description": "The storage class to be used for data persistent volumes",
|
"arc.postgres.storage-class.data.description": "The storage class to be used for data persistent volumes",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import * as loc from '../localizedConstants';
|
|||||||
import { throwUnless } from './utils';
|
import { throwUnless } from './utils';
|
||||||
export interface KubeClusterContext {
|
export interface KubeClusterContext {
|
||||||
name: string;
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
isCurrentContext: boolean;
|
isCurrentContext: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ export interface KubeClusterContext {
|
|||||||
*
|
*
|
||||||
* @param configFile
|
* @param configFile
|
||||||
*/
|
*/
|
||||||
export function getKubeConfigClusterContexts(configFile: string): Promise<KubeClusterContext[]> {
|
export function getKubeConfigClusterContexts(configFile: string): KubeClusterContext[] {
|
||||||
const config: any = yamljs.load(configFile);
|
const config: any = yamljs.load(configFile);
|
||||||
const rawContexts = <any[]>config['contexts'];
|
const rawContexts = <any[]>config['contexts'];
|
||||||
throwUnless(rawContexts && rawContexts.length, loc.noContextFound(configFile));
|
throwUnless(rawContexts && rawContexts.length, loc.noContextFound(configFile));
|
||||||
@@ -26,16 +27,16 @@ export function getKubeConfigClusterContexts(configFile: string): Promise<KubeCl
|
|||||||
throwUnless(currentContext, loc.noCurrentContextFound(configFile));
|
throwUnless(currentContext, loc.noCurrentContextFound(configFile));
|
||||||
const contexts: KubeClusterContext[] = [];
|
const contexts: KubeClusterContext[] = [];
|
||||||
rawContexts.forEach(rawContext => {
|
rawContexts.forEach(rawContext => {
|
||||||
const name = <string>rawContext['name'];
|
const name = rawContext.name as string;
|
||||||
|
const namespace = rawContext.context.namespace as string;
|
||||||
throwUnless(name, loc.noNameInContext(configFile));
|
throwUnless(name, loc.noNameInContext(configFile));
|
||||||
if (name) {
|
contexts.push({
|
||||||
contexts.push({
|
name: name,
|
||||||
name: name,
|
namespace: namespace,
|
||||||
isCurrentContext: name === currentContext
|
isCurrentContext: name === currentContext
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return Promise.resolve(contexts);
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,22 +48,23 @@ export function getKubeConfigClusterContexts(configFile: string): Promise<KubeCl
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param clusterContexts
|
* @param clusterContexts
|
||||||
* @param previousClusterContext
|
* @param previousClusterContextName
|
||||||
* @param throwIfNotFound
|
* @param throwIfNotFound
|
||||||
*/
|
*/
|
||||||
export function getCurrentClusterContext(clusterContexts: KubeClusterContext[], previousClusterContext?: string, throwIfNotFound: boolean = false): string {
|
export function getCurrentClusterContext(clusterContexts: KubeClusterContext[], previousClusterContextName?: string, throwIfNotFound: boolean = false): KubeClusterContext {
|
||||||
if (previousClusterContext) {
|
if (previousClusterContextName) {
|
||||||
if (clusterContexts.find(c => c.name === previousClusterContext)) { // if previous cluster context value is found in clusters then return that value
|
const previousClusterContext = clusterContexts.find(c => c.name === previousClusterContextName);
|
||||||
|
if (previousClusterContext) { // if previous cluster context value is found in clusters then return that value
|
||||||
return previousClusterContext;
|
return previousClusterContext;
|
||||||
} else {
|
} else {
|
||||||
if (throwIfNotFound) {
|
if (throwIfNotFound) {
|
||||||
throw new Error(loc.clusterContextNotFound(previousClusterContext));
|
throw new Error(loc.clusterContextNotFound(previousClusterContextName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not previousClusterContext or throwIfNotFound was false when previousCLusterContext was not found in the clusterContexts
|
// if not previousClusterContext or throwIfNotFound was false when previousCLusterContext was not found in the clusterContexts
|
||||||
const currentClusterContext = clusterContexts.find(c => c.isCurrentContext)?.name;
|
const currentClusterContext = clusterContexts.find(c => c.isCurrentContext);
|
||||||
throwUnless(currentClusterContext !== undefined, loc.noCurrentClusterContext);
|
throwUnless(currentClusterContext !== undefined, loc.noCurrentClusterContext);
|
||||||
return currentClusterContext;
|
return currentClusterContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
/**
|
/**
|
||||||
* Deferred promise
|
* Deferred promise
|
||||||
*/
|
*/
|
||||||
export class Deferred<T> {
|
export class Deferred<T = void> {
|
||||||
promise: Promise<T>;
|
promise: Promise<T>;
|
||||||
resolve!: (value?: T | PromiseLike<T>) => void;
|
resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
reject!: (reason?: any) => void;
|
reject!: (reason?: any) => void;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
this.resolve = <any>resolve;
|
this.resolve = resolve;
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function getDatabaseStateDisplayText(state: string): string {
|
|||||||
* @returns Promise resolving to the user's input if it passed validation,
|
* @returns Promise resolving to the user's input if it passed validation,
|
||||||
* or undefined if the input box was closed for any other reason
|
* or undefined if the input box was closed for any other reason
|
||||||
*/
|
*/
|
||||||
async function promptInputBox(title: string, options: vscode.InputBoxOptions): Promise<string> {
|
async function promptInputBox(title: string, options: vscode.InputBoxOptions): Promise<string | undefined> {
|
||||||
const inputBox = vscode.window.createInputBox();
|
const inputBox = vscode.window.createInputBox();
|
||||||
inputBox.title = title;
|
inputBox.title = title;
|
||||||
inputBox.prompt = options.prompt;
|
inputBox.prompt = options.prompt;
|
||||||
@@ -118,7 +118,7 @@ async function promptInputBox(title: string, options: vscode.InputBoxOptions): P
|
|||||||
inputBox.value = options.value ?? '';
|
inputBox.value = options.value ?? '';
|
||||||
inputBox.ignoreFocusOut = options.ignoreFocusOut ?? false;
|
inputBox.ignoreFocusOut = options.ignoreFocusOut ?? false;
|
||||||
|
|
||||||
return new Promise<any>(resolve => {
|
return new Promise(resolve => {
|
||||||
let valueAccepted = false;
|
let valueAccepted = false;
|
||||||
inputBox.onDidAccept(async () => {
|
inputBox.onDidAccept(async () => {
|
||||||
if (options.validateInput) {
|
if (options.validateInput) {
|
||||||
@@ -184,6 +184,30 @@ export async function promptAndConfirmPassword(validate: (input: string) => stri
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateGuid(): string {
|
||||||
|
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||||
|
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||||
|
let oct: string = '';
|
||||||
|
let tmp: number;
|
||||||
|
/* tslint:disable:no-bitwise */
|
||||||
|
for (let a: number = 0; a < 4; a++) {
|
||||||
|
tmp = (4294967296 * Math.random()) | 0;
|
||||||
|
oct += hexValues[tmp & 0xF] +
|
||||||
|
hexValues[tmp >> 4 & 0xF] +
|
||||||
|
hexValues[tmp >> 8 & 0xF] +
|
||||||
|
hexValues[tmp >> 12 & 0xF] +
|
||||||
|
hexValues[tmp >> 16 & 0xF] +
|
||||||
|
hexValues[tmp >> 20 & 0xF] +
|
||||||
|
hexValues[tmp >> 24 & 0xF] +
|
||||||
|
hexValues[tmp >> 28 & 0xF];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||||
|
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||||
|
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||||
|
/* tslint:enable:no-bitwise */
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the message to display for a given error object that may be a variety of types.
|
* Gets the message to display for a given error object that may be a variety of types.
|
||||||
* @param error The error object
|
* @param error The error object
|
||||||
@@ -198,12 +222,16 @@ export function getErrorMessage(error: any, useMessageWithLink: boolean = false)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an address into its separate ip and port values. Address must be in the form <ip>:<port>
|
* Parses an address into its separate ip and port values. Address must be in the form <ip>:<port>
|
||||||
|
* or <ip>,<port>
|
||||||
* @param address The address to parse
|
* @param address The address to parse
|
||||||
*/
|
*/
|
||||||
export function parseIpAndPort(address: string): { ip: string, port: string } {
|
export function parseIpAndPort(address: string): { ip: string, port: string } {
|
||||||
const sections = address.split(':');
|
let sections = address.split(':');
|
||||||
if (sections.length !== 2) {
|
if (sections.length !== 2) {
|
||||||
throw new Error(`Invalid address format for ${address}. Address must be in the form <ip>:<port>`);
|
sections = address.split(',');
|
||||||
|
if (sections.length !== 2) {
|
||||||
|
throw new Error(`Invalid address format for ${address}. Address must be in the form <ip>:<port> or <ip>,<port>`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ip: sections[0],
|
ip: sections[0],
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ export class IconPathHelper {
|
|||||||
public static discard: IconPath;
|
public static discard: IconPath;
|
||||||
public static fail: IconPath;
|
public static fail: IconPath;
|
||||||
public static information: IconPath;
|
public static information: IconPath;
|
||||||
public static gear: IconPath;
|
public static gearBlue: IconPath;
|
||||||
|
public static gearGray: IconPath;
|
||||||
|
|
||||||
public static setExtensionContext(context: vscode.ExtensionContext) {
|
public static setExtensionContext(context: vscode.ExtensionContext) {
|
||||||
IconPathHelper.context = context;
|
IconPathHelper.context = context;
|
||||||
@@ -141,9 +142,13 @@ export class IconPathHelper {
|
|||||||
light: context.asAbsolutePath('images/information.svg'),
|
light: context.asAbsolutePath('images/information.svg'),
|
||||||
dark: context.asAbsolutePath('images/information.svg'),
|
dark: context.asAbsolutePath('images/information.svg'),
|
||||||
};
|
};
|
||||||
IconPathHelper.gear = {
|
IconPathHelper.gearBlue = {
|
||||||
light: context.asAbsolutePath('images/gear.svg'),
|
light: context.asAbsolutePath('images/gear-colored-blue.svg'),
|
||||||
dark: context.asAbsolutePath('images/gear.svg'),
|
dark: context.asAbsolutePath('images/gear-colored-blue.svg'),
|
||||||
|
};
|
||||||
|
IconPathHelper.gearGray = {
|
||||||
|
light: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
||||||
|
dark: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
|||||||
vscode.window.registerTreeDataProvider('azureArc', treeDataProvider);
|
vscode.window.registerTreeDataProvider('azureArc', treeDataProvider);
|
||||||
|
|
||||||
vscode.commands.registerCommand('arc.createController', async () => {
|
vscode.commands.registerCommand('arc.createController', async () => {
|
||||||
await vscode.commands.executeCommand('azdata.resource.deploy', 'arc.control.create', ['arc.control.create']);
|
await vscode.commands.executeCommand('azdata.resource.deploy', 'arc-controller', ['arc-controller']);
|
||||||
});
|
});
|
||||||
|
|
||||||
vscode.commands.registerCommand('arc.connectToController', async () => {
|
vscode.commands.registerCommand('arc.connectToController', async () => {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export const settings = localize('arc.settings', "Settings");
|
|||||||
export const security = localize('arc.security', "Security");
|
export const security = localize('arc.security', "Security");
|
||||||
export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage");
|
export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage");
|
||||||
export const nodeParameters = localize('arc.nodeParameters', "Node Parameters");
|
export const nodeParameters = localize('arc.nodeParameters', "Node Parameters");
|
||||||
|
export const coordinatorNodeParameters = localize('arc.coordinatorNodeParameters', "Coordinator Node Parameters");
|
||||||
|
export const workerNodeParameters = localize('arc.workerNodeParameters', "Worker Node Parameters");
|
||||||
export const compute = localize('arc.compute', "Compute");
|
export const compute = localize('arc.compute', "Compute");
|
||||||
export const backup = localize('arc.backup', "Backup");
|
export const backup = localize('arc.backup', "Backup");
|
||||||
export const newSupportRequest = localize('arc.newSupportRequest', "New support request");
|
export const newSupportRequest = localize('arc.newSupportRequest', "New support request");
|
||||||
@@ -66,7 +68,9 @@ export const feedback = localize('arc.feedback', "Feedback");
|
|||||||
export const selectConnectionString = localize('arc.selectConnectionString', "Select from available client connection strings below.");
|
export const selectConnectionString = localize('arc.selectConnectionString', "Select from available client connection strings below.");
|
||||||
export const addingWorkerNodes = localize('arc.addingWorkerNodes', "adding worker nodes");
|
export const addingWorkerNodes = localize('arc.addingWorkerNodes', "adding worker nodes");
|
||||||
export const workerNodesDescription = localize('arc.workerNodesDescription', "Expand your server group and scale your database by adding worker nodes.");
|
export const workerNodesDescription = localize('arc.workerNodesDescription', "Expand your server group and scale your database by adding worker nodes.");
|
||||||
export const postgresConfigurationInformation = localize('arc.postgres.configurationInformation', "You can configure the number of CPU cores and storage size that will apply to both worker nodes and coordinator node. Each worker node will have the same configuration. Adjust the number of CPU cores and memory settings for your server group.");
|
export const postgresConfigurationInformation = localize('arc.postgres.configurationInformation', "You can configure the number of CPU cores and storage size that will apply to both worker nodes and coordinator node. Each worker node will have the same configuration. Adjust the number of CPU cores and memory settings for your server group. To reset the requests and/or limits, pass in empty value.");
|
||||||
|
export const workerNodesConfigurationInformation = localize('arc.workerNodesConfigurationInformation', "You can configure the number of CPU cores and storage size that will apply to all worker nodes. Adjust the number of CPU cores and memory settings for your server group. To reset the requests and/or limits, pass in empty value.");
|
||||||
|
export const coordinatorNodeConfigurationInformation = localize('arc.coordinatorNodeConfigurationInformation', "You can configure the number of CPU cores and storage size that will apply to the coordinator node. Adjust the number of CPU cores and memory settings for your server group. To reset the requests and/or limits, pass in empty value.");
|
||||||
export const workerNodesInformation = localize('arc.workerNodeInformation', "In preview it is not possible to reduce the number of worker nodes. Please refer to documentation linked above for more information.");
|
export const workerNodesInformation = localize('arc.workerNodeInformation', "In preview it is not possible to reduce the number of worker nodes. Please refer to documentation linked above for more information.");
|
||||||
export const vCores = localize('arc.vCores', "vCores");
|
export const vCores = localize('arc.vCores', "vCores");
|
||||||
export const ram = localize('arc.ram', "RAM");
|
export const ram = localize('arc.ram', "RAM");
|
||||||
@@ -75,7 +79,10 @@ export const resetAllToDefault = localize('arc.resetAllToDefault', "Reset all to
|
|||||||
export const resetToDefault = localize('arc.resetToDefault', "Reset to default");
|
export const resetToDefault = localize('arc.resetToDefault', "Reset to default");
|
||||||
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
|
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
|
||||||
export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal.");
|
export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal.");
|
||||||
|
export const supportRequestNote = localize('arc.supportRequestNote', "Note that the resource configuration must have been uploaded to Azure first in order to open a support request.");
|
||||||
export const running = localize('arc.running', "Running");
|
export const running = localize('arc.running', "Running");
|
||||||
|
export const ready = localize('arc.ready', "Ready");
|
||||||
|
export const notReady = localize('arc.notReady', "Not Ready");
|
||||||
export const pending = localize('arc.pending', "Pending");
|
export const pending = localize('arc.pending', "Pending");
|
||||||
export const failed = localize('arc.failed', "Failed");
|
export const failed = localize('arc.failed', "Failed");
|
||||||
export const unknown = localize('arc.unknown', "Unknown");
|
export const unknown = localize('arc.unknown', "Unknown");
|
||||||
@@ -90,13 +97,18 @@ export function connectToMSSql(name: string): string { return localize('arc.conn
|
|||||||
export function connectToPGSql(name: string): string { return localize('arc.connectToPGSql', "Connect to PostgreSQL Hyperscale - Azure Arc ({0})", name); }
|
export function connectToPGSql(name: string): string { return localize('arc.connectToPGSql', "Connect to PostgreSQL Hyperscale - Azure Arc ({0})", name); }
|
||||||
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
||||||
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
||||||
|
export const controllerUrlPlaceholder = localize('arc.controllerUrlPlaceholder', "https://<IP or hostname>:<port>");
|
||||||
|
export const controllerUrlDescription = localize('arc.controllerUrlDescription', "The Controller URL is necessary if there are multiple clusters with the same namespace - this should generally not be necessary.");
|
||||||
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
|
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
|
||||||
export const controllerName = localize('arc.controllerName', "Name");
|
export const controllerName = localize('arc.controllerName', "Name");
|
||||||
|
export const controllerNameDescription = localize('arc.controllerNameDescription', "The name to display in the tree view, this is not applied to the controller itself.");
|
||||||
export const controllerKubeConfig = localize('arc.controllerKubeConfig', "Kube Config File Path");
|
export const controllerKubeConfig = localize('arc.controllerKubeConfig', "Kube Config File Path");
|
||||||
export const controllerClusterContext = localize('arc.controllerClusterContext', "Cluster Context");
|
export const controllerClusterContext = localize('arc.controllerClusterContext', "Cluster Context");
|
||||||
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
||||||
export const postgresProviderName = localize('arc.postgresProviderName', "PGSQL");
|
export const postgresProviderName = localize('arc.postgresProviderName', "PGSQL");
|
||||||
export const miaaProviderName = localize('arc.miaaProviderName', "MSSQL");
|
export const miaaProviderName = localize('arc.miaaProviderName', "MSSQL");
|
||||||
|
export const controllerUsername = localize('arc.controllerUsername', "Controller Username");
|
||||||
|
export const controllerPassword = localize('arc.controllerPassword', "Controller Password");
|
||||||
export const username = localize('arc.username', "Username");
|
export const username = localize('arc.username', "Username");
|
||||||
export const password = localize('arc.password', "Password");
|
export const password = localize('arc.password', "Password");
|
||||||
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
||||||
@@ -128,12 +140,16 @@ export const postgresArcProductName = localize('arc.postgresArcProductName', "Az
|
|||||||
export const coordinator = localize('arc.coordinator', "Coordinator");
|
export const coordinator = localize('arc.coordinator', "Coordinator");
|
||||||
export const worker = localize('arc.worker', "Worker");
|
export const worker = localize('arc.worker', "Worker");
|
||||||
export const monitor = localize('arc.monitor', "Monitor");
|
export const monitor = localize('arc.monitor', "Monitor");
|
||||||
|
export const available = localize('arc.available', "Available");
|
||||||
|
export const issuesDetected = localize('arc.issuesDetected', "Issues Detected");
|
||||||
export const newDatabase = localize('arc.newDatabase', "New Database");
|
export const newDatabase = localize('arc.newDatabase', "New Database");
|
||||||
export const databaseName = localize('arc.databaseName', "Database name");
|
export const databaseName = localize('arc.databaseName', "Database name");
|
||||||
export const enterNewPassword = localize('arc.enterNewPassword', "Enter a new password");
|
export const enterNewPassword = localize('arc.enterNewPassword', "Enter a new password");
|
||||||
export const confirmNewPassword = localize('arc.confirmNewPassword', "Confirm the new password");
|
export const confirmNewPassword = localize('arc.confirmNewPassword', "Confirm the new password");
|
||||||
export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients', "Learn more about Azure PostgreSQL Hyperscale client interfaces");
|
export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients', "Learn more about Azure PostgreSQL Hyperscale client interfaces");
|
||||||
export const nodeParametersDescription = localize('arc.nodeParametersDescription', " These server parameters of the Coordinator node and the Worker nodes can be set to custom (non-default) values. Search to find parameters.");
|
export const nodeParametersDescription = localize('arc.nodeParametersDescription', " These server parameters of the Coordinator node and the Worker nodes can be set to custom (non-default) values. Search to find parameters.");
|
||||||
|
export const coordinatorNodeParametersDescription = localize('arc.coordinatorNodeParametersDescription', " These server parameters of the Coordinator node can be set to custom (non-default) values. Search to find parameters.");
|
||||||
|
export const workerNodesParametersDescription = localize('arc.workerNodesParametersDescription', " These server parameters of the Worker nodes can be set to custom (non-default) values. Search to find parameters.");
|
||||||
export const learnAboutNodeParameters = localize('arc.learnAboutNodeParameters', "Learn more about database engine settings for Azure Arc enabled PostgreSQL Hyperscale");
|
export const learnAboutNodeParameters = localize('arc.learnAboutNodeParameters', "Learn more about database engine settings for Azure Arc enabled PostgreSQL Hyperscale");
|
||||||
export const noNodeParametersFound = localize('arc.noNodeParametersFound', "No worker server parameters found...");
|
export const noNodeParametersFound = localize('arc.noNodeParametersFound', "No worker server parameters found...");
|
||||||
export const searchToFilter = localize('arc.searchToFilter', "Search to filter items...");
|
export const searchToFilter = localize('arc.searchToFilter', "Search to filter items...");
|
||||||
@@ -144,35 +160,41 @@ export const postgresComputeAndStorageDescriptionPartTwo = localize('arc.postgre
|
|||||||
export const computeAndStorageDescriptionPartThree = localize('arc.computeAndStorageDescriptionPartThree', "without downtime and by");
|
export const computeAndStorageDescriptionPartThree = localize('arc.computeAndStorageDescriptionPartThree', "without downtime and by");
|
||||||
export const computeAndStorageDescriptionPartFour = localize('arc.computeAndStorageDescriptionPartFour', "Before doing so, you need to ensure");
|
export const computeAndStorageDescriptionPartFour = localize('arc.computeAndStorageDescriptionPartFour', "Before doing so, you need to ensure");
|
||||||
export const computeAndStorageDescriptionPartFive = localize('arc.computeAndStorageDescriptionPartFive', "there are sufficient resources available");
|
export const computeAndStorageDescriptionPartFive = localize('arc.computeAndStorageDescriptionPartFive', "there are sufficient resources available");
|
||||||
|
export const resourceHealthDescription = localize('arc.resourceHealthDescription', "Resource health can tell you if your resource is running as expected.");
|
||||||
export const computeAndStorageDescriptionPartSix = localize('arc.computeAndStorageDescriptionPartSix', "in your Kubernetes cluster to honor this configuration.");
|
export const computeAndStorageDescriptionPartSix = localize('arc.computeAndStorageDescriptionPartSix', "in your Kubernetes cluster to honor this configuration.");
|
||||||
export const node = localize('arc.node', "node");
|
export const node = localize('arc.node', "node");
|
||||||
export const nodes = localize('arc.nodes', "nodes");
|
export const nodes = localize('arc.nodes', "nodes");
|
||||||
export const workerNodes = localize('arc.workerNodes', "Worker Nodes");
|
export const workerNodes = localize('arc.workerNodes', "Worker Nodes");
|
||||||
|
export const coordinatorNode = localize('arc.coordinatorNode', "Coordinator Node");
|
||||||
export const storagePerNode = localize('arc.storagePerNode', "storage per node");
|
export const storagePerNode = localize('arc.storagePerNode', "storage per node");
|
||||||
export const workerNodeCount = localize('arc.workerNodeCount', "Worker node count:");
|
export const workerNodeCount = localize('arc.workerNodeCount', "Worker node count:");
|
||||||
export const configurationPerNode = localize('arc.configurationPerNode', "Configuration (per node)");
|
export const configurationPerNode = localize('arc.configurationPerNode', "Configuration (per node)");
|
||||||
|
export const configuration = localize('arc.configurationCoordinatorNode', "Configuration");
|
||||||
export const coresLimit = localize('arc.coresLimit', "CPU limit:");
|
export const coresLimit = localize('arc.coresLimit', "CPU limit:");
|
||||||
export const coresRequest = localize('arc.coresRequest', "CPU request:");
|
export const coresRequest = localize('arc.coresRequest', "CPU request:");
|
||||||
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB):");
|
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB):");
|
||||||
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB):");
|
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB):");
|
||||||
export const workerValidationErrorMessage = localize('arc.workerValidationErrorMessage', "The number of workers cannot be decreased.");
|
|
||||||
export const memoryRequestValidationErrorMessage = localize('arc.memoryRequestValidationErrorMessage', "Memory request must be at least 0.25Gib");
|
|
||||||
export const memoryLimitValidationErrorMessage = localize('arc.memoryLimitValidationErrorMessage', "Memory limit must be at least 0.25Gib");
|
|
||||||
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
|
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
|
||||||
export const enterANonEmptyPassword = localize('arc.enterANonEmptyPassword', "Enter a non empty password or press escape to exit.");
|
export const enterANonEmptyPassword = localize('arc.enterANonEmptyPassword', "Enter a non empty password or press escape to exit.");
|
||||||
export const thePasswordsDoNotMatch = localize('arc.thePasswordsDoNotMatch', "The passwords do not match. Confirm the password or press escape to exit.");
|
export const thePasswordsDoNotMatch = localize('arc.thePasswordsDoNotMatch', "The passwords do not match. Confirm the password or press escape to exit.");
|
||||||
export const passwordReset = localize('arc.passwordReset', "Password reset successfully");
|
export const passwordReset = localize('arc.passwordReset', "Password reset successfully");
|
||||||
export const podOverview = localize('arc.podOverview', "Pod overview");
|
|
||||||
export const condition = localize('arc.condition', "Condition");
|
export const condition = localize('arc.condition', "Condition");
|
||||||
export const details = localize('arc.details', "Details");
|
export const details = localize('arc.details', "Details");
|
||||||
export const lastUpdated = localize('arc.lastUpdated', "Last updated");
|
export const lastTransition = localize('arc.lastTransition', "Last transition");
|
||||||
export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available.");
|
export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available.");
|
||||||
export const podsReady = localize('arc.podsReady', "pods ready");
|
export 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 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 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 postgresExtension = localize('arc.postgresExtension', "microsoft.azuredatastudio-postgresql");
|
||||||
|
export const podInitialized = localize('arc.podInitialized', "Pod is initialized.");
|
||||||
|
export const podReady = localize('arc.podReady', "Pod is ready.");
|
||||||
|
export const noPodIssuesDetected = localize('arc.noPodIssuesDetected', "There aren’t any known issues affecting this PostgreSQL Hyperscale instance.");
|
||||||
|
export const podIssuesDetected = localize('arc.podIssuesDetected', "The pods listed below are experiencing issues that may affect performance or availability.");
|
||||||
|
export const containerReady = localize('arc.containerReady', "Pod containers are ready.");
|
||||||
|
export const podScheduled = localize('arc.podScheduled', "Pod is schedulable.");
|
||||||
|
|
||||||
export function rangeSetting(min: string, max: string): string { return localize('arc.rangeSetting', "Value is expected to be in the range {0} - {1}", min, max); }
|
export function rangeSetting(min: string, max: string): string { return localize('arc.rangeSetting', "Value is expected to be in the range {0} - {1}", min, max); }
|
||||||
export function allowedValue(value: string): string { return localize('arc.allowedValue', "Value is expected to be {0}", value); }
|
|
||||||
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
|
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
|
||||||
export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); }
|
export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); }
|
||||||
export function installingExtension(name: string): string { return localize('arc.installingExtension', "Installing extension '{0}'...", name); }
|
export function installingExtension(name: string): string { return localize('arc.installingExtension', "Installing extension '{0}'...", name); }
|
||||||
@@ -182,6 +204,9 @@ export function instanceDeleted(name: string): string { return localize('arc.ins
|
|||||||
export function instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
|
export function instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
|
||||||
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); }
|
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); }
|
||||||
export function clickTheTroubleshootButton(resourceType: string): string { return localize('arc.clickTheTroubleshootButton', "Click the troubleshoot button to open the Azure Arc {0} troubleshooting notebook.", resourceType); }
|
export function clickTheTroubleshootButton(resourceType: string): string { return localize('arc.clickTheTroubleshootButton', "Click the troubleshoot button to open the Azure Arc {0} troubleshooting notebook.", resourceType); }
|
||||||
|
export function dataStorage(value: string): string { return localize('arc.dataStorage', "{0} data", value); }
|
||||||
|
export function logStorage(value: string): string { return localize('arc.logStorage', "{0} log", value); }
|
||||||
|
export function backupsStorage(value: string): string { return localize('arc.backupsStorage', "{0} backups", value); }
|
||||||
export function numVCores(vCores: string | undefined): string {
|
export function numVCores(vCores: string | undefined): string {
|
||||||
if (vCores && +vCores > 0) {
|
if (vCores && +vCores > 0) {
|
||||||
if (+vCores === 1) {
|
if (+vCores === 1) {
|
||||||
@@ -194,13 +219,11 @@ export function numVCores(vCores: string | undefined): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
|
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
|
||||||
export function validationMin(min: number): string { return localize('arc.validationMin', "Value must be greater than or equal to {0}.", min); }
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A connection is required to show and set database engine settings.");
|
export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A connection is required to show and set database engine settings.");
|
||||||
export const miaaConnectionRequired = localize('arc.miaaConnectionRequired', "A connection is required to list the databases on this instance.");
|
export const miaaConnectionRequired = localize('arc.miaaConnectionRequired', "A connection is required to list the databases on this instance.");
|
||||||
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
|
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
|
||||||
export function outOfRange(min: string, max: string): string { return localize('arc.outOfRange', "The number must be in range {0} - {1}", min, max); }
|
|
||||||
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
|
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
|
||||||
export function resetFailed(error: any): string { return localize('arc.resetFailed', "Reset failed. {0}", getErrorMessage(error)); }
|
export function resetFailed(error: any): string { return localize('arc.resetFailed', "Reset failed. {0}", getErrorMessage(error)); }
|
||||||
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
|
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
|
||||||
@@ -218,6 +241,7 @@ export function fetchEndpointsFailed(name: string, error: any): string { return
|
|||||||
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
export function fetchEngineSettingsFailed(name: string, error: any): string { return localize('arc.fetchEngineSettingsFailed', "An unexpected error occurred retrieving the engine settings for '{0}'. {1}", name, getErrorMessage(error)); }
|
export function fetchEngineSettingsFailed(name: string, error: any): string { return localize('arc.fetchEngineSettingsFailed', "An unexpected error occurred retrieving the engine settings for '{0}'. {1}", name, getErrorMessage(error)); }
|
||||||
|
export function numberOfIssuesDetected(name: string, issues: number): string { return localize('arc.numberOfIssuesDetected', "• {0} ({1} issues)", name, issues); }
|
||||||
export function instanceDeletionWarning(name: string): string { return localize('arc.instanceDeletionWarning', "Warning! Deleting an instance is permanent and cannot be undone. To delete the instance '{0}' type the name '{0}' below to proceed.", name); }
|
export function instanceDeletionWarning(name: string): string { return localize('arc.instanceDeletionWarning', "Warning! Deleting an instance is permanent and cannot be undone. To delete the instance '{0}' type the name '{0}' below to proceed.", name); }
|
||||||
export function invalidInstanceDeletionName(name: string): string { return localize('arc.invalidInstanceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
|
export function invalidInstanceDeletionName(name: string): string { return localize('arc.invalidInstanceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
|
||||||
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ export class ControllerModel {
|
|||||||
return this._info;
|
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) {
|
public set info(value: ControllerInfo) {
|
||||||
this._info = value;
|
this._info = value;
|
||||||
this._onInfoUpdated.fire(this._info);
|
this._onInfoUpdated.fire(this._info);
|
||||||
@@ -63,10 +77,10 @@ export class ControllerModel {
|
|||||||
* calls from changing the context while commands for this session are being executed.
|
* calls from changing the context while commands for this session are being executed.
|
||||||
* @param promptReconnect
|
* @param promptReconnect
|
||||||
*/
|
*/
|
||||||
public async acquireAzdataSession(promptReconnect: boolean = false): Promise<azdataExt.AzdataSession> {
|
public async login(promptReconnect: boolean = false): Promise<void> {
|
||||||
let promptForValidClusterContext: boolean = false;
|
let promptForValidClusterContext: boolean = false;
|
||||||
try {
|
try {
|
||||||
const contexts = await getKubeConfigClusterContexts(this.info.kubeConfigFilePath);
|
const contexts = getKubeConfigClusterContexts(this.info.kubeConfigFilePath);
|
||||||
getCurrentClusterContext(contexts, this.info.kubeClusterContext, true); // this throws if this.info.kubeClusterContext is not found in 'contexts'
|
getCurrentClusterContext(contexts, this.info.kubeClusterContext, true); // this throws if this.info.kubeClusterContext is not found in 'contexts'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const response = await vscode.window.showErrorMessage(loc.clusterContextConfigNoLongerValid(this.info.kubeConfigFilePath, this.info.kubeClusterContext, error), loc.yes, loc.no);
|
const response = await vscode.window.showErrorMessage(loc.clusterContextConfigNoLongerValid(this.info.kubeConfigFilePath, this.info.kubeClusterContext, error), loc.yes, loc.no);
|
||||||
@@ -100,8 +114,7 @@ export class ControllerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await this._azdataApi.azdata.login({ endpoint: this.info.endpoint, namespace: this.info.namespace }, this.info.username, this._password, this.azdataAdditionalEnvVars);
|
||||||
return this._azdataApi.azdata.acquireSession(this.info.url, this.info.username, this._password, this.azdataAdditionalEnvVars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,67 +128,64 @@ export class ControllerModel {
|
|||||||
await this.refresh(false);
|
await this.refresh(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise<void> {
|
public async refresh(showErrors: boolean = true): Promise<void> {
|
||||||
const session = await this.acquireAzdataSession(promptReconnect);
|
// First need to log in to ensure that we're able to authenticate with the controller
|
||||||
|
await this.login(false);
|
||||||
const newRegistrations: Registration[] = [];
|
const newRegistrations: Registration[] = [];
|
||||||
try {
|
await Promise.all([
|
||||||
await Promise.all([
|
this._azdataApi.azdata.arc.dc.config.show(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||||
this._azdataApi.azdata.arc.dc.config.show(this.azdataAdditionalEnvVars, session).then(result => {
|
this._controllerConfig = result.result;
|
||||||
this._controllerConfig = result.result;
|
this.configLastUpdated = new Date();
|
||||||
this.configLastUpdated = new Date();
|
this._onConfigUpdated.fire(this._controllerConfig);
|
||||||
this._onConfigUpdated.fire(this._controllerConfig);
|
}).catch(err => {
|
||||||
}).catch(err => {
|
// If an error occurs show a message so the user knows something failed but still
|
||||||
// If an error occurs show a message so the user knows something failed but still
|
// fire the event so callers hooking into this can handle the error (e.g. so dashboards don't show the
|
||||||
// fire the event so callers hooking into this can handle the error (e.g. so dashboards don't show the
|
// loading icon forever)
|
||||||
// loading icon forever)
|
if (showErrors) {
|
||||||
if (showErrors) {
|
vscode.window.showErrorMessage(loc.fetchConfigFailed(this.info.name, err));
|
||||||
vscode.window.showErrorMessage(loc.fetchConfigFailed(this.info.name, err));
|
}
|
||||||
}
|
this._onConfigUpdated.fire(this._controllerConfig);
|
||||||
this._onConfigUpdated.fire(this._controllerConfig);
|
throw err;
|
||||||
throw err;
|
}),
|
||||||
|
this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||||
|
this._endpoints = result.result;
|
||||||
|
this.endpointsLastUpdated = new Date();
|
||||||
|
this._onEndpointsUpdated.fire(this._endpoints);
|
||||||
|
}).catch(err => {
|
||||||
|
// If an error occurs show a message so the user knows something failed but still
|
||||||
|
// fire the event so callers can know to update (e.g. so dashboards don't show the
|
||||||
|
// loading icon forever)
|
||||||
|
if (showErrors) {
|
||||||
|
vscode.window.showErrorMessage(loc.fetchEndpointsFailed(this.info.name, err));
|
||||||
|
}
|
||||||
|
this._onEndpointsUpdated.fire(this._endpoints);
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
Promise.all([
|
||||||
|
this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||||
|
newRegistrations.push(...result.result.map(r => {
|
||||||
|
return {
|
||||||
|
instanceName: r.name,
|
||||||
|
state: r.state,
|
||||||
|
instanceType: ResourceType.postgresInstances
|
||||||
|
};
|
||||||
|
}));
|
||||||
}),
|
}),
|
||||||
this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars, session).then(result => {
|
this._azdataApi.azdata.arc.sql.mi.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
|
||||||
this._endpoints = result.result;
|
newRegistrations.push(...result.result.map(r => {
|
||||||
this.endpointsLastUpdated = new Date();
|
return {
|
||||||
this._onEndpointsUpdated.fire(this._endpoints);
|
instanceName: r.name,
|
||||||
}).catch(err => {
|
state: r.state,
|
||||||
// If an error occurs show a message so the user knows something failed but still
|
instanceType: ResourceType.sqlManagedInstances
|
||||||
// fire the event so callers can know to update (e.g. so dashboards don't show the
|
};
|
||||||
// loading icon forever)
|
}));
|
||||||
if (showErrors) {
|
|
||||||
vscode.window.showErrorMessage(loc.fetchEndpointsFailed(this.info.name, err));
|
|
||||||
}
|
|
||||||
this._onEndpointsUpdated.fire(this._endpoints);
|
|
||||||
throw err;
|
|
||||||
}),
|
|
||||||
Promise.all([
|
|
||||||
this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars, session).then(result => {
|
|
||||||
newRegistrations.push(...result.result.map(r => {
|
|
||||||
return {
|
|
||||||
instanceName: r.name,
|
|
||||||
state: r.state,
|
|
||||||
instanceType: ResourceType.postgresInstances
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}),
|
|
||||||
this._azdataApi.azdata.arc.sql.mi.list(this.azdataAdditionalEnvVars, session).then(result => {
|
|
||||||
newRegistrations.push(...result.result.map(r => {
|
|
||||||
return {
|
|
||||||
instanceName: r.name,
|
|
||||||
state: r.state,
|
|
||||||
instanceType: ResourceType.sqlManagedInstances
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
]).then(() => {
|
|
||||||
this._registrations = newRegistrations;
|
|
||||||
this.registrationsLastUpdated = new Date();
|
|
||||||
this._onRegistrationsUpdated.fire(this._registrations);
|
|
||||||
})
|
})
|
||||||
]);
|
]).then(() => {
|
||||||
} finally {
|
this._registrations = newRegistrations;
|
||||||
session.dispose();
|
this.registrationsLastUpdated = new Date();
|
||||||
}
|
this._onRegistrationsUpdated.fire(this._registrations);
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get endpoints(): azdataExt.DcEndpointListResult[] {
|
public get endpoints(): azdataExt.DcEndpointListResult[] {
|
||||||
@@ -204,6 +214,6 @@ export class ControllerModel {
|
|||||||
* property to for use a display label for this controller
|
* property to for use a display label for this controller
|
||||||
*/
|
*/
|
||||||
public get label(): string {
|
public get label(): string {
|
||||||
return `${this.info.name} (${this.info.url})`;
|
return `${this.info.name} (${this.controllerContext})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,11 +71,9 @@ export class MiaaModel extends ResourceModel {
|
|||||||
return this._refreshPromise.promise;
|
return this._refreshPromise.promise;
|
||||||
}
|
}
|
||||||
this._refreshPromise = new Deferred();
|
this._refreshPromise = new Deferred();
|
||||||
let session: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
try {
|
try {
|
||||||
session = await this.controllerModel.acquireAzdataSession();
|
|
||||||
try {
|
try {
|
||||||
const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, session);
|
const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext);
|
||||||
this._config = result.result;
|
this._config = result.result;
|
||||||
this.configLastUpdated = new Date();
|
this.configLastUpdated = new Date();
|
||||||
this._onConfigUpdated.fire(this._config);
|
this._onConfigUpdated.fire(this._config);
|
||||||
@@ -109,7 +107,6 @@ export class MiaaModel extends ResourceModel {
|
|||||||
this._refreshPromise.reject(err);
|
this._refreshPromise.reject(err);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
session?.dispose();
|
|
||||||
this._refreshPromise = undefined;
|
this._refreshPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export type EngineSettingsModel = {
|
|||||||
|
|
||||||
export class PostgresModel extends ResourceModel {
|
export class PostgresModel extends ResourceModel {
|
||||||
private _config?: azdataExt.PostgresServerShowResult;
|
private _config?: azdataExt.PostgresServerShowResult;
|
||||||
public _engineSettings: EngineSettingsModel[] = [];
|
public workerNodesEngineSettings: EngineSettingsModel[] = [];
|
||||||
|
public coordinatorNodeEngineSettings: EngineSettingsModel[] = [];
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
|
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
|
||||||
@@ -52,10 +53,7 @@ export class PostgresModel extends ResourceModel {
|
|||||||
|
|
||||||
/** Returns the major version of Postgres */
|
/** Returns the major version of Postgres */
|
||||||
public get engineVersion(): string | undefined {
|
public get engineVersion(): string | undefined {
|
||||||
const kind = this._config?.kind;
|
return this._config?.spec.engine.version;
|
||||||
return kind
|
|
||||||
? kind.substring(kind.lastIndexOf('-') + 1)
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the IP address and port of Postgres */
|
/** Returns the IP address and port of Postgres */
|
||||||
@@ -75,7 +73,9 @@ export class PostgresModel extends ResourceModel {
|
|||||||
const ramLimit = this._config.spec.scheduling?.default?.resources?.limits?.memory;
|
const ramLimit = this._config.spec.scheduling?.default?.resources?.limits?.memory;
|
||||||
const cpuRequest = this._config.spec.scheduling?.default?.resources?.requests?.cpu;
|
const cpuRequest = this._config.spec.scheduling?.default?.resources?.requests?.cpu;
|
||||||
const ramRequest = this._config.spec.scheduling?.default?.resources?.requests?.memory;
|
const ramRequest = this._config.spec.scheduling?.default?.resources?.requests?.memory;
|
||||||
const storage = this._config.spec.storage?.data?.size;
|
const dataStorage = this._config.spec.storage?.data?.size;
|
||||||
|
const logStorage = this._config.spec.storage?.logs?.size;
|
||||||
|
const backupsStorage = this._config.spec.storage?.backups?.size;
|
||||||
|
|
||||||
// scale.shards was renamed to scale.workers. Check both for backwards compatibility.
|
// scale.shards was renamed to scale.workers. Check both for backwards compatibility.
|
||||||
const scale = this._config.spec.scale;
|
const scale = this._config.spec.scale;
|
||||||
@@ -93,8 +93,19 @@ export class PostgresModel extends ResourceModel {
|
|||||||
configuration.push(`${ramLimit ?? ramRequest!} ${loc.ram}`);
|
configuration.push(`${ramLimit ?? ramRequest!} ${loc.ram}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storage) {
|
let storage: string[] = [];
|
||||||
configuration.push(`${storage} ${loc.storagePerNode}`);
|
if (dataStorage) {
|
||||||
|
storage.push(loc.dataStorage(dataStorage));
|
||||||
|
}
|
||||||
|
if (logStorage) {
|
||||||
|
storage.push(loc.logStorage(logStorage));
|
||||||
|
}
|
||||||
|
if (backupsStorage) {
|
||||||
|
storage.push(loc.backupsStorage(backupsStorage));
|
||||||
|
}
|
||||||
|
if (dataStorage || logStorage || backupsStorage) {
|
||||||
|
storage.push(`${loc.storagePerNode}`);
|
||||||
|
configuration.push(storage.join(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration.join(', ');
|
return configuration.join(', ');
|
||||||
@@ -107,10 +118,8 @@ export class PostgresModel extends ResourceModel {
|
|||||||
return this._refreshPromise.promise;
|
return this._refreshPromise.promise;
|
||||||
}
|
}
|
||||||
this._refreshPromise = new Deferred();
|
this._refreshPromise = new Deferred();
|
||||||
let session: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
try {
|
try {
|
||||||
session = await this.controllerModel.acquireAzdataSession();
|
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext)).result;
|
||||||
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, session)).result;
|
|
||||||
this.configLastUpdated = new Date();
|
this.configLastUpdated = new Date();
|
||||||
this._onConfigUpdated.fire(this._config);
|
this._onConfigUpdated.fire(this._config);
|
||||||
this._refreshPromise.resolve();
|
this._refreshPromise.resolve();
|
||||||
@@ -118,7 +127,6 @@ export class PostgresModel extends ResourceModel {
|
|||||||
this._refreshPromise.reject(err);
|
this._refreshPromise.reject(err);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
session?.dispose();
|
|
||||||
this._refreshPromise = undefined;
|
this._refreshPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +145,7 @@ export class PostgresModel extends ResourceModel {
|
|||||||
this._activeConnectionId = result.connectionId;
|
this._activeConnectionId = result.connectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Need to make separate calls for worker nodes and coordinator node
|
||||||
const provider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this._connectionProfile!.providerName, azdata.DataProviderType.QueryProvider);
|
const provider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this._connectionProfile!.providerName, azdata.DataProviderType.QueryProvider);
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this._activeConnectionId);
|
const ownerUri = await azdata.connection.getUriForConnection(this._activeConnectionId);
|
||||||
|
|
||||||
@@ -150,7 +159,7 @@ export class PostgresModel extends ResourceModel {
|
|||||||
'shared_preload_libraries', 'synchronous_commit', 'ssl', 'unix_socket_permissions', 'wal_level'
|
'shared_preload_libraries', 'synchronous_commit', 'ssl', 'unix_socket_permissions', 'wal_level'
|
||||||
];
|
];
|
||||||
|
|
||||||
this._engineSettings = [];
|
this.workerNodesEngineSettings = [];
|
||||||
|
|
||||||
engineSettings.rows.forEach(row => {
|
engineSettings.rows.forEach(row => {
|
||||||
let rowValues = row.map(c => c.displayValue);
|
let rowValues = row.map(c => c.displayValue);
|
||||||
@@ -166,12 +175,12 @@ export class PostgresModel extends ResourceModel {
|
|||||||
type: rowValues.shift()
|
type: rowValues.shift()
|
||||||
};
|
};
|
||||||
|
|
||||||
this._engineSettings.push(result);
|
this.workerNodesEngineSettings.push(result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.engineSettingsLastUpdated = new Date();
|
this.engineSettingsLastUpdated = new Date();
|
||||||
this._onEngineSettingsUpdated.fire(this._engineSettings);
|
this._onEngineSettingsUpdated.fire(this.workerNodesEngineSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createConnectionProfile(): azdata.IConnectionProfile {
|
protected createConnectionProfile(): azdata.IConnectionProfile {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
|
|||||||
const controller = (await getRegisteredDataControllers(this._treeProvider)).find(ci => ci.label === controllerLabel);
|
const controller = (await getRegisteredDataControllers(this._treeProvider)).find(ci => ci.label === controllerLabel);
|
||||||
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
||||||
switch (variableName) {
|
switch (variableName) {
|
||||||
case 'endpoint': return controller.info.url;
|
case 'endpoint': return controller.info.endpoint || '';
|
||||||
case 'username': return controller.info.username;
|
case 'username': return controller.info.username;
|
||||||
case 'kubeConfig': return controller.info.kubeConfigFilePath;
|
case 'kubeConfig': return controller.info.kubeConfigFilePath;
|
||||||
case 'clusterContext': return controller.info.kubeClusterContext;
|
case 'clusterContext': return controller.info.kubeClusterContext;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ describe('KubeUtils', function (): void {
|
|||||||
contexts[1].name.should.equal('kubernetes-admin@kubernetes', `test: ${testName} failed`);
|
contexts[1].name.should.equal('kubernetes-admin@kubernetes', `test: ${testName} failed`);
|
||||||
contexts[1].isCurrentContext.should.be.false(`test: ${testName} failed`);
|
contexts[1].isCurrentContext.should.be.false(`test: ${testName} failed`);
|
||||||
};
|
};
|
||||||
verifyContexts(await getKubeConfigClusterContexts(configFile), 'getKubeConfigClusterContexts');
|
verifyContexts(getKubeConfigClusterContexts(configFile), 'getKubeConfigClusterContexts');
|
||||||
});
|
});
|
||||||
it('throws error when unable to load config file', async () => {
|
it('throws error when unable to load config file', async () => {
|
||||||
const error = new Error('unknown error accessing file');
|
const error = new Error('unknown error accessing file');
|
||||||
|
|||||||
@@ -10,77 +10,81 @@ import * as azdataExt from 'azdata-ext';
|
|||||||
*/
|
*/
|
||||||
export class FakeAzdataApi implements azdataExt.IAzdataApi {
|
export class FakeAzdataApi implements azdataExt.IAzdataApi {
|
||||||
|
|
||||||
public postgresInstances: azdataExt.PostgresServerListResult[] = [];
|
private _arcApi = {
|
||||||
public miaaInstances: azdataExt.SqlMiListResult[] = [];
|
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,
|
||||||
|
engineSettings?: string,
|
||||||
|
extensions?: string,
|
||||||
|
memoryLimit?: string,
|
||||||
|
memoryRequest?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
port?: number,
|
||||||
|
replaceEngineSettings?: boolean,
|
||||||
|
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
|
// API Implementation
|
||||||
//
|
//
|
||||||
public get arc() {
|
public get arc() {
|
||||||
const self = this;
|
return this._arcApi;
|
||||||
return {
|
|
||||||
dc: {
|
|
||||||
create(_namespace: string, _name: string, _connectivityMode: string, _resourceGroup: string, _location: string, _subscription: string, _profileName?: string, _storageClass?: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
|
||||||
endpoint: {
|
|
||||||
async list(): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> { return <any>{ result: [] }; }
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
list(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> { throw new Error('Method not implemented.'); },
|
|
||||||
async show(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> { return <any>{ result: undefined! }; }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
postgres: {
|
|
||||||
server: {
|
|
||||||
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
|
||||||
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return <any>{ result: self.postgresInstances }; },
|
|
||||||
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
|
|
||||||
edit(
|
|
||||||
_name: string,
|
|
||||||
_args: {
|
|
||||||
adminPassword?: boolean,
|
|
||||||
coresLimit?: string,
|
|
||||||
coresRequest?: string,
|
|
||||||
engineSettings?: string,
|
|
||||||
extensions?: string,
|
|
||||||
memoryLimit?: string,
|
|
||||||
memoryRequest?: string,
|
|
||||||
noWait?: boolean,
|
|
||||||
port?: number,
|
|
||||||
replaceEngineSettings?: boolean,
|
|
||||||
workers?: number
|
|
||||||
},
|
|
||||||
_engineVersion?: string,
|
|
||||||
_additionalEnvVars?: azdataExt.AdditionalEnvVars
|
|
||||||
): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sql: {
|
|
||||||
mi: {
|
|
||||||
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
|
|
||||||
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return <any>{ result: self.miaaInstances }; },
|
|
||||||
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); },
|
|
||||||
edit(
|
|
||||||
_name: string,
|
|
||||||
_args: {
|
|
||||||
coresLimit?: string,
|
|
||||||
coresRequest?: string,
|
|
||||||
memoryLimit?: string,
|
|
||||||
memoryRequest?: string,
|
|
||||||
noWait?: boolean
|
|
||||||
}): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
getPath(): Promise<string> {
|
getPath(): Promise<string> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<void>> {
|
login(_endpointOrNamespace: azdataExt.EndpointOrNamespace, _username: string, _password: string, _additionalEnvVars: azdataExt.AdditionalEnvVars = {}, _azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
|
||||||
return <any>undefined;
|
return <any>undefined;
|
||||||
}
|
}
|
||||||
acquireSession(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataSession> {
|
|
||||||
return Promise.resolve({ dispose: () => { } });
|
|
||||||
}
|
|
||||||
version(): Promise<azdataExt.AzdataOutput<string>> {
|
version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider
|
|||||||
export class FakeControllerModel extends ControllerModel {
|
export class FakeControllerModel extends ControllerModel {
|
||||||
|
|
||||||
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
|
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
|
||||||
const _info: ControllerInfo = Object.assign({ id: uuid(), url: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', username: '', rememberPassword: false, resources: [] }, info);
|
const _info: ControllerInfo = Object.assign({ id: uuid(), endpoint: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', namespace: '', username: '', rememberPassword: false, resources: [] }, info);
|
||||||
super(treeDataProvider!, _info, password);
|
super(treeDataProvider!, _info, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ interface ExtensionGlobalMemento extends vscode.Memento {
|
|||||||
setKeysForSync(keys: string[]): void;
|
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: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('ControllerModel', function (): void {
|
describe('ControllerModel', function (): void {
|
||||||
afterEach(function (): void {
|
afterEach(function (): void {
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
@@ -39,15 +53,15 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
|
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
|
||||||
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
|
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||||
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
|
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Rejected with expected error when user cancels', async function (): Promise<void> {
|
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"
|
// 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));
|
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
|
||||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
|
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||||
await should(model.acquireAzdataSession()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
|
await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Reads password from cred store', async function (): Promise<void> {
|
it('Reads password from cred store', async function (): Promise<void> {
|
||||||
@@ -62,13 +76,13 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||||
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
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);
|
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
|
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||||
|
|
||||||
await model.acquireAzdataSession();
|
await model.login();
|
||||||
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
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> {
|
it('Prompt for password when not in cred store', async function (): Promise<void> {
|
||||||
@@ -83,18 +97,18 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||||
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
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);
|
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||||
|
|
||||||
// Set up dialog to return new model with our password
|
// Set up dialog to return new model with our password
|
||||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
|
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
|
||||||
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||||
|
|
||||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
|
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||||
|
|
||||||
await model.acquireAzdataSession();
|
await model.login();
|
||||||
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
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> {
|
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
|
||||||
@@ -108,19 +122,19 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||||
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
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);
|
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||||
|
|
||||||
// Set up dialog to return new model with our new password from the reprompt
|
// Set up dialog to return new model with our new password from the reprompt
|
||||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
|
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 waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||||
|
|
||||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
|
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
|
||||||
|
|
||||||
await model.acquireAzdataSession(true);
|
await model.login(true);
|
||||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||||
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
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> {
|
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
|
||||||
@@ -134,20 +148,20 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||||
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
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);
|
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||||
|
|
||||||
// Set up dialog to return new model with our new password from the reprompt
|
// Set up dialog to return new model with our new password from the reprompt
|
||||||
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
|
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 waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
|
||||||
|
|
||||||
// Set up original model with a password
|
// Set up original model with a password
|
||||||
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword');
|
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
|
||||||
|
|
||||||
await model.acquireAzdataSession(true);
|
await model.login(true);
|
||||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
||||||
azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
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> {
|
it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
|
||||||
@@ -162,7 +176,7 @@ describe('ControllerModel', function (): void {
|
|||||||
|
|
||||||
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
|
||||||
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
|
||||||
azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
|
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);
|
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
|
||||||
|
|
||||||
@@ -170,27 +184,19 @@ describe('ControllerModel', function (): void {
|
|||||||
const originalPassword = 'originalPassword';
|
const originalPassword = 'originalPassword';
|
||||||
const model = new ControllerModel(
|
const model = new ControllerModel(
|
||||||
treeDataProvider,
|
treeDataProvider,
|
||||||
{
|
getDefaultControllerInfo(),
|
||||||
id: uuid(),
|
|
||||||
url: '127.0.0.1',
|
|
||||||
kubeConfigFilePath: '/path/to/.kube/config',
|
|
||||||
kubeClusterContext: 'currentCluster',
|
|
||||||
username: 'admin',
|
|
||||||
name: 'arc',
|
|
||||||
rememberPassword: false,
|
|
||||||
resources: []
|
|
||||||
},
|
|
||||||
originalPassword
|
originalPassword
|
||||||
);
|
);
|
||||||
await treeDataProvider.addOrUpdateController(model, originalPassword);
|
await treeDataProvider.addOrUpdateController(model, originalPassword);
|
||||||
|
|
||||||
const newInfo: ControllerInfo = {
|
const newInfo: ControllerInfo = {
|
||||||
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
|
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
|
||||||
url: 'newUrl',
|
endpoint: 'newUrl',
|
||||||
kubeConfigFilePath: '/path/to/.kube/config',
|
kubeConfigFilePath: '/path/to/.kube/config',
|
||||||
kubeClusterContext: 'currentCluster',
|
kubeClusterContext: 'currentCluster',
|
||||||
username: 'newUser',
|
username: 'newUser',
|
||||||
name: 'newName',
|
name: 'newName',
|
||||||
|
namespace: 'newNamespace',
|
||||||
rememberPassword: true,
|
rememberPassword: true,
|
||||||
resources: []
|
resources: []
|
||||||
};
|
};
|
||||||
@@ -203,7 +209,7 @@ describe('ControllerModel', function (): void {
|
|||||||
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
|
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
|
||||||
{ controllerModel: newModel, password: newPassword }));
|
{ controllerModel: newModel, password: newPassword }));
|
||||||
|
|
||||||
await model.acquireAzdataSession(true);
|
await model.login(true);
|
||||||
should(waitForCloseStub.called).be.true('waitForClose should have been called');
|
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((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');
|
should(model.info).deepEqual(newInfo, 'Model info should have been updated');
|
||||||
|
|||||||
544
extensions/arc/src/test/models/postgresModel.test.ts
Normal file
544
extensions/arc/src/test/models/postgresModel.test.ts
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 azdata from 'azdata';
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { generateGuid } from '../../common/utils';
|
||||||
|
import { UserCancelledError } from '../../common/api';
|
||||||
|
import { ControllerModel, Registration } from '../../models/controllerModel';
|
||||||
|
import { PostgresModel, EngineSettingsModel } from '../../models/postgresModel';
|
||||||
|
import { ConnectToPGSqlDialog } from '../../ui/dialogs/connectPGDialog';
|
||||||
|
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
|
||||||
|
import { FakeControllerModel } from '../mocks/fakeControllerModel';
|
||||||
|
import { FakeAzdataApi } from '../mocks/fakeAzdataApi';
|
||||||
|
|
||||||
|
export const FakePostgresServerShowOutput: azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult> = {
|
||||||
|
logs: [],
|
||||||
|
stdout: [],
|
||||||
|
stderr: [],
|
||||||
|
result: {
|
||||||
|
apiVersion: 'version',
|
||||||
|
kind: 'postgresql',
|
||||||
|
metadata: {
|
||||||
|
creationTimestamp: '',
|
||||||
|
generation: 1,
|
||||||
|
name: 'pgt',
|
||||||
|
namespace: 'ns',
|
||||||
|
resourceVersion: '',
|
||||||
|
selfLink: '',
|
||||||
|
uid: '',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
engine: {
|
||||||
|
extensions: [{ name: '' }],
|
||||||
|
settings: {
|
||||||
|
default: { ['']: '' }
|
||||||
|
},
|
||||||
|
version: ''
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
shards: 0,
|
||||||
|
workers: 0
|
||||||
|
},
|
||||||
|
scheduling: {
|
||||||
|
default: {
|
||||||
|
resources: {
|
||||||
|
requests: {
|
||||||
|
cpu: '',
|
||||||
|
memory: ''
|
||||||
|
},
|
||||||
|
limits: {
|
||||||
|
cpu: '',
|
||||||
|
memory: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
type: '',
|
||||||
|
port: 0
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
data: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
},
|
||||||
|
backups: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
externalEndpoint: '127.0.0.1:5432',
|
||||||
|
readyPods: '',
|
||||||
|
state: '',
|
||||||
|
logSearchDashboard: '',
|
||||||
|
metricsDashboard: '',
|
||||||
|
podsStatus: [{
|
||||||
|
conditions: [{
|
||||||
|
lastTransitionTime: '',
|
||||||
|
message: '',
|
||||||
|
reason: '',
|
||||||
|
status: '',
|
||||||
|
type: '',
|
||||||
|
}],
|
||||||
|
name: '',
|
||||||
|
role: '',
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('PostgresModel', function (): void {
|
||||||
|
let controllerModel: ControllerModel;
|
||||||
|
let postgresModel: PostgresModel;
|
||||||
|
let azdataApi: azdataExt.IAzdataApi;
|
||||||
|
|
||||||
|
afterEach(function (): void {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Setup Controller Model
|
||||||
|
controllerModel = new FakeControllerModel();
|
||||||
|
|
||||||
|
//Stub calling azdata login and acquiring session
|
||||||
|
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
|
||||||
|
|
||||||
|
// 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 });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refresh', function (): void {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 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));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updates model to expected config', async function (): Promise<void> {
|
||||||
|
const postgresShowStub = sinon.stub(azdataApi.arc.postgres.server, 'show').resolves(FakePostgresServerShowOutput);
|
||||||
|
|
||||||
|
await postgresModel.refresh();
|
||||||
|
sinon.assert.calledOnceWithExactly(postgresShowStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||||
|
sinon.assert.match(postgresModel.config, FakePostgresServerShowOutput.result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updates onConfigLastUpdated when model is refreshed', async function (): Promise<void> {
|
||||||
|
const postgresShowStub = sinon.stub(azdataApi.arc.postgres.server, 'show').resolves(FakePostgresServerShowOutput);
|
||||||
|
|
||||||
|
await postgresModel.refresh();
|
||||||
|
sinon.assert.calledOnceWithExactly(postgresShowStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||||
|
should(postgresModel.configLastUpdated).be.Date();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Calls onConfigUpdated event when model is refreshed', async function (): Promise<void> {
|
||||||
|
const postgresShowStub = sinon.stub(azdataApi.arc.postgres.server, 'show').resolves(FakePostgresServerShowOutput);
|
||||||
|
const configUpdatedEvent = sinon.spy(vscode.EventEmitter.prototype, 'fire');
|
||||||
|
|
||||||
|
await postgresModel.refresh();
|
||||||
|
sinon.assert.calledOnceWithExactly(postgresShowStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
|
||||||
|
sinon.assert.calledOnceWithExactly(configUpdatedEvent, postgresModel.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Expected exception is thrown', async function (): Promise<void> {
|
||||||
|
// Stub 'azdata arc postgres server show' to throw an exception
|
||||||
|
const error = new Error('something bad happened');
|
||||||
|
sinon.stub(azdataApi.arc.postgres.server, 'show').throws(error);
|
||||||
|
|
||||||
|
await should(postgresModel.refresh()).be.rejectedWith(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getConnectionProfile', function (): void {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Setup PostgresModel
|
||||||
|
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '', userName: 'postgres', connectionId: '12345678' };
|
||||||
|
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||||
|
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||||
|
|
||||||
|
sinon.stub(azdataApi.arc.postgres.server, 'show').resolves(FakePostgresServerShowOutput);
|
||||||
|
|
||||||
|
//Call to provide external endpoint
|
||||||
|
await postgresModel.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Rejected with expected error when user cancels', async function (): Promise<void> {
|
||||||
|
const close = sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
|
||||||
|
await should(postgresModel['getConnectionProfile']()).be.rejectedWith(new UserCancelledError());
|
||||||
|
sinon.assert.calledOnce(close);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Show dialog prompt if password not found', async function (): Promise<void> {
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect');
|
||||||
|
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const show = sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
const iconnectionProfileMock = TypeMoq.Mock.ofType<azdata.IConnectionProfile>();
|
||||||
|
iconnectionProfileMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const close = sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(iconnectionProfileMock.object));
|
||||||
|
|
||||||
|
await postgresModel['getConnectionProfile']();
|
||||||
|
sinon.assert.notCalled(connect);
|
||||||
|
sinon.assert.calledOnce(show);
|
||||||
|
sinon.assert.calledOnce(close);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Reads password from cred store and no dialog prompt', async function (): Promise<void> {
|
||||||
|
const password = generateGuid();
|
||||||
|
// 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 }));
|
||||||
|
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||||
|
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const show = sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
const treeSave = sinon.spy(AzureArcTreeDataProvider.prototype, 'saveControllers');
|
||||||
|
|
||||||
|
await postgresModel['getConnectionProfile']();
|
||||||
|
sinon.assert.calledOnce(connect);
|
||||||
|
sinon.assert.notCalled(show);
|
||||||
|
sinon.assert.calledOnce(treeSave);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Reads password from cred store and connect fails, show dialog prompt', async function (): Promise<void> {
|
||||||
|
const password = generateGuid();
|
||||||
|
// 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 }));
|
||||||
|
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||||
|
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => false);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const iconnectionProfileMock = TypeMoq.Mock.ofType<azdata.IConnectionProfile>();
|
||||||
|
iconnectionProfileMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const close = sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(iconnectionProfileMock.object));
|
||||||
|
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const show = sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
await postgresModel['getConnectionProfile']();
|
||||||
|
sinon.assert.calledOnce(connect);
|
||||||
|
sinon.assert.calledOnce(show);
|
||||||
|
sinon.assert.calledOnce(close);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Show dialog prompt if username not found', async function (): Promise<void> {
|
||||||
|
// Setup PostgresModel without username
|
||||||
|
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '', connectionId: '12345678' };
|
||||||
|
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||||
|
let postgresModelNew = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||||
|
await postgresModelNew.refresh();
|
||||||
|
|
||||||
|
const password = generateGuid();
|
||||||
|
// 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 }));
|
||||||
|
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
|
||||||
|
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect');
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const show = sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
const iconnectionProfileMock = TypeMoq.Mock.ofType<azdata.IConnectionProfile>();
|
||||||
|
iconnectionProfileMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const close = sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(iconnectionProfileMock.object));
|
||||||
|
|
||||||
|
await postgresModelNew['getConnectionProfile']();
|
||||||
|
sinon.assert.notCalled(connect);
|
||||||
|
sinon.assert.calledOnce(show);
|
||||||
|
sinon.assert.calledOnce(close);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Shows dialog prompt if no connection id', async function (): Promise<void> {
|
||||||
|
// Setup PostgresModel without connectionId
|
||||||
|
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };
|
||||||
|
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||||
|
let postgresModelNew = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||||
|
await postgresModelNew.refresh();
|
||||||
|
|
||||||
|
const provider = sinon.stub(azdata.credentials, 'getProvider');
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect');
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const show = sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
const iconnectionProfileMock = TypeMoq.Mock.ofType<azdata.IConnectionProfile>();
|
||||||
|
iconnectionProfileMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const close = sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(iconnectionProfileMock.object));
|
||||||
|
|
||||||
|
await postgresModelNew['getConnectionProfile']();
|
||||||
|
sinon.assert.notCalled(provider);
|
||||||
|
sinon.assert.notCalled(connect);
|
||||||
|
sinon.assert.calledOnce(show);
|
||||||
|
sinon.assert.calledOnce(close);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getEngineSettings', function (): void {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Setup PostgresModel
|
||||||
|
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '', userName: 'postgres', connectionId: '12345678' };
|
||||||
|
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
|
||||||
|
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
|
||||||
|
|
||||||
|
//Stub calling refresh postgres model
|
||||||
|
sinon.stub(azdataApi.arc.postgres.server, 'show').resolves(FakePostgresServerShowOutput);
|
||||||
|
|
||||||
|
//Stub how to get connection profile
|
||||||
|
const iconnectionProfileMock = TypeMoq.Mock.ofType<azdata.IConnectionProfile>();
|
||||||
|
iconnectionProfileMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(ConnectToPGSqlDialog.prototype, 'waitForClose').returns(Promise.resolve(iconnectionProfileMock.object));
|
||||||
|
const cancelButtonMock = TypeMoq.Mock.ofType<azdata.window.Button>();
|
||||||
|
cancelButtonMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const dialogMock = TypeMoq.Mock.ofType<azdata.window.Dialog>();
|
||||||
|
dialogMock.setup(x => x.cancelButton).returns(() => cancelButtonMock.object);
|
||||||
|
dialogMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.window, 'createModelViewDialog').returns(dialogMock.object);
|
||||||
|
sinon.stub(azdata.window, 'openDialog');
|
||||||
|
|
||||||
|
sinon.stub(azdata.connection, 'getUriForConnection');
|
||||||
|
|
||||||
|
//Call to provide external endpoint
|
||||||
|
await postgresModel.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Throw error when trying to connect fails', async function (): Promise<void> {
|
||||||
|
const errorMessage = 'Mock connection fail occured';
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => false);
|
||||||
|
connectionResultMock.setup(x => x.errorMessage).returns(() => errorMessage);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
await should(postgresModel.getEngineSettings()).be.rejectedWith(new Error(errorMessage));
|
||||||
|
sinon.assert.calledOnce(connect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Update active connection id when connect passes', async function (): Promise<void> {
|
||||||
|
const connectionID = '098765';
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup(x => x.connectionId).returns(() => connectionID);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
const connect = sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const array: azdata.DbCellValue[][] = [];
|
||||||
|
|
||||||
|
const executeMock = TypeMoq.Mock.ofType<azdata.SimpleExecuteResult>();
|
||||||
|
executeMock.setup(x => x.rows).returns(() => array);
|
||||||
|
executeMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const providerMock = TypeMoq.Mock.ofType<azdata.QueryProvider>();
|
||||||
|
providerMock.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => executeMock.object);
|
||||||
|
providerMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.dataprotocol, 'getProvider').returns(providerMock.object);
|
||||||
|
|
||||||
|
await postgresModel.getEngineSettings();
|
||||||
|
sinon.assert.calledOnce(connect);
|
||||||
|
sinon.assert.match(postgresModel['_activeConnectionId'], connectionID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Updates engineSettingsLastUpdated after populating engine settings', async function (): Promise<void> {
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const array: azdata.DbCellValue[][] = [];
|
||||||
|
|
||||||
|
const executeMock = TypeMoq.Mock.ofType<azdata.SimpleExecuteResult>();
|
||||||
|
executeMock.setup(x => x.rows).returns(() => array);
|
||||||
|
executeMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const providerMock = TypeMoq.Mock.ofType<azdata.QueryProvider>();
|
||||||
|
providerMock.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => executeMock.object);
|
||||||
|
providerMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.dataprotocol, 'getProvider').returns(providerMock.object);
|
||||||
|
|
||||||
|
await postgresModel.getEngineSettings();
|
||||||
|
should(postgresModel.engineSettingsLastUpdated).be.Date();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Calls onEngineSettingsUpdated event after populating engine settings', async function (): Promise<void> {
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const array: azdata.DbCellValue[][] = [];
|
||||||
|
|
||||||
|
const executeMock = TypeMoq.Mock.ofType<azdata.SimpleExecuteResult>();
|
||||||
|
executeMock.setup(x => x.rows).returns(() => array);
|
||||||
|
executeMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const providerMock = TypeMoq.Mock.ofType<azdata.QueryProvider>();
|
||||||
|
providerMock.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => executeMock.object);
|
||||||
|
providerMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.dataprotocol, 'getProvider').returns(providerMock.object);
|
||||||
|
|
||||||
|
const onEngineSettingsUpdated = sinon.spy(vscode.EventEmitter.prototype, 'fire');
|
||||||
|
|
||||||
|
await postgresModel.getEngineSettings();
|
||||||
|
sinon.assert.calledOnceWithExactly(onEngineSettingsUpdated, postgresModel.workerNodesEngineSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Populating engine settings skips certain parameters', async function (): Promise<void> {
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const rows: azdata.DbCellValue[][] = [
|
||||||
|
[{
|
||||||
|
displayValue: 'archive_timeout',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
const executeMock = TypeMoq.Mock.ofType<azdata.SimpleExecuteResult>();
|
||||||
|
executeMock.setup(x => x.rows).returns(() => rows);
|
||||||
|
executeMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const providerMock = TypeMoq.Mock.ofType<azdata.QueryProvider>();
|
||||||
|
providerMock.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => executeMock.object);
|
||||||
|
providerMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.dataprotocol, 'getProvider').returns(providerMock.object);
|
||||||
|
|
||||||
|
await postgresModel.getEngineSettings();
|
||||||
|
should(postgresModel.workerNodesEngineSettings.pop()).be.undefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Populates engine settings accurately', async function (): Promise<void> {
|
||||||
|
const connectionResultMock = TypeMoq.Mock.ofType<azdata.ConnectionResult>();
|
||||||
|
connectionResultMock.setup(x => x.connected).returns(() => true);
|
||||||
|
connectionResultMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connectionResultMock.object));
|
||||||
|
|
||||||
|
const rows: azdata.DbCellValue[][] = [
|
||||||
|
[{
|
||||||
|
displayValue: 'test0',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test1',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test2',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test3',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test4',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test5',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayValue: 'test6',
|
||||||
|
isNull: false,
|
||||||
|
invariantCultureDisplayValue: ''
|
||||||
|
}],
|
||||||
|
];
|
||||||
|
|
||||||
|
const engineSettingsModelCompare: EngineSettingsModel = {
|
||||||
|
parameterName: 'test0',
|
||||||
|
value: 'test1',
|
||||||
|
description: 'test2',
|
||||||
|
min: 'test3',
|
||||||
|
max: 'test4',
|
||||||
|
options: 'test5',
|
||||||
|
type: 'test6'
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeMock = TypeMoq.Mock.ofType<azdata.SimpleExecuteResult>();
|
||||||
|
executeMock.setup(x => x.rows).returns(() => rows);
|
||||||
|
executeMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
|
||||||
|
const providerMock = TypeMoq.Mock.ofType<azdata.QueryProvider>();
|
||||||
|
providerMock.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => executeMock.object);
|
||||||
|
providerMock.setup((x: any) => x.then).returns(() => undefined);
|
||||||
|
sinon.stub(azdata.dataprotocol, 'getProvider').returns(providerMock.object);
|
||||||
|
|
||||||
|
await postgresModel.getEngineSettings();
|
||||||
|
should(postgresModel.workerNodesEngineSettings.pop()).be.match(engineSettingsModelCompare);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
export const FakePostgresServerShowOutput: azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult> = {
|
||||||
|
logs: [],
|
||||||
|
stdout: [],
|
||||||
|
stderr: [],
|
||||||
|
result: {
|
||||||
|
apiVersion: 'version',
|
||||||
|
kind: 'postgresql',
|
||||||
|
metadata: {
|
||||||
|
creationTimestamp: '',
|
||||||
|
generation: 1,
|
||||||
|
name: 'pgt',
|
||||||
|
namespace: 'ns',
|
||||||
|
resourceVersion: '',
|
||||||
|
selfLink: '',
|
||||||
|
uid: '',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
engine: {
|
||||||
|
extensions: [{ name: '' }],
|
||||||
|
settings: {
|
||||||
|
default: { ['']: '' }
|
||||||
|
},
|
||||||
|
version: '12'
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
shards: 0,
|
||||||
|
workers: 0
|
||||||
|
},
|
||||||
|
scheduling: {
|
||||||
|
default: {
|
||||||
|
resources: {
|
||||||
|
requests: {
|
||||||
|
cpu: '',
|
||||||
|
memory: ''
|
||||||
|
},
|
||||||
|
limits: {
|
||||||
|
cpu: '',
|
||||||
|
memory: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
type: '',
|
||||||
|
port: 0
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
data: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
},
|
||||||
|
backups: {
|
||||||
|
className: '',
|
||||||
|
size: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
externalEndpoint: '127.0.0.1:5432',
|
||||||
|
readyPods: '',
|
||||||
|
state: '',
|
||||||
|
logSearchDashboard: '',
|
||||||
|
metricsDashboard: '',
|
||||||
|
podsStatus: [{
|
||||||
|
conditions: [{
|
||||||
|
lastTransitionTime: '',
|
||||||
|
message: '',
|
||||||
|
reason: '',
|
||||||
|
status: '',
|
||||||
|
type: '',
|
||||||
|
}],
|
||||||
|
name: '',
|
||||||
|
role: '',
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('postgresConnectionStringsPage', function (): void {
|
||||||
|
let controllerModel: ControllerModel;
|
||||||
|
let postgresModel: PostgresModel;
|
||||||
|
let azdataApi: azdataExt.IAzdataApi;
|
||||||
|
let postgresConnectionStrings: PostgresConnectionStringsPage;
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
// 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 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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('String contain correct ip and port', async function (): Promise<void> {
|
||||||
|
// Call to provide external endpoint
|
||||||
|
await postgresModel.refresh();
|
||||||
|
|
||||||
|
let endpoint = FakePostgresServerShowOutput.result.status.externalEndpoint.split(':');
|
||||||
|
|
||||||
|
postgresConnectionStrings['getConnectionStrings']().forEach(k => {
|
||||||
|
should(k.value.includes(endpoint[0])).be.True();
|
||||||
|
should(k.value.includes(endpoint[1])).be.True();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
describe('postgresOverviewPage', () => {
|
||||||
|
let postgresOverview: PostgresOverviewPage;
|
||||||
|
let azdataApi: azdataExt.IAzdataApi;
|
||||||
|
let controllerModel: ControllerModel;
|
||||||
|
let postgresModel: PostgresModel;
|
||||||
|
|
||||||
|
let showInformationMessage: sinon.SinonStub;
|
||||||
|
let showErrorMessage: sinon.SinonStub;
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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'];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -18,8 +18,8 @@ describe('ConnectControllerDialog', function (): void {
|
|||||||
|
|
||||||
(<{ info: ControllerInfo | undefined, description: string }[]>[
|
(<{ info: ControllerInfo | undefined, description: string }[]>[
|
||||||
{ info: undefined, description: 'all input' },
|
{ info: undefined, description: 'all input' },
|
||||||
{ info: { url: '127.0.0.1' }, description: 'all but URL' },
|
{ info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
|
||||||
{ info: { url: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
|
{ 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> {
|
it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
|
||||||
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||||
connectControllerDialog.showDialog(test.info, undefined);
|
connectControllerDialog.showDialog(test.info, undefined);
|
||||||
@@ -32,7 +32,7 @@ describe('ConnectControllerDialog', function (): void {
|
|||||||
it('validate returns false if controller refresh fails', async function (): Promise<void> {
|
it('validate returns false if controller refresh fails', async function (): Promise<void> {
|
||||||
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
|
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
|
||||||
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
|
||||||
const info = { id: uuid(), url: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
|
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');
|
connectControllerDialog.showDialog(info, 'pwd');
|
||||||
await connectControllerDialog.isInitialized;
|
await connectControllerDialog.isInitialized;
|
||||||
const validateResult = await connectControllerDialog.validate();
|
const validateResult = await connectControllerDialog.validate();
|
||||||
@@ -41,36 +41,36 @@ describe('ConnectControllerDialog', function (): void {
|
|||||||
|
|
||||||
it('validate replaces http with https', async function (): Promise<void> {
|
it('validate replaces http with https', async function (): Promise<void> {
|
||||||
await validateConnectControllerDialog(
|
await validateConnectControllerDialog(
|
||||||
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
|
{ 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');
|
'https://127.0.0.1:30081');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validate appends https if missing', async function (): Promise<void> {
|
it('validate appends https if missing', async function (): Promise<void> {
|
||||||
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
|
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');
|
'https://127.0.0.1:30080');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validate appends default port if missing', async function (): Promise<void> {
|
it('validate appends default port if missing', async function (): Promise<void> {
|
||||||
await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
|
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');
|
'https://127.0.0.1:30080');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validate appends both port and https if missing', async function (): Promise<void> {
|
it('validate appends both port and https if missing', async function (): Promise<void> {
|
||||||
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
|
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');
|
'https://127.0.0.1:30080');
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const name of ['', undefined]) {
|
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> {
|
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
|
||||||
await validateConnectControllerDialog(
|
await validateConnectControllerDialog(
|
||||||
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, username: 'sa', rememberPassword: true, resources: [] },
|
{ 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');
|
'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> {
|
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(
|
await validateConnectControllerDialog(
|
||||||
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', username: 'sa', rememberPassword: true, resources: [] },
|
{ 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',
|
'https://127.0.0.1:30081',
|
||||||
undefined);
|
undefined);
|
||||||
});
|
});
|
||||||
@@ -92,6 +92,6 @@ async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl
|
|||||||
const validateResult = await connectControllerDialog.validate();
|
const validateResult = await connectControllerDialog.validate();
|
||||||
should(validateResult).be.true('Validation should have returned true');
|
should(validateResult).be.true('Validation should have returned true');
|
||||||
const model = await connectControllerDialog.waitForClose();
|
const model = await connectControllerDialog.waitForClose();
|
||||||
should(model?.controllerModel.info.url).equal(expectedUrl);
|
should(model?.controllerModel.info.endpoint).equal(expectedUrl);
|
||||||
should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
|
should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,20 @@ interface ExtensionGlobalMemento extends vscode.Memento {
|
|||||||
setKeysForSync(keys: string[]): void;
|
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: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('AzureArcTreeDataProvider tests', function (): void {
|
describe('AzureArcTreeDataProvider tests', function (): void {
|
||||||
let treeDataProvider: AzureArcTreeDataProvider;
|
let treeDataProvider: AzureArcTreeDataProvider;
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
@@ -58,7 +72,7 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
treeDataProvider['_loading'] = false;
|
treeDataProvider['_loading'] = false;
|
||||||
let children = await treeDataProvider.getChildren();
|
let children = await treeDataProvider.getChildren();
|
||||||
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
should(children.length).equal(1, 'Controller node should be added correctly');
|
should(children.length).equal(1, 'Controller node should be added correctly');
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
@@ -69,12 +83,12 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
treeDataProvider['_loading'] = false;
|
treeDataProvider['_loading'] = false;
|
||||||
let children = await treeDataProvider.getChildren();
|
let children = await treeDataProvider.getChildren();
|
||||||
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
should(children.length).equal(0, 'There initially shouldn\'t be any children');
|
||||||
const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
|
const originalInfo: ControllerInfo = getDefaultControllerInfo();
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
|
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
should(children.length).equal(1, 'Controller node should be added correctly');
|
should(children.length).equal(1, 'Controller node should be added correctly');
|
||||||
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
|
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
|
||||||
const newInfo = { id: originalInfo.id, url: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] };
|
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);
|
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||||
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
|
||||||
@@ -102,18 +116,20 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
return mockArcApi.object;
|
return mockArcApi.object;
|
||||||
});
|
});
|
||||||
const fakeAzdataApi = new FakeAzdataApi();
|
const fakeAzdataApi = new FakeAzdataApi();
|
||||||
fakeAzdataApi.postgresInstances = [{ name: 'pg1', state: '', workers: 0 }];
|
const pgInstances = [{ name: 'pg1', state: '', workers: 0 }];
|
||||||
fakeAzdataApi.miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
|
const miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
|
||||||
|
fakeAzdataApi.postgresInstances = pgInstances;
|
||||||
|
fakeAzdataApi.miaaInstances = miaaInstances;
|
||||||
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
|
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
|
||||||
|
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
|
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
|
||||||
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
|
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
|
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
|
||||||
const children = await treeDataProvider.getChildren(controllerNode);
|
const children = await treeDataProvider.getChildren(controllerNode);
|
||||||
should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child');
|
should(children.filter(c => c.label === pgInstances[0].name).length).equal(1, 'Should have a Postgres child');
|
||||||
should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
|
should(children.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');
|
should(children.length).equal(2, 'Should have exactly 2 children');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -121,8 +137,10 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
describe('removeController', function (): void {
|
describe('removeController', function (): void {
|
||||||
it('removing a controller should work as expected', async function (): Promise<void> {
|
it('removing a controller should work as expected', async function (): Promise<void> {
|
||||||
treeDataProvider['_loading'] = false;
|
treeDataProvider['_loading'] = false;
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||||
const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] });
|
const info2 = getDefaultControllerInfo();
|
||||||
|
info2.username = 'cloudsa';
|
||||||
|
const controllerModel2 = new ControllerModel(treeDataProvider, info2);
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
await treeDataProvider.addOrUpdateController(controllerModel2, '');
|
||||||
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
|
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
|
||||||
@@ -139,20 +157,20 @@ describe('AzureArcTreeDataProvider tests', function (): void {
|
|||||||
|
|
||||||
describe('openResourceDashboard', function (): void {
|
describe('openResourceDashboard', function (): void {
|
||||||
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
|
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||||
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||||
await should(openDashboardPromise).be.rejected();
|
await should(openDashboardPromise).be.rejected();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
|
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
|
||||||
await should(openDashboardPromise).be.rejected();
|
await should(openDashboardPromise).be.rejected();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
|
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
|
||||||
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
|
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
|
||||||
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
|
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
|
||||||
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
await treeDataProvider.addOrUpdateController(controllerModel, '');
|
||||||
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
|
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
|
||||||
|
|||||||
3
extensions/arc/src/typings/arc.d.ts
vendored
3
extensions/arc/src/typings/arc.d.ts
vendored
@@ -37,7 +37,8 @@ declare module 'arc' {
|
|||||||
id: string,
|
id: string,
|
||||||
kubeConfigFilePath: string,
|
kubeConfigFilePath: string,
|
||||||
kubeClusterContext: string
|
kubeClusterContext: string
|
||||||
url: string,
|
endpoint: string | undefined,
|
||||||
|
namespace: string,
|
||||||
name: string,
|
name: string,
|
||||||
username: string,
|
username: string,
|
||||||
rememberPassword: boolean,
|
rememberPassword: boolean,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
|
|||||||
|
|
||||||
export abstract class Dashboard {
|
export abstract class Dashboard {
|
||||||
|
|
||||||
private dashboard!: azdata.window.ModelViewDashboard;
|
protected dashboard!: azdata.window.ModelViewDashboard;
|
||||||
|
|
||||||
constructor(protected title: string, protected readonly name: string) { }
|
constructor(protected title: string, protected readonly name: string) { }
|
||||||
|
|
||||||
@@ -16,6 +16,10 @@ export abstract class Dashboard {
|
|||||||
await this.dashboard.open();
|
await this.dashboard.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async closeDashboard(): Promise<void> {
|
||||||
|
await this.dashboard.close();
|
||||||
|
}
|
||||||
|
|
||||||
protected createDashboard(): azdata.window.ModelViewDashboard {
|
protected createDashboard(): azdata.window.ModelViewDashboard {
|
||||||
const dashboard = azdata.window.createModelViewDashboard(this.title, this.name);
|
const dashboard = azdata.window.createModelViewDashboard(this.title, this.name);
|
||||||
dashboard.registerTabs(async modelView => {
|
dashboard.registerTabs(async modelView => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export abstract class DashboardPage extends InitializingComponent {
|
|||||||
|
|
||||||
protected disposables: vscode.Disposable[] = [];
|
protected disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView) {
|
constructor(protected modelView: azdata.ModelView, protected dashboard: azdata.window.ModelViewDashboard) {
|
||||||
super();
|
super();
|
||||||
this.disposables.push(modelView.onClosed(() => {
|
this.disposables.push(modelView.onClosed(() => {
|
||||||
// Clean up best we can
|
// Clean up best we can
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export class RadioOptionsGroup {
|
|||||||
private _loadingBuilder: azdata.LoadingComponentBuilder;
|
private _loadingBuilder: azdata.LoadingComponentBuilder;
|
||||||
private _currentRadioOption!: azdata.RadioButtonComponent;
|
private _currentRadioOption!: azdata.RadioButtonComponent;
|
||||||
|
|
||||||
|
private _onRadioOptionChanged: vscode.EventEmitter<string | undefined> = new vscode.EventEmitter<string | undefined>();
|
||||||
|
public onRadioOptionChanged: vscode.Event<string | undefined> = this._onRadioOptionChanged.event;
|
||||||
|
|
||||||
constructor(private _modelBuilder: azdata.ModelBuilder, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) {
|
constructor(private _modelBuilder: azdata.ModelBuilder, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) {
|
||||||
this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
||||||
this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
|
this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
|
||||||
@@ -26,7 +29,7 @@ export class RadioOptionsGroup {
|
|||||||
return this._loadingBuilder.component();
|
return this._loadingBuilder.component();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(optionsInfoGetter: () => Promise<RadioOptionsInfo>): Promise<void> {
|
async load(optionsInfoGetter: () => RadioOptionsInfo | Promise<RadioOptionsInfo>): Promise<void> {
|
||||||
this.component().loading = true;
|
this.component().loading = true;
|
||||||
this._divContainer.clearItems();
|
this._divContainer.clearItems();
|
||||||
try {
|
try {
|
||||||
@@ -51,6 +54,7 @@ export class RadioOptionsGroup {
|
|||||||
// it is just better to keep things clean.
|
// it is just better to keep things clean.
|
||||||
this._currentRadioOption.checked = false;
|
this._currentRadioOption.checked = false;
|
||||||
this._currentRadioOption = radioOption;
|
this._currentRadioOption = radioOption;
|
||||||
|
this._onRadioOptionChanged.fire(this.value);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._divContainer.addItem(radioOption);
|
this._divContainer.addItem(radioOption);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class ControllerDashboard extends Dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||||
const overviewPage = new ControllerDashboardOverviewPage(modelView, this._controllerModel);
|
const overviewPage = new ControllerDashboardOverviewPage(modelView, this.dashboard, this._controllerModel);
|
||||||
return [
|
return [
|
||||||
overviewPage.tab
|
overviewPage.tab
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
|||||||
instanceNamespace: '-',
|
instanceNamespace: '-',
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel) {
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
|
|
||||||
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
||||||
|
|
||||||
@@ -147,7 +147,12 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
newInstance.onDidClick(async () => {
|
newInstance.onDidClick(async () => {
|
||||||
await vscode.commands.executeCommand('azdata.resource.deploy', 'azure-sql-mi', ['azure-sql-mi', 'arc.postgres'], { 'azure-sql-mi': { 'mi-type': ['arc-mi'] } });
|
const node = this._controllerModel.treeDataProvider.getControllerNode(this._controllerModel);
|
||||||
|
await vscode.commands.executeCommand('azdata.resource.deploy',
|
||||||
|
'azure-sql-mi', // Default option
|
||||||
|
['azure-sql-mi', 'arc-postgres'], // Type filter
|
||||||
|
{ 'azure-sql-mi': { 'mi-type': ['arc-mi'] } }, // Options filter
|
||||||
|
{ 'CONTROLLER_NAME': node?.label });
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Refresh
|
// Refresh
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
|
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _miaaModel: MiaaModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _miaaModel: MiaaModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
|
||||||
this.initializeConfigurationBoxes();
|
this.initializeConfigurationBoxes();
|
||||||
@@ -129,19 +129,18 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
cancellable: false
|
cancellable: false
|
||||||
},
|
},
|
||||||
async (_progress, _token): Promise<void> => {
|
async (_progress, _token): Promise<void> => {
|
||||||
let session: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
try {
|
try {
|
||||||
session = await this._miaaModel.controllerModel.acquireAzdataSession();
|
|
||||||
await this._azdataApi.azdata.arc.sql.mi.edit(
|
await this._azdataApi.azdata.arc.sql.mi.edit(
|
||||||
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.azdataAdditionalEnvVars, session);
|
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.azdataAdditionalEnvVars, this._miaaModel.controllerModel.controllerContext);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.saveButton!.enabled = true;
|
this.saveButton!.enabled = true;
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
|
||||||
session?.dispose();
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
await this._miaaModel.refresh();
|
await this._miaaModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -217,7 +216,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 2,
|
min: 2,
|
||||||
validationErrorMessage: loc.memoryLimitValidationErrorMessage,
|
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
placeHolder: loc.loading
|
placeHolder: loc.loading
|
||||||
}).component();
|
}).component();
|
||||||
@@ -235,7 +233,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 2,
|
min: 2,
|
||||||
validationErrorMessage: loc.memoryRequestValidationErrorMessage,
|
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
placeHolder: loc.loading
|
placeHolder: loc.loading
|
||||||
}).component();
|
}).component();
|
||||||
@@ -320,7 +317,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
currentCPUSize = '';
|
currentCPUSize = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
|
|
||||||
this.coresRequestBox!.placeHolder = currentCPUSize;
|
this.coresRequestBox!.placeHolder = currentCPUSize;
|
||||||
this.coresRequestBox!.value = '';
|
this.coresRequestBox!.value = '';
|
||||||
this.saveArgs.coresRequest = undefined;
|
this.saveArgs.coresRequest = undefined;
|
||||||
@@ -331,7 +327,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
|
|||||||
currentCPUSize = '';
|
currentCPUSize = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
|
|
||||||
this.coresLimitBox!.placeHolder = currentCPUSize;
|
this.coresLimitBox!.placeHolder = currentCPUSize;
|
||||||
this.coresLimitBox!.value = '';
|
this.coresLimitBox!.value = '';
|
||||||
this.saveArgs.coresLimit = undefined;
|
this.saveArgs.coresLimit = undefined;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import * as loc from '../../../localizedConstants';
|
|||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { KeyValueContainer, KeyValue, InputKeyValue, MultilineInputKeyValue } from '../../components/keyValueContainer';
|
import { KeyValueContainer, KeyValue, InputKeyValue, MultilineInputKeyValue } from '../../components/keyValueContainer';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
|
||||||
import { MiaaModel } from '../../../models/miaaModel';
|
import { MiaaModel } from '../../../models/miaaModel';
|
||||||
import { parseIpAndPort } from '../../../common/utils';
|
import { parseIpAndPort } from '../../../common/utils';
|
||||||
|
|
||||||
@@ -17,9 +16,9 @@ export class MiaaConnectionStringsPage extends DashboardPage {
|
|||||||
private _keyValueContainer!: KeyValueContainer;
|
private _keyValueContainer!: KeyValueContainer;
|
||||||
private _connectionStringsMessage!: azdata.TextComponent;
|
private _connectionStringsMessage!: azdata.TextComponent;
|
||||||
|
|
||||||
constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _miaaModel: MiaaModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
this.disposables.push(this._controllerModel.onRegistrationsUpdated(_ =>
|
this.disposables.push(this._miaaModel.onConfigUpdated(_ =>
|
||||||
this.eventuallyRunOnInitialized(() => this.updateConnectionStrings())));
|
this.eventuallyRunOnInitialized(() => this.updateConnectionStrings())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ export class MiaaDashboard extends Dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||||
const overviewPage = new MiaaDashboardOverviewPage(modelView, this._controllerModel, this._miaaModel);
|
const overviewPage = new MiaaDashboardOverviewPage(modelView, this.dashboard, this._controllerModel, this._miaaModel);
|
||||||
const connectionStringsPage = new MiaaConnectionStringsPage(modelView, this._controllerModel, this._miaaModel);
|
const connectionStringsPage = new MiaaConnectionStringsPage(modelView, this.dashboard, this._miaaModel);
|
||||||
const computeAndStoragePage = new MiaaComputeAndStoragePage(modelView, this._miaaModel);
|
const computeAndStoragePage = new MiaaComputeAndStoragePage(modelView, this.dashboard, this._miaaModel);
|
||||||
return [
|
return [
|
||||||
overviewPage.tab,
|
overviewPage.tab,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
vCores: ''
|
vCores: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
||||||
|
|
||||||
@@ -244,17 +244,18 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
cancellable: false
|
cancellable: false
|
||||||
},
|
},
|
||||||
async (_progress, _token) => {
|
async (_progress, _token) => {
|
||||||
const session = await this._controllerModel.acquireAzdataSession();
|
return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
|
||||||
try {
|
|
||||||
return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name, this._controllerModel.azdataAdditionalEnvVars, session);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await this._controllerModel.refreshTreeNode();
|
await this._controllerModel.refreshTreeNode();
|
||||||
vscode.window.showInformationMessage(loc.instanceDeleted(this._miaaModel.info.name));
|
vscode.window.showInformationMessage(loc.instanceDeleted(this._miaaModel.info.name));
|
||||||
|
try {
|
||||||
|
await this.dashboard.close();
|
||||||
|
} catch (err) {
|
||||||
|
// Failures closing the dashboard aren't something we need to show users
|
||||||
|
console.log('Error closing MIAA dashboard ', err);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._miaaModel.info.name, error));
|
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._miaaModel.info.name, error));
|
||||||
|
|||||||
@@ -12,30 +12,43 @@ import { DashboardPage } from '../../components/dashboardPage';
|
|||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
import { convertToGibibyteString } from '../../../common/utils';
|
import { convertToGibibyteString } from '../../../common/utils';
|
||||||
|
|
||||||
|
export type ConfigurationSpecModel = {
|
||||||
|
workers?: number,
|
||||||
|
workerCoresRequest?: string,
|
||||||
|
workerCoresLimit?: string,
|
||||||
|
workerMemoryRequest?: string,
|
||||||
|
workerMemoryLimit?: string,
|
||||||
|
coordinatorCoresRequest?: string,
|
||||||
|
coordinatorCoresLimit?: string,
|
||||||
|
coordinatorMemoryRequest?: string,
|
||||||
|
coordinatorMemoryLimit?: string
|
||||||
|
};
|
||||||
|
|
||||||
export class PostgresComputeAndStoragePage extends DashboardPage {
|
export class PostgresComputeAndStoragePage extends DashboardPage {
|
||||||
private workerContainer?: azdata.DivContainer;
|
private workerContainer!: azdata.DivContainer;
|
||||||
|
private coordinatorContainer!: azdata.DivContainer;
|
||||||
|
|
||||||
private workerBox?: azdata.InputBoxComponent;
|
private workerBox!: azdata.InputBoxComponent;
|
||||||
private coresLimitBox?: azdata.InputBoxComponent;
|
private workerCoresLimitBox!: azdata.InputBoxComponent;
|
||||||
private coresRequestBox?: azdata.InputBoxComponent;
|
private workerCoresRequestBox!: azdata.InputBoxComponent;
|
||||||
private memoryLimitBox?: azdata.InputBoxComponent;
|
private workerMemoryLimitBox!: azdata.InputBoxComponent;
|
||||||
private memoryRequestBox?: azdata.InputBoxComponent;
|
private workerMemoryRequestBox!: azdata.InputBoxComponent;
|
||||||
|
|
||||||
private discardButton?: azdata.ButtonComponent;
|
private coordinatorCoresLimitBox!: azdata.InputBoxComponent;
|
||||||
private saveButton?: azdata.ButtonComponent;
|
private coordinatorCoresRequestBox!: azdata.InputBoxComponent;
|
||||||
|
private coordinatorMemoryLimitBox!: azdata.InputBoxComponent;
|
||||||
|
private coordinatorMemoryRequestBox!: azdata.InputBoxComponent;
|
||||||
|
|
||||||
private saveArgs: {
|
private currentConfiguration: ConfigurationSpecModel = {};
|
||||||
workers?: number,
|
private saveArgs: ConfigurationSpecModel = {};
|
||||||
coresLimit?: string,
|
|
||||||
coresRequest?: string,
|
private discardButton!: azdata.ButtonComponent;
|
||||||
memoryLimit?: string,
|
private saveButton!: azdata.ButtonComponent;
|
||||||
memoryRequest?: string
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
|
||||||
this.initializeConfigurationBoxes();
|
this.initializeConfigurationBoxes();
|
||||||
@@ -61,16 +74,16 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
const content = this.modelView.modelBuilder.divContainer().component();
|
const content = this.modelView.modelBuilder.divContainer().component();
|
||||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
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,
|
value: loc.computeAndStorage,
|
||||||
CSSStyles: { ...cssStyles.title }
|
CSSStyles: { ...cssStyles.title }
|
||||||
}).component());
|
}).component());
|
||||||
|
|
||||||
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.postgresComputeAndStorageDescriptionPartOne,
|
value: loc.postgresComputeAndStorageDescriptionPartOne,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'max-width': 'auto' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'max-width': 'auto' }
|
||||||
}).component();
|
}).component();
|
||||||
const infoComputeStorage_p2 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p2 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.postgresComputeAndStorageDescriptionPartTwo,
|
value: loc.postgresComputeAndStorageDescriptionPartTwo,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
@@ -81,7 +94,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const infoComputeStorage_p3 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p3 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.computeAndStorageDescriptionPartThree,
|
value: loc.computeAndStorageDescriptionPartThree,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
@@ -92,17 +105,17 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.computeAndStorageDescriptionPartFour,
|
value: loc.computeAndStorageDescriptionPartFour,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.computeAndStorageDescriptionPartFive,
|
value: loc.computeAndStorageDescriptionPartFive,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.computeAndStorageDescriptionPartSix,
|
value: loc.computeAndStorageDescriptionPartSix,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
@@ -122,15 +135,26 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
.component();
|
.component();
|
||||||
content.addItem(computeInfoAndLinks, { CSSStyles: { 'min-height': '30px' } });
|
content.addItem(computeInfoAndLinks, { CSSStyles: { 'min-height': '30px' } });
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
// Worker nodes section
|
||||||
|
this.workerContainer = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
this.workerContainer.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.workerNodes,
|
value: loc.workerNodes,
|
||||||
CSSStyles: { ...cssStyles.title, 'margin-top': '25px' }
|
CSSStyles: { ...cssStyles.title, 'margin-top': '25px' }
|
||||||
}).component());
|
}).component());
|
||||||
|
this.workerContainer.addItems(this.createUserInputWorkerSection(), { CSSStyles: { 'min-height': '30px' } });
|
||||||
this.workerContainer = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
this.workerContainer.addItems(this.createUserInputSection(), { CSSStyles: { 'min-height': '30px' } });
|
|
||||||
content.addItem(this.workerContainer, { CSSStyles: { 'min-height': '30px' } });
|
content.addItem(this.workerContainer, { CSSStyles: { 'min-height': '30px' } });
|
||||||
|
|
||||||
|
// Coordinator node section
|
||||||
|
this.coordinatorContainer = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
this.coordinatorContainer.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.coordinatorNode,
|
||||||
|
CSSStyles: { ...cssStyles.title, 'margin-top': '25px' }
|
||||||
|
}).component());
|
||||||
|
this.coordinatorContainer.addItems(this.createUserInputCoordinatorSection(), { CSSStyles: { 'min-height': '30px' } });
|
||||||
|
|
||||||
|
// TODO unhide once once ready to make azdata calls
|
||||||
|
// content.addItem(this.coordinatorContainer, { CSSStyles: { 'min-height': '30px' } });
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
@@ -138,7 +162,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
// Save Edits
|
// Save Edits
|
||||||
this.saveButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
this.saveButton = this.modelView.modelBuilder.button().withProps({
|
||||||
label: loc.saveText,
|
label: loc.saveText,
|
||||||
iconPath: IconPathHelper.save,
|
iconPath: IconPathHelper.save,
|
||||||
enabled: false
|
enabled: false
|
||||||
@@ -146,7 +170,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.saveButton.onDidClick(async () => {
|
this.saveButton.onDidClick(async () => {
|
||||||
this.saveButton!.enabled = false;
|
this.saveButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
await vscode.window.withProgress(
|
await vscode.window.withProgress(
|
||||||
{
|
{
|
||||||
@@ -155,31 +179,47 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
cancellable: false
|
cancellable: false
|
||||||
},
|
},
|
||||||
async (_progress, _token): Promise<void> => {
|
async (_progress, _token): Promise<void> => {
|
||||||
let session: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
try {
|
try {
|
||||||
session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
|
||||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
this._postgresModel.info.name,
|
this._postgresModel.info.name,
|
||||||
this.saveArgs,
|
{
|
||||||
this._postgresModel.engineVersion,
|
workers: this.saveArgs.workers,
|
||||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
coresRequest: this.saveArgs.workerCoresRequest,
|
||||||
session
|
coresLimit: this.saveArgs.workerCoresLimit,
|
||||||
);
|
memoryRequest: this.saveArgs.workerMemoryRequest,
|
||||||
|
memoryLimit: this.saveArgs.workerMemoryLimit
|
||||||
|
},
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars);
|
||||||
|
/* TODO add second edit call for coordinator configuration
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{
|
||||||
|
coresRequest: this.saveArgs.coordinatorCoresRequest,
|
||||||
|
coresLimit: this.saveArgs.coordinatorCoresLimit,
|
||||||
|
memoryRequest: this.saveArgs.coordinatorMemoryRequest,
|
||||||
|
memoryLimit: this.saveArgs.coordinatorMemoryLimit
|
||||||
|
},
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
*/
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If an error occurs while editing the instance then re-enable the save button since
|
// If an error occurs while editing the instance then re-enable the save button since
|
||||||
// the edit wasn't successfully applied
|
// the edit wasn't successfully applied
|
||||||
this.saveButton!.enabled = true;
|
this.saveButton.enabled = true;
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
|
||||||
session?.dispose();
|
|
||||||
}
|
}
|
||||||
await this._postgresModel.refresh();
|
try {
|
||||||
|
await this._postgresModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||||
|
|
||||||
this.discardButton!.enabled = false;
|
this.discardButton.enabled = false;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
||||||
@@ -187,7 +227,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Discard
|
// Discard
|
||||||
this.discardButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
this.discardButton = this.modelView.modelBuilder.button().withProps({
|
||||||
label: loc.discardText,
|
label: loc.discardText,
|
||||||
iconPath: IconPathHelper.discard,
|
iconPath: IconPathHelper.discard,
|
||||||
enabled: false
|
enabled: false
|
||||||
@@ -195,15 +235,17 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.discardButton.onDidClick(async () => {
|
this.discardButton.onDidClick(async () => {
|
||||||
this.discardButton!.enabled = false;
|
this.discardButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
this.editWorkerNodeCount();
|
this.workerBox.value = this.currentConfiguration.workers!.toString();
|
||||||
this.editCores();
|
this.workerCoresRequestBox.value = this.currentConfiguration.workerCoresRequest;
|
||||||
this.editMemory();
|
this.workerCoresLimitBox.value = this.currentConfiguration.workerCoresLimit;
|
||||||
|
this.workerMemoryRequestBox.value = this.currentConfiguration.workerMemoryRequest;
|
||||||
|
this.workerMemoryLimitBox.value = this.currentConfiguration.workerMemoryLimit;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
||||||
} finally {
|
} finally {
|
||||||
this.saveButton!.enabled = false;
|
this.saveButton.enabled = false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -213,25 +255,27 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
]).component();
|
]).component();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeConfigurationBoxes() {
|
private initializeConfigurationBoxes(): void {
|
||||||
this.workerBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
// Worker node count
|
||||||
|
this.workerBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
validationErrorMessage: loc.workerValidationErrorMessage,
|
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
placeHolder: loc.loading
|
placeHolder: loc.loading,
|
||||||
|
required: true
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.workerBox.onTextChanged(() => {
|
this.workerBox.onTextChanged(() => {
|
||||||
if (!(this.handleOnTextChanged(this.workerBox!))) {
|
if (!this.saveValueToEdit(this.workerBox, this.currentConfiguration.workers!.toString())) {
|
||||||
this.saveArgs.workers = undefined;
|
this.saveArgs.workers = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.saveArgs.workers = parseInt(this.workerBox!.value!);
|
this.saveArgs.workers = parseInt(this.workerBox.value!);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
// Worker nodes cores request
|
||||||
|
this.workerCoresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 1,
|
min: 1,
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
@@ -239,16 +283,19 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.coresLimitBox.onTextChanged(() => {
|
this.workerCoresRequestBox.onTextChanged(() => {
|
||||||
if (!(this.handleOnTextChanged(this.coresLimitBox!))) {
|
if (!(this.saveValueToEdit(this.workerCoresRequestBox, this.currentConfiguration.workerCoresRequest!))) {
|
||||||
this.saveArgs.coresLimit = undefined;
|
this.saveArgs.workerCoresRequest = undefined;
|
||||||
|
} else if (this.workerCoresRequestBox.value === '') {
|
||||||
|
this.saveArgs.workerCoresRequest = '""';
|
||||||
} else {
|
} else {
|
||||||
this.saveArgs.coresLimit = this.coresLimitBox!.value;
|
this.saveArgs.workerCoresRequest = this.workerCoresRequestBox.value;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
// Worker nodes cores limit
|
||||||
|
this.workerCoresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 1,
|
min: 1,
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
@@ -256,67 +303,152 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.coresRequestBox.onTextChanged(() => {
|
this.workerCoresLimitBox.onTextChanged(() => {
|
||||||
if (!(this.handleOnTextChanged(this.coresRequestBox!))) {
|
if (!(this.saveValueToEdit(this.workerCoresLimitBox, this.currentConfiguration.workerCoresLimit!))) {
|
||||||
this.saveArgs.coresRequest = undefined;
|
this.saveArgs.workerCoresLimit = undefined;
|
||||||
|
} else if (this.workerCoresLimitBox.value === '') {
|
||||||
|
this.saveArgs.workerCoresLimit = '""';
|
||||||
} else {
|
} else {
|
||||||
this.saveArgs.coresRequest = this.coresRequestBox!.value;
|
this.saveArgs.workerCoresLimit = this.workerCoresLimitBox.value;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
// Worker nodes memory request
|
||||||
|
this.workerMemoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 0.25,
|
min: 0.25,
|
||||||
validationErrorMessage: loc.memoryLimitValidationErrorMessage,
|
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
placeHolder: loc.loading
|
placeHolder: loc.loading
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.memoryLimitBox.onTextChanged(() => {
|
this.workerMemoryRequestBox.onTextChanged(() => {
|
||||||
if (!(this.handleOnTextChanged(this.memoryLimitBox!))) {
|
if (!(this.saveValueToEdit(this.workerMemoryRequestBox, this.currentConfiguration.workerMemoryRequest!))) {
|
||||||
this.saveArgs.memoryLimit = undefined;
|
this.saveArgs.workerMemoryRequest = undefined;
|
||||||
|
} else if (this.workerMemoryRequestBox.value === '') {
|
||||||
|
this.saveArgs.workerMemoryRequest = '""';
|
||||||
} else {
|
} else {
|
||||||
this.saveArgs.memoryLimit = this.memoryLimitBox!.value + 'Gi';
|
this.saveArgs.workerMemoryRequest = this.workerMemoryRequestBox.value + 'Gi';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
// Worker nodes memory limit
|
||||||
|
this.workerMemoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
min: 0.25,
|
min: 0.25,
|
||||||
validationErrorMessage: loc.memoryRequestValidationErrorMessage,
|
|
||||||
inputType: 'number',
|
inputType: 'number',
|
||||||
placeHolder: loc.loading
|
placeHolder: loc.loading
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this.memoryRequestBox.onTextChanged(() => {
|
this.workerMemoryLimitBox.onTextChanged(() => {
|
||||||
if (!(this.handleOnTextChanged(this.memoryRequestBox!))) {
|
if (!(this.saveValueToEdit(this.workerMemoryLimitBox, this.currentConfiguration.workerMemoryLimit!))) {
|
||||||
this.saveArgs.memoryRequest = undefined;
|
this.saveArgs.workerMemoryLimit = undefined;
|
||||||
|
} else if (this.workerMemoryLimitBox.value === '') {
|
||||||
|
this.saveArgs.workerMemoryLimit = '""';
|
||||||
} else {
|
} else {
|
||||||
this.saveArgs.memoryRequest = this.memoryRequestBox!.value + 'Gi';
|
this.saveArgs.workerMemoryLimit = this.workerMemoryLimitBox.value + 'Gi';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Coordinator node cores request
|
||||||
|
this.coordinatorCoresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
readOnly: false,
|
||||||
|
min: 1,
|
||||||
|
inputType: 'number',
|
||||||
|
placeHolder: loc.loading
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.coordinatorCoresRequestBox.onTextChanged(() => {
|
||||||
|
if (!(this.saveValueToEdit(this.coordinatorCoresRequestBox, this.currentConfiguration.coordinatorCoresRequest!))) {
|
||||||
|
this.saveArgs.coordinatorCoresRequest = undefined;
|
||||||
|
} else if (this.coordinatorCoresRequestBox.value === '') {
|
||||||
|
this.saveArgs.coordinatorCoresRequest = '""';
|
||||||
|
} else {
|
||||||
|
this.saveArgs.coordinatorCoresRequest = this.coordinatorCoresRequestBox.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Coordinator node cores limit
|
||||||
|
this.coordinatorCoresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
readOnly: false,
|
||||||
|
min: 1,
|
||||||
|
inputType: 'number',
|
||||||
|
placeHolder: loc.loading
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.coordinatorCoresLimitBox.onTextChanged(() => {
|
||||||
|
if (!(this.saveValueToEdit(this.coordinatorCoresLimitBox, this.currentConfiguration.coordinatorCoresLimit!))) {
|
||||||
|
this.saveArgs.coordinatorCoresLimit = undefined;
|
||||||
|
} else if (this.coordinatorCoresLimitBox.value === '') {
|
||||||
|
this.saveArgs.coordinatorCoresLimit = '""';
|
||||||
|
} else {
|
||||||
|
this.saveArgs.coordinatorCoresLimit = this.coordinatorCoresLimitBox.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Coordinator node memory request
|
||||||
|
this.coordinatorMemoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
readOnly: false,
|
||||||
|
min: 0.25,
|
||||||
|
inputType: 'number',
|
||||||
|
placeHolder: loc.loading
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.coordinatorMemoryRequestBox.onTextChanged(() => {
|
||||||
|
if (!(this.saveValueToEdit(this.coordinatorMemoryRequestBox, this.currentConfiguration.coordinatorMemoryRequest!))) {
|
||||||
|
this.saveArgs.coordinatorMemoryRequest = undefined;
|
||||||
|
} else if (this.coordinatorMemoryRequestBox.value === '') {
|
||||||
|
this.saveArgs.coordinatorMemoryRequest = '""';
|
||||||
|
} else {
|
||||||
|
this.saveArgs.coordinatorMemoryRequest = this.coordinatorMemoryRequestBox.value + 'Gi';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Coordinator node memory limit
|
||||||
|
this.coordinatorMemoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
readOnly: false,
|
||||||
|
min: 0.25,
|
||||||
|
inputType: 'number',
|
||||||
|
placeHolder: loc.loading
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.coordinatorMemoryLimitBox.onTextChanged(() => {
|
||||||
|
if (!(this.saveValueToEdit(this.coordinatorMemoryLimitBox, this.currentConfiguration.coordinatorMemoryLimit!))) {
|
||||||
|
this.saveArgs.coordinatorMemoryLimit = undefined;
|
||||||
|
} else if (this.coordinatorMemoryLimitBox.value === '') {
|
||||||
|
this.saveArgs.coordinatorMemoryLimit = '""';
|
||||||
|
} else {
|
||||||
|
this.saveArgs.coordinatorMemoryLimit = this.coordinatorMemoryLimitBox.value + 'Gi';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createUserInputSection(): azdata.Component[] {
|
private createUserInputWorkerSection(): azdata.Component[] {
|
||||||
if (this._postgresModel.configLastUpdated) {
|
if (this._postgresModel.configLastUpdated) {
|
||||||
this.editWorkerNodeCount();
|
this.editWorkerNodeCount();
|
||||||
this.editCores();
|
this.editWorkerCores();
|
||||||
this.editMemory();
|
this.editWorkerMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.createWorkerNodesSectionContainer(),
|
this.createWorkerNodesSectionContainer(),
|
||||||
this.createCoresMemorySection(),
|
this.createCoresMemorySection(loc.configurationPerNode, loc.postgresConfigurationInformation), // use loc.workerNodesConfigurationInformation when coordinator section is included
|
||||||
this.createConfigurationSectionContainer(loc.coresRequest, this.coresRequestBox!),
|
this.createConfigurationSectionContainer(loc.coresRequest, this.workerCoresRequestBox),
|
||||||
this.createConfigurationSectionContainer(loc.coresLimit, this.coresLimitBox!),
|
this.createConfigurationSectionContainer(loc.coresLimit, this.workerCoresLimitBox),
|
||||||
this.createConfigurationSectionContainer(loc.memoryRequest, this.memoryRequestBox!),
|
this.createConfigurationSectionContainer(loc.memoryRequest, this.workerMemoryRequestBox),
|
||||||
this.createConfigurationSectionContainer(loc.memoryLimit, this.memoryLimitBox!)
|
this.createConfigurationSectionContainer(loc.memoryLimit, this.workerMemoryLimitBox)
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -330,7 +462,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const keyComponent = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const keyComponent = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.workerNodeCount,
|
value: loc.workerNodeCount,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
@@ -338,7 +470,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
const keyContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
const keyContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||||
keyContainer.addItem(keyComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
keyContainer.addItem(keyComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
||||||
|
|
||||||
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
const information = this.modelView.modelBuilder.button().withProps({
|
||||||
iconPath: IconPathHelper.information,
|
iconPath: IconPathHelper.information,
|
||||||
title: loc.workerNodesInformation,
|
title: loc.workerNodesInformation,
|
||||||
width: '15px',
|
width: '15px',
|
||||||
@@ -350,13 +482,29 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
flexContainer.addItem(keyContainer, keyFlex);
|
flexContainer.addItem(keyContainer, keyFlex);
|
||||||
|
|
||||||
const inputContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
const inputContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||||
inputContainer.addItem(this.workerBox!, { CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '225px' } });
|
inputContainer.addItem(this.workerBox, { CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '225px' } });
|
||||||
|
|
||||||
flexContainer.addItem(inputContainer, inputFlex);
|
flexContainer.addItem(inputContainer, inputFlex);
|
||||||
|
|
||||||
return flexContainer;
|
return flexContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createUserInputCoordinatorSection(): azdata.Component[] {
|
||||||
|
if (this._postgresModel.configLastUpdated) {
|
||||||
|
this.editCoordinatorCores();
|
||||||
|
this.editCoordinatorMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
this.createCoresMemorySection(loc.configuration, loc.coordinatorNodeConfigurationInformation),
|
||||||
|
this.createConfigurationSectionContainer(loc.coresRequest, this.coordinatorCoresRequestBox),
|
||||||
|
this.createConfigurationSectionContainer(loc.coresLimit, this.coordinatorCoresLimitBox),
|
||||||
|
this.createConfigurationSectionContainer(loc.memoryRequest, this.coordinatorMemoryRequestBox),
|
||||||
|
this.createConfigurationSectionContainer(loc.memoryLimit, this.coordinatorMemoryLimitBox)
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private createConfigurationSectionContainer(key: string, input: azdata.Component): azdata.FlexContainer {
|
private createConfigurationSectionContainer(key: string, input: azdata.Component): azdata.FlexContainer {
|
||||||
const inputFlex = { flex: '0 1 150px' };
|
const inputFlex = { flex: '0 1 150px' };
|
||||||
const keyFlex = { flex: `0 1 250px` };
|
const keyFlex = { flex: `0 1 250px` };
|
||||||
@@ -366,7 +514,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const keyComponent = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const keyComponent = this.modelView.modelBuilder.text().withProps({
|
||||||
value: key,
|
value: key,
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
@@ -383,39 +531,43 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
return flexContainer;
|
return flexContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleOnTextChanged(component: azdata.InputBoxComponent): boolean {
|
/**
|
||||||
if ((!component.value)) {
|
* A function that determines if an input box's value should be considered or not.
|
||||||
// if there is no text found in the inputbox component return false
|
* Triggers the save and discard buttons to become enabled depending on the value change.
|
||||||
|
*
|
||||||
|
* If new value is the same as value found in config, do not consider this new value for editing.
|
||||||
|
* If new value is invalid, do not consider this new value for editing and enable discard button.
|
||||||
|
* If value is valid and not equal to original value found in config, add this new value to be considered
|
||||||
|
* for editing and enable save/discard buttons.
|
||||||
|
*
|
||||||
|
* @param component The input box that had an onTextChanged event triggered.
|
||||||
|
* @param originalValue The value that was contained in the input box before user interaction.
|
||||||
|
* @return A boolean that reads true if the new value should be taken in for editing or not.
|
||||||
|
*/
|
||||||
|
private saveValueToEdit(component: azdata.InputBoxComponent, originalValue: string): boolean {
|
||||||
|
if (component.value === originalValue) {
|
||||||
return false;
|
return false;
|
||||||
} else if ((!component.valid)) {
|
} else if ((!component.valid)) {
|
||||||
// if value given by user is not valid enable discard button for user
|
|
||||||
// to clear all inputs and return false
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// if a valid value has been entered into the input box, enable save and discard buttons
|
this.saveButton.enabled = true;
|
||||||
// so that user could choose to either edit instance or clear all inputs
|
this.discardButton.enabled = true;
|
||||||
// return true
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private editWorkerNodeCount() {
|
private editWorkerNodeCount(): void {
|
||||||
// scale.shards was renamed to scale.workers. Check both for backwards compatibility.
|
// scale.shards was renamed to scale.workers. Check both for backwards compatibility.
|
||||||
let scale = this._postgresModel.config?.spec.scale;
|
let scale = this._postgresModel.config?.spec.scale;
|
||||||
let currentWorkers = scale?.workers ?? scale?.shards ?? 0;
|
this.currentConfiguration.workers = scale?.workers ?? scale?.shards ?? 0;
|
||||||
|
|
||||||
this.workerBox!.min = currentWorkers;
|
|
||||||
this.workerBox!.placeHolder = currentWorkers.toString();
|
|
||||||
this.workerBox!.value = '';
|
|
||||||
|
|
||||||
|
this.workerBox.min = this.currentConfiguration.workers;
|
||||||
|
this.workerBox.placeHolder = '';
|
||||||
|
this.workerBox.value = this.currentConfiguration.workers.toString();
|
||||||
this.saveArgs.workers = undefined;
|
this.saveArgs.workers = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCoresMemorySection(): azdata.DivContainer {
|
private createCoresMemorySection(title: string, description: string): azdata.DivContainer {
|
||||||
const titleFlex = { flex: `0 1 250px` };
|
const titleFlex = { flex: `0 1 250px` };
|
||||||
|
|
||||||
const flexContainer = this.modelView.modelBuilder.flexContainer().withLayout({
|
const flexContainer = this.modelView.modelBuilder.flexContainer().withLayout({
|
||||||
@@ -423,17 +575,17 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const titleComponent = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
const titleComponent = this.modelView.modelBuilder.text().withProps({
|
||||||
value: loc.configurationPerNode,
|
value: title,
|
||||||
CSSStyles: { ...cssStyles.title, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
CSSStyles: { ...cssStyles.title, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const titleContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
const titleContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||||
titleContainer.addItem(titleComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
titleContainer.addItem(titleComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
|
||||||
|
|
||||||
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
const information = this.modelView.modelBuilder.button().withProps({
|
||||||
iconPath: IconPathHelper.information,
|
iconPath: IconPathHelper.information,
|
||||||
title: loc.postgresConfigurationInformation,
|
title: description,
|
||||||
width: '15px',
|
width: '15px',
|
||||||
height: '15px',
|
height: '15px',
|
||||||
enabled: false
|
enabled: false
|
||||||
@@ -448,62 +600,108 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
|
|||||||
return configurationSection;
|
return configurationSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private editCores() {
|
private editWorkerCores(): void {
|
||||||
let currentCPUSize = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.cpu;
|
//Cores Request
|
||||||
|
this.currentConfiguration.workerCoresRequest = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.cpu;
|
||||||
if (!currentCPUSize) {
|
if (!this.currentConfiguration.workerCoresRequest) {
|
||||||
currentCPUSize = '';
|
this.currentConfiguration.workerCoresRequest = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
|
this.workerCoresRequestBox.placeHolder = '';
|
||||||
this.coresRequestBox!.placeHolder = currentCPUSize;
|
this.workerCoresRequestBox.value = this.currentConfiguration.workerCoresRequest;
|
||||||
this.coresRequestBox!.value = '';
|
this.saveArgs.workerCoresRequest = undefined;
|
||||||
this.saveArgs.coresRequest = undefined;
|
|
||||||
|
|
||||||
currentCPUSize = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.cpu;
|
// Cores Limit
|
||||||
|
this.currentConfiguration.workerCoresLimit = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.cpu;
|
||||||
if (!currentCPUSize) {
|
if (!this.currentConfiguration.workerCoresLimit) {
|
||||||
currentCPUSize = '';
|
this.currentConfiguration.workerCoresLimit = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
|
this.workerCoresLimitBox.placeHolder = '';
|
||||||
this.coresLimitBox!.placeHolder = currentCPUSize;
|
this.workerCoresLimitBox.value = this.currentConfiguration.workerCoresLimit;
|
||||||
this.coresLimitBox!.value = '';
|
this.saveArgs.workerCoresLimit = undefined;
|
||||||
this.saveArgs.coresLimit = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private editMemory() {
|
private editWorkerMemory(): void {
|
||||||
let currentMemSizeConversion: string;
|
//Memory Request
|
||||||
let currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.memory;
|
let currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.memory;
|
||||||
|
|
||||||
if (!currentMemorySize) {
|
if (!currentMemorySize) {
|
||||||
currentMemSizeConversion = '';
|
this.currentConfiguration.workerMemoryRequest = '';
|
||||||
} else {
|
} else {
|
||||||
currentMemSizeConversion = convertToGibibyteString(currentMemorySize);
|
this.currentConfiguration.workerMemoryRequest = convertToGibibyteString(currentMemorySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.memoryRequestBox!.placeHolder = currentMemSizeConversion!;
|
this.workerMemoryRequestBox.placeHolder = '';
|
||||||
this.memoryRequestBox!.value = '';
|
this.workerMemoryRequestBox.value = this.currentConfiguration.workerMemoryRequest;
|
||||||
|
this.saveArgs.workerMemoryRequest = undefined;
|
||||||
this.saveArgs.memoryRequest = undefined;
|
|
||||||
|
|
||||||
|
//Memory Limit
|
||||||
currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.memory;
|
currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.memory;
|
||||||
|
|
||||||
if (!currentMemorySize) {
|
if (!currentMemorySize) {
|
||||||
currentMemSizeConversion = '';
|
this.currentConfiguration.workerMemoryLimit = '';
|
||||||
} else {
|
} else {
|
||||||
currentMemSizeConversion = convertToGibibyteString(currentMemorySize);
|
this.currentConfiguration.workerMemoryLimit = convertToGibibyteString(currentMemorySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.memoryLimitBox!.placeHolder = currentMemSizeConversion!;
|
this.workerMemoryLimitBox.placeHolder = '';
|
||||||
this.memoryLimitBox!.value = '';
|
this.workerMemoryLimitBox.value = this.currentConfiguration.workerMemoryLimit;
|
||||||
|
this.saveArgs.workerMemoryLimit = undefined;
|
||||||
this.saveArgs.memoryLimit = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleServiceUpdated() {
|
private editCoordinatorCores(): void {
|
||||||
|
// TODO get current cpu size for coordinator
|
||||||
|
this.currentConfiguration.coordinatorCoresRequest = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.cpu;
|
||||||
|
if (!this.currentConfiguration.coordinatorCoresRequest) {
|
||||||
|
this.currentConfiguration.coordinatorCoresRequest = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.coordinatorCoresRequestBox.placeHolder = '';
|
||||||
|
this.coordinatorCoresRequestBox.value = this.currentConfiguration.coordinatorCoresRequest;
|
||||||
|
this.saveArgs.coordinatorCoresRequest = undefined;
|
||||||
|
|
||||||
|
// TODO get current cpu size for coordinator
|
||||||
|
this.currentConfiguration.coordinatorCoresLimit = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.cpu;
|
||||||
|
if (!this.currentConfiguration.coordinatorCoresLimit) {
|
||||||
|
this.currentConfiguration.coordinatorCoresLimit = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.coordinatorCoresLimitBox.placeHolder = '';
|
||||||
|
this.coordinatorCoresLimitBox.value = this.currentConfiguration.coordinatorCoresLimit;
|
||||||
|
this.saveArgs.coordinatorCoresLimit = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private editCoordinatorMemory(): void {
|
||||||
|
// TODO get current memory size for coordinator
|
||||||
|
let currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.requests?.memory;
|
||||||
|
if (!currentMemorySize) {
|
||||||
|
this.currentConfiguration.coordinatorCoresRequest = '';
|
||||||
|
} else {
|
||||||
|
this.currentConfiguration.coordinatorCoresRequest = convertToGibibyteString(currentMemorySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.coordinatorMemoryRequestBox.placeHolder = '';
|
||||||
|
this.coordinatorMemoryRequestBox.value = this.currentConfiguration.coordinatorMemoryRequest;
|
||||||
|
this.saveArgs.coordinatorMemoryRequest = undefined;
|
||||||
|
|
||||||
|
// TODO get current memory size for coordinator
|
||||||
|
currentMemorySize = this._postgresModel.config?.spec.scheduling?.default?.resources?.limits?.memory;
|
||||||
|
if (!currentMemorySize) {
|
||||||
|
this.currentConfiguration.coordinatorCoresLimit = '';
|
||||||
|
} else {
|
||||||
|
this.currentConfiguration.coordinatorCoresLimit = convertToGibibyteString(currentMemorySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.coordinatorMemoryLimitBox.placeHolder = '';
|
||||||
|
this.coordinatorMemoryLimitBox.value = this.currentConfiguration.coordinatorMemoryLimit;
|
||||||
|
this.saveArgs.coordinatorMemoryLimit = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleServiceUpdated(): void {
|
||||||
this.editWorkerNodeCount();
|
this.editWorkerNodeCount();
|
||||||
this.editCores();
|
this.editWorkerCores();
|
||||||
this.editMemory();
|
this.editWorkerMemory();
|
||||||
|
/* TODO perform once Coordinator section is in view
|
||||||
|
this.editCoordinatorCores();
|
||||||
|
this.editCoordinatorMemory(); */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import { PostgresModel } from '../../../models/postgresModel';
|
|||||||
|
|
||||||
export class PostgresConnectionStringsPage extends DashboardPage {
|
export class PostgresConnectionStringsPage extends DashboardPage {
|
||||||
private keyValueContainer?: KeyValueContainer;
|
private keyValueContainer?: KeyValueContainer;
|
||||||
|
private connectionStringsLoading!: azdata.LoadingComponent;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
|
|
||||||
this.disposables.push(this._postgresModel.onConfigUpdated(
|
this.disposables.push(this._postgresModel.onConfigUpdated(
|
||||||
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
||||||
@@ -59,7 +60,14 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
|||||||
|
|
||||||
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
|
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
|
||||||
this.disposables.push(this.keyValueContainer);
|
this.disposables.push(this.keyValueContainer);
|
||||||
content.addItem(this.keyValueContainer.container);
|
|
||||||
|
this.connectionStringsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.keyValueContainer.container)
|
||||||
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
|
loading: !this._postgresModel.configLastUpdated
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
content.addItem(this.connectionStringsLoading, { CSSStyles: cssStyles.text });
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -88,5 +96,6 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
|||||||
|
|
||||||
private handleServiceUpdated() {
|
private handleServiceUpdated() {
|
||||||
this.keyValueContainer?.refresh(this.getConnectionStrings());
|
this.keyValueContainer?.refresh(this.getConnectionStrings());
|
||||||
|
this.connectionStringsLoading.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as loc from '../../../localizedConstants';
|
||||||
|
import { IconPathHelper } from '../../../constants';
|
||||||
|
import { PostgresParametersPage } from './postgresParameters';
|
||||||
|
import { PostgresModel, EngineSettingsModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export class PostgresCoordinatorNodeParametersPage extends PostgresParametersPage {
|
||||||
|
|
||||||
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, postgresModel: PostgresModel) {
|
||||||
|
super(modelView, dashboard, postgresModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get title(): string {
|
||||||
|
return loc.coordinatorNodeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get id(): string {
|
||||||
|
return 'postgres-coordinator-node-parameters';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get icon(): { dark: string; light: string; } {
|
||||||
|
return IconPathHelper.gearGray;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get description(): string {
|
||||||
|
return loc.coordinatorNodeParametersDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get engineSettings(): EngineSettingsModel[] {
|
||||||
|
return this._postgresModel.coordinatorNodeEngineSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async saveParameterEdits(): Promise<void> {
|
||||||
|
/* TODO add correct azdata call for editing coordinator parameters
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: engineSettings.toString() },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
session);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resetAllParameters(): Promise<void> {
|
||||||
|
/* TODO add correct azdata call for editing coordinator parameters
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: `''`, replaceEngineSettings: true },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
session);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resetParameter(): Promise<void> {
|
||||||
|
/* TODO add correct azdata call for editing coordinator parameters
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: parameterName + '=' },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
session);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,9 @@ import { Dashboard } from '../../components/dashboard';
|
|||||||
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
|
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
|
||||||
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
|
import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
|
||||||
import { PostgresComputeAndStoragePage } from './postgresComputeAndStoragePage';
|
import { PostgresComputeAndStoragePage } from './postgresComputeAndStoragePage';
|
||||||
import { PostgresParametersPage } from './postgresParametersPage';
|
import { PostgresWorkerNodeParametersPage } from './postgresWorkerNodeParametersPage';
|
||||||
import { PostgresPropertiesPage } from './postgresPropertiesPage';
|
import { PostgresPropertiesPage } from './postgresPropertiesPage';
|
||||||
|
import { PostgresResourceHealthPage } from './postgresResourceHealthPage';
|
||||||
|
|
||||||
export class PostgresDashboard extends Dashboard {
|
export class PostgresDashboard extends Dashboard {
|
||||||
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
@@ -31,13 +32,16 @@ export class PostgresDashboard extends Dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {
|
||||||
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
|
const overviewPage = new PostgresOverviewPage(modelView, this.dashboard, this._controllerModel, this._postgresModel);
|
||||||
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
|
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this.dashboard, this._postgresModel);
|
||||||
const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this._postgresModel);
|
const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this.dashboard, this._postgresModel);
|
||||||
const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
|
const propertiesPage = new PostgresPropertiesPage(modelView, this.dashboard, this._controllerModel, this._postgresModel);
|
||||||
const parametersPage = new PostgresParametersPage(modelView, this._postgresModel);
|
// TODO Add dashboard once backend is able to be connected for per role server parameter edits.
|
||||||
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
|
// const coordinatorNodeParametersPage = new PostgresCoordinatorNodeParametersPage(modelView, this._postgresModel);
|
||||||
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);
|
const workerNodeParametersPage = new PostgresWorkerNodeParametersPage(modelView, this.dashboard, this._postgresModel);
|
||||||
|
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this.dashboard, this._context, this._controllerModel, this._postgresModel);
|
||||||
|
const supportRequestPage = new PostgresSupportRequestPage(modelView, this.dashboard, this._controllerModel, this._postgresModel);
|
||||||
|
const resourceHealthPage = new PostgresResourceHealthPage(modelView, this.dashboard, this._postgresModel);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
overviewPage.tab,
|
overviewPage.tab,
|
||||||
@@ -47,12 +51,13 @@ export class PostgresDashboard extends Dashboard {
|
|||||||
propertiesPage.tab,
|
propertiesPage.tab,
|
||||||
connectionStringsPage.tab,
|
connectionStringsPage.tab,
|
||||||
computeAndStoragePage.tab,
|
computeAndStoragePage.tab,
|
||||||
parametersPage.tab
|
workerNodeParametersPage.tab
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: loc.supportAndTroubleshooting,
|
title: loc.supportAndTroubleshooting,
|
||||||
tabs: [
|
tabs: [
|
||||||
|
resourceHealthPage.tab,
|
||||||
diagnoseAndSolveProblemsPage.tab,
|
diagnoseAndSolveProblemsPage.tab,
|
||||||
supportRequestPage.tab
|
supportRequestPage.tab
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import * as loc from '../../../localizedConstants';
|
|||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
|
|
||||||
export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
|
export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
|
||||||
constructor(protected modelView: azdata.ModelView, private _context: vscode.ExtensionContext, private _postgresModel: PostgresModel) {
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
@@ -50,9 +51,8 @@ export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
|
|||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
troubleshootButton.onDidClick(() => {
|
troubleshootButton.onDidClick(() => {
|
||||||
process.env['POSTGRES_SERVER_NAMESPACE'] = this._postgresModel.config?.metadata.namespace;
|
process.env['POSTGRES_SERVER_NAMESPACE'] = this._controllerModel.controllerConfig?.metadata.namespace ?? '';
|
||||||
process.env['POSTGRES_SERVER_NAME'] = this._postgresModel.info.name;
|
process.env['POSTGRES_SERVER_NAME'] = this._postgresModel.info.name;
|
||||||
process.env['POSTGRES_SERVER_VERSION'] = this._postgresModel.engineVersion;
|
|
||||||
vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arcDataServices'), true, 'postgres/tsg100-troubleshoot-postgres');
|
vscode.commands.executeCommand('bookTreeView.openBook', this._context.asAbsolutePath('notebooks/arcDataServices'), true, 'postgres/tsg100-troubleshoot-postgres');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -7,27 +7,38 @@ import * as vscode from 'vscode';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as loc from '../../../localizedConstants';
|
import * as loc from '../../../localizedConstants';
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
import { IconPathHelper, cssStyles, iconSize } from '../../../constants';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { ControllerModel } from '../../../models/controllerModel';
|
import { ControllerModel } from '../../../models/controllerModel';
|
||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
import { promptAndConfirmPassword, promptForInstanceDeletion } from '../../../common/utils';
|
import { promptAndConfirmPassword, promptForInstanceDeletion } from '../../../common/utils';
|
||||||
import { ResourceType } from 'arc';
|
import { ResourceType } from 'arc';
|
||||||
|
|
||||||
|
export type PodStatusModel = {
|
||||||
|
podName: azdata.Component,
|
||||||
|
type: string,
|
||||||
|
status: string
|
||||||
|
};
|
||||||
|
|
||||||
export class PostgresOverviewPage extends DashboardPage {
|
export class PostgresOverviewPage extends DashboardPage {
|
||||||
|
|
||||||
private propertiesLoading!: azdata.LoadingComponent;
|
private propertiesLoading!: azdata.LoadingComponent;
|
||||||
|
private serverGroupNodesLoading!: azdata.LoadingComponent;
|
||||||
private kibanaLoading!: azdata.LoadingComponent;
|
private kibanaLoading!: azdata.LoadingComponent;
|
||||||
private grafanaLoading!: azdata.LoadingComponent;
|
private grafanaLoading!: azdata.LoadingComponent;
|
||||||
|
|
||||||
private properties!: azdata.PropertiesContainerComponent;
|
private properties!: azdata.PropertiesContainerComponent;
|
||||||
private kibanaLink!: azdata.HyperlinkComponent;
|
private kibanaLink!: azdata.HyperlinkComponent;
|
||||||
private grafanaLink!: azdata.HyperlinkComponent;
|
private grafanaLink!: azdata.HyperlinkComponent;
|
||||||
|
private deleteButton!: azdata.ButtonComponent;
|
||||||
|
|
||||||
|
private podStatusTable!: azdata.DeclarativeTableComponent;
|
||||||
|
private podStatusData: PodStatusModel[] = [];
|
||||||
|
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
private readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
@@ -132,8 +143,63 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
|
||||||
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
content.addItem(endpointsTable);
|
content.addItem(endpointsTable);
|
||||||
|
|
||||||
|
// Server Group Nodes
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||||
|
value: loc.serverGroupNodes,
|
||||||
|
CSSStyles: titleCSS
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
this.podStatusTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||||
|
width: '100%',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
displayName: loc.name,
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '35%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow,
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'text-overflow': 'ellipsis',
|
||||||
|
'white-space': 'nowrap',
|
||||||
|
'max-width': '0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.type,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '35%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.status,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '30%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: [this.podStatusData.map(p => [p.podName, p.type, p.status])]
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.serverGroupNodesLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.podStatusTable)
|
||||||
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
|
loading: !this._postgresModel.configLastUpdated
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.refreshServerNodes();
|
||||||
|
|
||||||
|
content.addItem(this.serverGroupNodesLoading, { CSSStyles: cssStyles.text });
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -151,21 +217,13 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
try {
|
try {
|
||||||
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
|
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
|
||||||
if (password) {
|
if (password) {
|
||||||
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
try {
|
this._postgresModel.info.name,
|
||||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
{
|
||||||
this._postgresModel.info.name,
|
adminPassword: true,
|
||||||
{
|
noWait: true
|
||||||
adminPassword: true,
|
},
|
||||||
noWait: true
|
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azdataAdditionalEnvVars));
|
||||||
},
|
|
||||||
this._postgresModel.engineVersion,
|
|
||||||
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azdataAdditionalEnvVars),
|
|
||||||
session
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
vscode.window.showInformationMessage(loc.passwordReset);
|
vscode.window.showInformationMessage(loc.passwordReset);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -176,14 +234,14 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Delete service
|
// Delete service
|
||||||
const deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
this.deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
label: loc.deleteText,
|
label: loc.deleteText,
|
||||||
iconPath: IconPathHelper.delete
|
iconPath: IconPathHelper.delete
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
deleteButton.onDidClick(async () => {
|
this.deleteButton.onDidClick(async () => {
|
||||||
deleteButton.enabled = false;
|
this.deleteButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
if (await promptForInstanceDeletion(this._postgresModel.info.name)) {
|
if (await promptForInstanceDeletion(this._postgresModel.info.name)) {
|
||||||
await vscode.window.withProgress(
|
await vscode.window.withProgress(
|
||||||
@@ -193,22 +251,23 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
cancellable: false
|
cancellable: false
|
||||||
},
|
},
|
||||||
async (_progress, _token) => {
|
async (_progress, _token) => {
|
||||||
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
|
||||||
try {
|
|
||||||
return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name, this._controllerModel.azdataAdditionalEnvVars, session);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await this._controllerModel.refreshTreeNode();
|
await this._controllerModel.refreshTreeNode();
|
||||||
vscode.window.showInformationMessage(loc.instanceDeleted(this._postgresModel.info.name));
|
vscode.window.showInformationMessage(loc.instanceDeleted(this._postgresModel.info.name));
|
||||||
|
try {
|
||||||
|
await this.dashboard.close();
|
||||||
|
} catch (err) {
|
||||||
|
// Failures closing the dashboard aren't something we need to show users
|
||||||
|
console.log('Error closing Arc Postgres dashboard ', err);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._postgresModel.info.name, error));
|
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._postgresModel.info.name, error));
|
||||||
} finally {
|
} finally {
|
||||||
deleteButton.enabled = true;
|
this.deleteButton.enabled = true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -223,6 +282,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
refreshButton.enabled = false;
|
refreshButton.enabled = false;
|
||||||
try {
|
try {
|
||||||
this.propertiesLoading!.loading = true;
|
this.propertiesLoading!.loading = true;
|
||||||
|
this.serverGroupNodesLoading!.loading = true;
|
||||||
this.kibanaLoading!.loading = true;
|
this.kibanaLoading!.loading = true;
|
||||||
this.grafanaLoading!.loading = true;
|
this.grafanaLoading!.loading = true;
|
||||||
|
|
||||||
@@ -257,7 +317,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
{ component: resetPasswordButton },
|
{ component: resetPasswordButton },
|
||||||
{ component: deleteButton },
|
{ component: this.deleteButton },
|
||||||
{ component: refreshButton, toolbarSeparatorAfter: true },
|
{ component: refreshButton, toolbarSeparatorAfter: true },
|
||||||
{ component: openInAzurePortalButton }
|
{ component: openInAzurePortalButton }
|
||||||
]).component();
|
]).component();
|
||||||
@@ -281,6 +341,54 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPodStatus(): PodStatusModel[] {
|
||||||
|
let podModels: PodStatusModel[] = [];
|
||||||
|
const podStatus = this._postgresModel.config?.status.podsStatus;
|
||||||
|
|
||||||
|
podStatus?.forEach(p => {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
const podLabelContainer = this.modelView.modelBuilder.flexContainer().withProps({
|
||||||
|
CSSStyles: { 'alignItems': 'center', 'height': '15px' }
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const imageComponent = this.modelView.modelBuilder.image().withProps({
|
||||||
|
iconPath: IconPathHelper.postgres,
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
iconHeight: '15px',
|
||||||
|
iconWidth: '15px'
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let podLabel = this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: p.name,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
if (p.role.toUpperCase() === loc.worker.toUpperCase()) {
|
||||||
|
podLabelContainer.addItem(imageComponent, { CSSStyles: { 'margin-left': '15px', 'margin-right': '0px' } });
|
||||||
|
podLabelContainer.addItem(podLabel);
|
||||||
|
let pod: PodStatusModel = {
|
||||||
|
podName: podLabelContainer,
|
||||||
|
type: loc.worker,
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
podModels.push(pod);
|
||||||
|
} else {
|
||||||
|
podLabelContainer.addItem(imageComponent, { CSSStyles: { 'margin-right': '0px' } });
|
||||||
|
podLabelContainer.addItem(podLabel);
|
||||||
|
let pod: PodStatusModel = {
|
||||||
|
podName: podLabelContainer,
|
||||||
|
type: loc.coordinator,
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
podModels.unshift(pod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return podModels;
|
||||||
|
}
|
||||||
|
|
||||||
private refreshDashboardLinks(): void {
|
private refreshDashboardLinks(): void {
|
||||||
if (this._postgresModel.config) {
|
if (this._postgresModel.config) {
|
||||||
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
|
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
|
||||||
@@ -295,6 +403,14 @@ 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.serverGroupNodesLoading.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private handleRegistrationsUpdated() {
|
private handleRegistrationsUpdated() {
|
||||||
this.properties!.propertyItems = this.getProperties();
|
this.properties!.propertyItems = this.getProperties();
|
||||||
this.propertiesLoading!.loading = false;
|
this.propertiesLoading!.loading = false;
|
||||||
@@ -304,5 +420,6 @@ export class PostgresOverviewPage extends DashboardPage {
|
|||||||
this.properties!.propertyItems = this.getProperties();
|
this.properties!.propertyItems = this.getProperties();
|
||||||
this.propertiesLoading!.loading = false;
|
this.propertiesLoading!.loading = false;
|
||||||
this.refreshDashboardLinks();
|
this.refreshDashboardLinks();
|
||||||
|
this.refreshServerNodes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
626
extensions/arc/src/ui/dashboards/postgres/postgresParameters.ts
Normal file
626
extensions/arc/src/ui/dashboards/postgres/postgresParameters.ts
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
|
import * as loc from '../../../localizedConstants';
|
||||||
|
import { UserCancelledError } from '../../../common/api';
|
||||||
|
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||||
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { EngineSettingsModel, PostgresModel } from '../../../models/postgresModel';
|
||||||
|
import { debounce } from '../../../common/utils';
|
||||||
|
|
||||||
|
export type ParametersModel = {
|
||||||
|
parameterName: string,
|
||||||
|
originalValue: string,
|
||||||
|
valueComponent: azdata.TextComponent | azdata.DropDownComponent | azdata.CheckBoxComponent,
|
||||||
|
information?: azdata.ButtonComponent,
|
||||||
|
description: string,
|
||||||
|
resetButton: azdata.ButtonComponent
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class PostgresParametersPage extends DashboardPage {
|
||||||
|
private searchBox!: azdata.InputBoxComponent;
|
||||||
|
protected _parametersTable!: azdata.DeclarativeTableComponent;
|
||||||
|
private parameterContainer!: azdata.DivContainer;
|
||||||
|
private parametersTableLoading?: azdata.LoadingComponent;
|
||||||
|
|
||||||
|
private discardButton!: azdata.ButtonComponent;
|
||||||
|
private saveButton!: azdata.ButtonComponent;
|
||||||
|
private resetAllButton!: azdata.ButtonComponent;
|
||||||
|
private connectToServerButton?: azdata.ButtonComponent;
|
||||||
|
|
||||||
|
protected _parameters: ParametersModel[] = [];
|
||||||
|
private changedComponentValues: Set<string> = new Set();
|
||||||
|
private parameterUpdates: Map<string, string> = new Map();
|
||||||
|
|
||||||
|
protected readonly _azdataApi: azdataExt.IExtension;
|
||||||
|
|
||||||
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, protected _postgresModel: PostgresModel) {
|
||||||
|
super(modelView, dashboard);
|
||||||
|
|
||||||
|
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
|
||||||
|
this.initializeSearchBox();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
|
||||||
|
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshParametersTable()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract get description(): string;
|
||||||
|
|
||||||
|
protected abstract get engineSettings(): EngineSettingsModel[];
|
||||||
|
|
||||||
|
protected get container(): azdata.Component {
|
||||||
|
const root = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
const content = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: this.title,
|
||||||
|
CSSStyles: { ...cssStyles.title }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: this.description,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.hyperlink().withProps({
|
||||||
|
label: loc.learnAboutNodeParameters,
|
||||||
|
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale'
|
||||||
|
}).component(), { CSSStyles: { 'margin-bottom': '20px' } });
|
||||||
|
|
||||||
|
content.addItem(this.searchBox!, { CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-bottom': '20px' } });
|
||||||
|
|
||||||
|
this._parametersTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||||
|
width: '100%',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
displayName: loc.parameterName,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '20%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.value,
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: false,
|
||||||
|
width: '20%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.description,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '50%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.resetToDefault,
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: false,
|
||||||
|
width: '10%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: []
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.parameterContainer = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
this.selectComponent();
|
||||||
|
content.addItem(this.parameterContainer);
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
|
// Save Edits
|
||||||
|
this.saveButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
label: loc.saveText,
|
||||||
|
iconPath: IconPathHelper.save,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let engineSettings: string[] = [];
|
||||||
|
this.disposables.push(
|
||||||
|
this.saveButton.onDidClick(async () => {
|
||||||
|
this.saveButton.enabled = false;
|
||||||
|
try {
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
async (_progress, _token): Promise<void> => {
|
||||||
|
try {
|
||||||
|
this.parameterUpdates.forEach((value, key) => {
|
||||||
|
engineSettings.push(`${key}="${value}"`);
|
||||||
|
});
|
||||||
|
await this.saveParameterEdits(engineSettings.toString());
|
||||||
|
} catch (err) {
|
||||||
|
// If an error occurs while editing the instance then re-enable the save button since
|
||||||
|
// the edit wasn't successfully applied
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._postgresModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||||
|
|
||||||
|
engineSettings = [];
|
||||||
|
this.changedComponentValues.clear();
|
||||||
|
this.parameterUpdates.clear();
|
||||||
|
this.discardButton.enabled = false;
|
||||||
|
this.resetAllButton.enabled = true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Discard
|
||||||
|
this.discardButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
label: loc.discardText,
|
||||||
|
iconPath: IconPathHelper.discard,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.discardButton.onDidClick(async () => {
|
||||||
|
this.discardButton.enabled = false;
|
||||||
|
try {
|
||||||
|
this.discardParametersTableChanges();
|
||||||
|
} catch (error) {
|
||||||
|
this.discardButton!.enabled = true;
|
||||||
|
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
||||||
|
} finally {
|
||||||
|
this.changedComponentValues.clear();
|
||||||
|
this.saveButton.enabled = false;
|
||||||
|
this.parameterUpdates.clear();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset all
|
||||||
|
this.resetAllButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
label: loc.resetAllToDefault,
|
||||||
|
iconPath: IconPathHelper.reset,
|
||||||
|
enabled: false
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.resetAllButton.onDidClick(async () => {
|
||||||
|
this.resetAllButton.enabled = false;
|
||||||
|
this.discardButton.enabled = false;
|
||||||
|
this.saveButton.enabled = false;
|
||||||
|
try {
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
async (_progress, _token): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await this.resetAllParameters();
|
||||||
|
} catch (err) {
|
||||||
|
// If an error occurs while resetting the instance then re-enable the reset button since
|
||||||
|
// the edit wasn't successfully applied
|
||||||
|
if (this.parameterUpdates.size > 0) {
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
}
|
||||||
|
this.resetAllButton.enabled = true;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.changedComponentValues.clear();
|
||||||
|
try {
|
||||||
|
await this._postgresModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||||
|
this.parameterUpdates.clear();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.resetFailed(error));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
|
{ component: this.saveButton },
|
||||||
|
{ component: this.discardButton },
|
||||||
|
{ component: this.resetAllButton }
|
||||||
|
]).component();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initializeConnectButton(): void {
|
||||||
|
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
label: loc.connectToServer,
|
||||||
|
enabled: false,
|
||||||
|
CSSStyles: { 'max-width': '125px' }
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.connectToServerButton.onDidClick(async () => {
|
||||||
|
this.connectToServerButton!.enabled = false;
|
||||||
|
if (!vscode.extensions.getExtension(loc.postgresExtension)) {
|
||||||
|
const response = await vscode.window.showErrorMessage(loc.missingExtension('PostgreSQL'), loc.yes, loc.no);
|
||||||
|
if (response !== loc.yes) {
|
||||||
|
this.connectToServerButton!.enabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: loc.installingExtension(loc.postgresExtension),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
async (_progress, _token): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
|
||||||
|
} catch (err) {
|
||||||
|
vscode.window.showErrorMessage(loc.extensionInstallationFailed(loc.postgresExtension));
|
||||||
|
this.connectToServerButton!.enabled = true;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
vscode.window.showInformationMessage(loc.extensionInstalled(loc.postgresExtension));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parametersTableLoading!.loading = true;
|
||||||
|
await this.callGetEngineSettings().finally(() => this.parametersTableLoading!.loading = false);
|
||||||
|
this.searchBox.enabled = true;
|
||||||
|
this.resetAllButton.enabled = true;
|
||||||
|
this.parameterContainer.clearItems();
|
||||||
|
this.parameterContainer.addItem(this._parametersTable);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectComponent(): void {
|
||||||
|
if (!this._postgresModel.engineSettingsLastUpdated) {
|
||||||
|
this.parameterContainer.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.connectToPostgresDescription,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
|
}).component());
|
||||||
|
this.initializeConnectButton();
|
||||||
|
this.parameterContainer.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
|
||||||
|
this.parametersTableLoading = this.modelView.modelBuilder.loadingComponent().component();
|
||||||
|
this.parameterContainer.addItem(this.parametersTableLoading);
|
||||||
|
} else {
|
||||||
|
this.searchBox.enabled = true;
|
||||||
|
this.resetAllButton.enabled = true;
|
||||||
|
this.parameterContainer.addItem(this._parametersTable!);
|
||||||
|
this.refreshParametersTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async callGetEngineSettings(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this._postgresModel.getEngineSettings();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof UserCancelledError) {
|
||||||
|
vscode.window.showWarningMessage(loc.pgConnectionRequired);
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
||||||
|
}
|
||||||
|
this.connectToServerButton!.enabled = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initializeSearchBox(): void {
|
||||||
|
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
readOnly: false,
|
||||||
|
enabled: false,
|
||||||
|
placeHolder: loc.searchToFilter
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this.searchBox.onTextChanged(() => {
|
||||||
|
this.onSearchFilter();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@debounce(500)
|
||||||
|
private onSearchFilter(): void {
|
||||||
|
if (!this.searchBox.value) {
|
||||||
|
this._parametersTable.setFilter(undefined);
|
||||||
|
} else {
|
||||||
|
this.filterParameters(this.searchBox.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterParameters(search: string): void {
|
||||||
|
const filteredRowIndexes: number[] = [];
|
||||||
|
this._parametersTable.data?.forEach((row, index) => {
|
||||||
|
if (row[0].toUpperCase()?.search(search.toUpperCase()) !== -1 || row[2].toUpperCase()?.search(search.toUpperCase()) !== -1) {
|
||||||
|
filteredRowIndexes.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._parametersTable.setFilter(filteredRowIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleOnTextChanged(component: azdata.InputBoxComponent, name: string, currentValue: string | undefined): boolean {
|
||||||
|
if (!component.valid) {
|
||||||
|
// If invalid value return false and enable discard button
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
this.collectChangedComponents(name);
|
||||||
|
return false;
|
||||||
|
} else if (component.value === currentValue) {
|
||||||
|
this.removeFromChangedComponents(name);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
/* If a valid value has been entered into the input box, enable save and discard buttons
|
||||||
|
so that user could choose to either edit instance or clear all inputs
|
||||||
|
return true */
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
this.collectChangedComponents(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createParameterComponents(engineSetting: EngineSettingsModel): ParametersModel {
|
||||||
|
|
||||||
|
// Can reset individual parameter
|
||||||
|
const resetParameterButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
iconPath: IconPathHelper.reset,
|
||||||
|
title: loc.resetToDefault,
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
enabled: true
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
resetParameterButton.onDidClick(async () => {
|
||||||
|
try {
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: loc.updatingInstance(this._postgresModel.info.name),
|
||||||
|
cancellable: false
|
||||||
|
},
|
||||||
|
async (_progress, _token): Promise<void> => {
|
||||||
|
await this.resetParameter(engineSetting.parameterName!);
|
||||||
|
try {
|
||||||
|
await this._postgresModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||||
|
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let valueComponent: azdata.Component;
|
||||||
|
if (engineSetting.type === 'enum') {
|
||||||
|
// If type is enum, component should be drop down menu
|
||||||
|
let options = engineSetting.options?.slice(1, -1).split(',');
|
||||||
|
let values: string[] = [];
|
||||||
|
options!.forEach(option => {
|
||||||
|
values.push(option.slice(option.indexOf('"') + 1, -1));
|
||||||
|
});
|
||||||
|
|
||||||
|
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
|
||||||
|
values: values,
|
||||||
|
value: engineSetting.value,
|
||||||
|
width: '150px'
|
||||||
|
}).component();
|
||||||
|
valueComponent = valueBox;
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
valueBox.onValueChanged(() => {
|
||||||
|
if (engineSetting.value !== String(valueBox.value)) {
|
||||||
|
this.parameterUpdates.set(engineSetting.parameterName!, String(valueBox.value));
|
||||||
|
this.collectChangedComponents(engineSetting.parameterName!);
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||||
|
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||||
|
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (engineSetting.type === 'bool') {
|
||||||
|
// If type is bool, component should be checkbox to turn on or off
|
||||||
|
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
|
||||||
|
label: loc.on,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
|
}).component();
|
||||||
|
valueComponent = valueBox;
|
||||||
|
|
||||||
|
if (engineSetting.value === 'on') {
|
||||||
|
valueBox.checked = true;
|
||||||
|
} else {
|
||||||
|
valueBox.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
valueBox.onChanged(() => {
|
||||||
|
if (valueBox.checked && engineSetting.value === 'off') {
|
||||||
|
this.parameterUpdates.set(engineSetting.parameterName!, loc.on);
|
||||||
|
this.collectChangedComponents(engineSetting.parameterName!);
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
} else if (!valueBox.checked && engineSetting.value === 'on') {
|
||||||
|
this.parameterUpdates.set(engineSetting.parameterName!, loc.off);
|
||||||
|
this.collectChangedComponents(engineSetting.parameterName!);
|
||||||
|
this.saveButton.enabled = true;
|
||||||
|
this.discardButton.enabled = true;
|
||||||
|
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||||
|
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||||
|
this.removeFromChangedComponents(engineSetting.parameterName!);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (engineSetting.type === 'string') {
|
||||||
|
// If type is string, component should be text inputbox
|
||||||
|
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: engineSetting.value,
|
||||||
|
width: '150px'
|
||||||
|
}).component();
|
||||||
|
valueComponent = valueBox;
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
valueBox.onTextChanged(() => {
|
||||||
|
if ((this.handleOnTextChanged(valueBox, engineSetting.parameterName!, engineSetting.value))) {
|
||||||
|
this.parameterUpdates.set(engineSetting.parameterName!, `"${valueBox.value!}"`);
|
||||||
|
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||||
|
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
|
||||||
|
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
min: parseInt(engineSetting.min!),
|
||||||
|
max: parseInt(engineSetting.max!),
|
||||||
|
inputType: 'number',
|
||||||
|
value: engineSetting.value,
|
||||||
|
width: '150px'
|
||||||
|
}).component();
|
||||||
|
valueComponent = valueBox;
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
valueBox.onTextChanged(() => {
|
||||||
|
if ((this.handleOnTextChanged(valueBox, engineSetting.parameterName!, engineSetting.value))) {
|
||||||
|
this.parameterUpdates.set(engineSetting.parameterName!, valueBox.value!);
|
||||||
|
} else if (this.parameterUpdates.has(engineSetting.parameterName!)) {
|
||||||
|
this.parameterUpdates.delete(engineSetting.parameterName!);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Information bubble title to show allowed values
|
||||||
|
let information = this.modelView.modelBuilder.button().withProps({
|
||||||
|
iconPath: IconPathHelper.information,
|
||||||
|
width: '15px',
|
||||||
|
height: '15px',
|
||||||
|
enabled: false,
|
||||||
|
title: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
return {
|
||||||
|
parameterName: engineSetting.parameterName!,
|
||||||
|
originalValue: engineSetting.value!,
|
||||||
|
valueComponent: valueComponent,
|
||||||
|
information: information,
|
||||||
|
description: engineSetting.description!,
|
||||||
|
resetButton: resetParameterButton
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parameterName: engineSetting.parameterName!,
|
||||||
|
originalValue: engineSetting.value!,
|
||||||
|
valueComponent: valueComponent,
|
||||||
|
description: engineSetting.description!,
|
||||||
|
resetButton: resetParameterButton
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectChangedComponents(name: string): void {
|
||||||
|
if (!this.changedComponentValues.has(name)) {
|
||||||
|
this.changedComponentValues.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeFromChangedComponents(name: string): void {
|
||||||
|
if (this.changedComponentValues.has(name)) {
|
||||||
|
this.changedComponentValues.delete(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private discardParametersTableChanges(): void {
|
||||||
|
let instanceOfCheckBox = function (object: any): object is azdata.CheckBoxComponent {
|
||||||
|
return 'checked' in object;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.changedComponentValues.forEach(v => {
|
||||||
|
let param = this._parameters.find(p => p.parameterName === v);
|
||||||
|
if (instanceOfCheckBox(param!.valueComponent)) {
|
||||||
|
if (param!.originalValue === 'on') {
|
||||||
|
param!.valueComponent.checked = true;
|
||||||
|
} else {
|
||||||
|
param!.valueComponent.checked = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param!.valueComponent.value = param!.originalValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshParametersTable(): void {
|
||||||
|
this._parameters = this.engineSettings.map(parameter => this.createParameterComponents(parameter));
|
||||||
|
|
||||||
|
this._parametersTable.data = this._parameters.map(p => {
|
||||||
|
if (p.information) {
|
||||||
|
// Container to hold input component and information bubble
|
||||||
|
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||||
|
valueContainer.addItem(p.valueComponent, { CSSStyles: { 'margin-right': '0px' } });
|
||||||
|
valueContainer.addItem(p.information, { CSSStyles: { 'margin-left': '5px' } });
|
||||||
|
return [p.parameterName, valueContainer, p.description, p.resetButton];
|
||||||
|
} else {
|
||||||
|
return [p.parameterName, p.valueComponent, p.description, p.resetButton];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleServiceUpdated(): Promise<void> {
|
||||||
|
if (this._postgresModel.configLastUpdated && !this._postgresModel.engineSettingsLastUpdated) {
|
||||||
|
this.connectToServerButton!.enabled = true;
|
||||||
|
this.parametersTableLoading!.loading = false;
|
||||||
|
} else if (this._postgresModel.engineSettingsLastUpdated) {
|
||||||
|
await this.callGetEngineSettings();
|
||||||
|
this.discardButton.enabled = false;
|
||||||
|
this.saveButton.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract saveParameterEdits(engineSettings: string): Promise<void>;
|
||||||
|
|
||||||
|
protected abstract resetAllParameters(): Promise<void>;
|
||||||
|
|
||||||
|
protected abstract resetParameter(parameterName: string): Promise<void>;
|
||||||
|
}
|
||||||
@@ -1,588 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import * as azdata from 'azdata';
|
|
||||||
import * as azdataExt from 'azdata-ext';
|
|
||||||
import * as loc from '../../../localizedConstants';
|
|
||||||
import { UserCancelledError } from '../../../common/api';
|
|
||||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
|
||||||
import { EngineSettingsModel, PostgresModel } from '../../../models/postgresModel';
|
|
||||||
import { debounce } from '../../../common/utils';
|
|
||||||
|
|
||||||
export type ParametersModel = {
|
|
||||||
parameterName: string,
|
|
||||||
valueContainer: azdata.FlexContainer,
|
|
||||||
description: string,
|
|
||||||
resetButton: azdata.ButtonComponent
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PostgresParametersPage extends DashboardPage {
|
|
||||||
private searchBox!: azdata.InputBoxComponent;
|
|
||||||
private parametersTable!: azdata.DeclarativeTableComponent;
|
|
||||||
private parameterContainer?: azdata.DivContainer;
|
|
||||||
private _parametersTableLoading!: azdata.LoadingComponent;
|
|
||||||
|
|
||||||
private discardButton!: azdata.ButtonComponent;
|
|
||||||
private saveButton!: azdata.ButtonComponent;
|
|
||||||
private resetAllButton!: azdata.ButtonComponent;
|
|
||||||
private connectToServerButton?: azdata.ButtonComponent;
|
|
||||||
|
|
||||||
private _parameters: ParametersModel[] = [];
|
|
||||||
private parameterUpdates: Map<string, string> = new Map();
|
|
||||||
|
|
||||||
private readonly _azdataApi: azdataExt.IExtension;
|
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
|
|
||||||
super(modelView);
|
|
||||||
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
|
||||||
|
|
||||||
this.initializeConnectButton();
|
|
||||||
this.initializeSearchBox();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
|
|
||||||
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshParametersTable()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get title(): string {
|
|
||||||
return loc.nodeParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get id(): string {
|
|
||||||
return 'postgres-node-parameters';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get icon(): { dark: string; light: string; } {
|
|
||||||
return IconPathHelper.gear;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get container(): azdata.Component {
|
|
||||||
const root = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
const content = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
|
||||||
value: loc.nodeParameters,
|
|
||||||
CSSStyles: { ...cssStyles.title }
|
|
||||||
}).component());
|
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.text().withProps({
|
|
||||||
value: loc.nodeParametersDescription,
|
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
||||||
}).component());
|
|
||||||
|
|
||||||
content.addItem(this.modelView.modelBuilder.hyperlink().withProps({
|
|
||||||
label: loc.learnAboutNodeParameters,
|
|
||||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale'
|
|
||||||
}).component(), { CSSStyles: { 'margin-bottom': '20px' } });
|
|
||||||
|
|
||||||
content.addItem(this.searchBox!, { CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-bottom': '20px' } });
|
|
||||||
|
|
||||||
this.parametersTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
|
||||||
width: '100%',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
displayName: loc.parameterName,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '20%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.value,
|
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
|
||||||
isReadOnly: false,
|
|
||||||
width: '20%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.description,
|
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
|
||||||
isReadOnly: true,
|
|
||||||
width: '50%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: {
|
|
||||||
...cssStyles.tableRow
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: loc.resetToDefault,
|
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
|
||||||
isReadOnly: false,
|
|
||||||
width: '10%',
|
|
||||||
headerCssStyles: cssStyles.tableHeader,
|
|
||||||
rowCssStyles: cssStyles.tableRow
|
|
||||||
}
|
|
||||||
],
|
|
||||||
data: []
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._parametersTableLoading = this.modelView.modelBuilder.loadingComponent().component();
|
|
||||||
|
|
||||||
this.parameterContainer = this.modelView.modelBuilder.divContainer().component();
|
|
||||||
this.selectComponent();
|
|
||||||
|
|
||||||
content.addItem(this.parameterContainer);
|
|
||||||
|
|
||||||
this.initialized = true;
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get toolbarContainer(): azdata.ToolbarContainer {
|
|
||||||
// Save Edits
|
|
||||||
this.saveButton = this.modelView.modelBuilder.button().withProps({
|
|
||||||
label: loc.saveText,
|
|
||||||
iconPath: IconPathHelper.save,
|
|
||||||
enabled: false
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
let engineSettings: string[] = [];
|
|
||||||
this.disposables.push(
|
|
||||||
this.saveButton.onDidClick(async () => {
|
|
||||||
this.saveButton!.enabled = false;
|
|
||||||
try {
|
|
||||||
await vscode.window.withProgress(
|
|
||||||
{
|
|
||||||
location: vscode.ProgressLocation.Notification,
|
|
||||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
|
||||||
cancellable: false
|
|
||||||
},
|
|
||||||
async (_progress, _token): Promise<void> => {
|
|
||||||
try {
|
|
||||||
this.parameterUpdates!.forEach((value, key) => {
|
|
||||||
engineSettings.push(`${key}="${value}"`);
|
|
||||||
});
|
|
||||||
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
|
||||||
try {
|
|
||||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
|
||||||
this._postgresModel.info.name,
|
|
||||||
{ engineSettings: engineSettings.toString() },
|
|
||||||
this._postgresModel.engineVersion,
|
|
||||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
|
||||||
session);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// If an error occurs while editing the instance then re-enable the save button since
|
|
||||||
// the edit wasn't successfully applied
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
await this._postgresModel.refresh();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
|
||||||
|
|
||||||
engineSettings = [];
|
|
||||||
this.parameterUpdates!.clear();
|
|
||||||
this.discardButton!.enabled = false;
|
|
||||||
this.resetAllButton!.enabled = true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Discard
|
|
||||||
this.discardButton = this.modelView.modelBuilder.button().withProps({
|
|
||||||
label: loc.discardText,
|
|
||||||
iconPath: IconPathHelper.discard,
|
|
||||||
enabled: false
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
this.discardButton.onDidClick(async () => {
|
|
||||||
this.discardButton!.enabled = false;
|
|
||||||
try {
|
|
||||||
this.refreshParametersTable();
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
|
|
||||||
} finally {
|
|
||||||
this.saveButton!.enabled = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset all
|
|
||||||
this.resetAllButton = this.modelView.modelBuilder.button().withProps({
|
|
||||||
label: loc.resetAllToDefault,
|
|
||||||
iconPath: IconPathHelper.reset,
|
|
||||||
enabled: false
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
this.resetAllButton.onDidClick(async () => {
|
|
||||||
this.resetAllButton!.enabled = false;
|
|
||||||
this.discardButton!.enabled = false;
|
|
||||||
this.saveButton!.enabled = false;
|
|
||||||
try {
|
|
||||||
await vscode.window.withProgress(
|
|
||||||
{
|
|
||||||
location: vscode.ProgressLocation.Notification,
|
|
||||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
|
||||||
cancellable: false
|
|
||||||
},
|
|
||||||
async (_progress, _token): Promise<void> => {
|
|
||||||
//all
|
|
||||||
// azdata arc postgres server edit -n <server group name> -e '' -re
|
|
||||||
let session: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
try {
|
|
||||||
session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
|
||||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
|
||||||
this._postgresModel.info.name,
|
|
||||||
{ engineSettings: `''`, replaceEngineSettings: true },
|
|
||||||
this._postgresModel.engineVersion,
|
|
||||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
|
||||||
session);
|
|
||||||
} catch (err) {
|
|
||||||
// If an error occurs while resetting the instance then re-enable the reset button since
|
|
||||||
// the edit wasn't successfully applied
|
|
||||||
if (this.parameterUpdates.size > 0) {
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
}
|
|
||||||
this.resetAllButton!.enabled = true;
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
session?.dispose();
|
|
||||||
}
|
|
||||||
await this._postgresModel.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
|
||||||
this.parameterUpdates!.clear();
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.resetFailed(error));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
|
||||||
{ component: this.saveButton },
|
|
||||||
{ component: this.discardButton },
|
|
||||||
{ component: this.resetAllButton }
|
|
||||||
]).component();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeConnectButton(): void {
|
|
||||||
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
|
|
||||||
label: loc.connectToServer,
|
|
||||||
enabled: false,
|
|
||||||
CSSStyles: { 'max-width': '125px' }
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
this.connectToServerButton!.onDidClick(async () => {
|
|
||||||
this.connectToServerButton!.enabled = false;
|
|
||||||
if (!vscode.extensions.getExtension(loc.postgresExtension)) {
|
|
||||||
const response = await vscode.window.showErrorMessage(loc.missingExtension('PostgreSQL'), loc.yes, loc.no);
|
|
||||||
if (response !== loc.yes) {
|
|
||||||
this.connectToServerButton!.enabled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await vscode.window.withProgress(
|
|
||||||
{
|
|
||||||
location: vscode.ProgressLocation.Notification,
|
|
||||||
title: loc.installingExtension(loc.postgresExtension),
|
|
||||||
cancellable: false
|
|
||||||
},
|
|
||||||
async (_progress, _token): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
|
|
||||||
} catch (err) {
|
|
||||||
vscode.window.showErrorMessage(loc.extensionInstallationFailed(loc.postgresExtension));
|
|
||||||
this.connectToServerButton!.enabled = true;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
vscode.window.showInformationMessage(loc.extensionInstalled(loc.postgresExtension));
|
|
||||||
}
|
|
||||||
|
|
||||||
this._parametersTableLoading!.loading = true;
|
|
||||||
await this.callGetEngineSettings().finally(() => this._parametersTableLoading!.loading = false);
|
|
||||||
this.searchBox!.enabled = true;
|
|
||||||
this.resetAllButton!.enabled = true;
|
|
||||||
this.parameterContainer!.clearItems();
|
|
||||||
this.parameterContainer!.addItem(this.parametersTable);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private selectComponent(): void {
|
|
||||||
if (!this._postgresModel.engineSettingsLastUpdated) {
|
|
||||||
this.parameterContainer!.addItem(this.modelView.modelBuilder.text().withProps({
|
|
||||||
value: loc.connectToPostgresDescription,
|
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
||||||
}).component());
|
|
||||||
this.parameterContainer!.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
|
|
||||||
this.parameterContainer!.addItem(this._parametersTableLoading!);
|
|
||||||
} else {
|
|
||||||
this.searchBox!.enabled = true;
|
|
||||||
this.resetAllButton!.enabled = true;
|
|
||||||
this.parameterContainer!.addItem(this.parametersTable!);
|
|
||||||
this.refreshParametersTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async callGetEngineSettings(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this._postgresModel.getEngineSettings();
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof UserCancelledError) {
|
|
||||||
vscode.window.showWarningMessage(loc.pgConnectionRequired);
|
|
||||||
} else {
|
|
||||||
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
|
|
||||||
}
|
|
||||||
this.connectToServerButton!.enabled = true;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeSearchBox(): void {
|
|
||||||
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
|
|
||||||
readOnly: false,
|
|
||||||
enabled: false,
|
|
||||||
placeHolder: loc.searchToFilter
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
this.searchBox.onTextChanged(() => {
|
|
||||||
this.onSearchFilter();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@debounce(500)
|
|
||||||
private onSearchFilter(): void {
|
|
||||||
if (!this.searchBox!.value) {
|
|
||||||
this.parametersTable.setFilter(undefined);
|
|
||||||
} else {
|
|
||||||
this.filterParameters(this.searchBox!.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private filterParameters(search: string): void {
|
|
||||||
const filteredRowIndexes: number[] = [];
|
|
||||||
this.parametersTable.data?.forEach((row, index) => {
|
|
||||||
if (row[0].toUpperCase()?.search(search.toUpperCase()) !== -1 || row[2].toUpperCase()?.search(search.toUpperCase()) !== -1) {
|
|
||||||
filteredRowIndexes.push(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.parametersTable.setFilter(filteredRowIndexes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleOnTextChanged(component: azdata.InputBoxComponent, currentValue: string | undefined): boolean {
|
|
||||||
if (!component.valid) {
|
|
||||||
// If invalid value return false and enable discard button
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
return false;
|
|
||||||
} else if (component.value === currentValue) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
/* If a valid value has been entered into the input box, enable save and discard buttons
|
|
||||||
so that user could choose to either edit instance or clear all inputs
|
|
||||||
return true */
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createParameterComponents(engineSetting: EngineSettingsModel): ParametersModel {
|
|
||||||
|
|
||||||
// Container to hold input component and information bubble
|
|
||||||
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
|
||||||
|
|
||||||
if (engineSetting.type === 'enum') {
|
|
||||||
// If type is enum, component should be drop down menu
|
|
||||||
let options = engineSetting.options?.slice(1, -1).split(',');
|
|
||||||
let values: string[] = [];
|
|
||||||
options!.forEach(option => {
|
|
||||||
values.push(option.slice(option.indexOf('"') + 1, -1));
|
|
||||||
});
|
|
||||||
|
|
||||||
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
|
|
||||||
values: values,
|
|
||||||
value: engineSetting.value,
|
|
||||||
width: '150px'
|
|
||||||
}).component();
|
|
||||||
valueContainer.addItem(valueBox);
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
valueBox.onValueChanged(() => {
|
|
||||||
if (engineSetting.value !== String(valueBox.value)) {
|
|
||||||
this.parameterUpdates!.set(engineSetting.parameterName!, String(valueBox.value));
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
|
|
||||||
this.parameterUpdates!.delete(engineSetting.parameterName!);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else if (engineSetting.type === 'bool') {
|
|
||||||
// If type is bool, component should be checkbox to turn on or off
|
|
||||||
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
|
|
||||||
label: loc.on,
|
|
||||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
|
||||||
}).component();
|
|
||||||
valueContainer.addItem(valueBox);
|
|
||||||
|
|
||||||
if (engineSetting.value === 'on') {
|
|
||||||
valueBox.checked = true;
|
|
||||||
} else {
|
|
||||||
valueBox.checked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
valueBox.onChanged(() => {
|
|
||||||
if (valueBox.checked && engineSetting.value === 'off') {
|
|
||||||
this.parameterUpdates!.set(engineSetting.parameterName!, loc.on);
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
} else if (!valueBox.checked && engineSetting.value === 'on') {
|
|
||||||
this.parameterUpdates!.set(engineSetting.parameterName!, loc.off);
|
|
||||||
this.saveButton!.enabled = true;
|
|
||||||
this.discardButton!.enabled = true;
|
|
||||||
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
|
|
||||||
this.parameterUpdates!.delete(engineSetting.parameterName!);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else if (engineSetting.type === 'string') {
|
|
||||||
// If type is string, component should be text inputbox
|
|
||||||
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: engineSetting.value,
|
|
||||||
width: '150px'
|
|
||||||
}).component();
|
|
||||||
valueContainer.addItem(valueBox);
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
valueBox.onTextChanged(() => {
|
|
||||||
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
|
|
||||||
this.parameterUpdates!.set(engineSetting.parameterName!, `"${valueBox.value!}"`);
|
|
||||||
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
|
|
||||||
this.parameterUpdates!.delete(engineSetting.parameterName!);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
|
|
||||||
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
min: parseInt(engineSetting.min!),
|
|
||||||
max: parseInt(engineSetting.max!),
|
|
||||||
validationErrorMessage: loc.outOfRange(engineSetting.min!, engineSetting.max!),
|
|
||||||
inputType: 'number',
|
|
||||||
value: engineSetting.value,
|
|
||||||
width: '150px'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px' } });
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
valueBox.onTextChanged(() => {
|
|
||||||
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
|
|
||||||
this.parameterUpdates!.set(engineSetting.parameterName!, valueBox.value!);
|
|
||||||
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
|
|
||||||
this.parameterUpdates!.delete(engineSetting.parameterName!);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Information bubble title to show allowed values
|
|
||||||
let information = this.modelView.modelBuilder.button().withProps({
|
|
||||||
iconPath: IconPathHelper.information,
|
|
||||||
width: '15px',
|
|
||||||
height: '15px',
|
|
||||||
enabled: false,
|
|
||||||
title: loc.allowedValue(loc.rangeSetting(engineSetting.min!, engineSetting.max!))
|
|
||||||
}).component();
|
|
||||||
valueContainer.addItem(information, { CSSStyles: { 'margin-left': '5px' } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can reset individual parameter
|
|
||||||
const resetParameterButton = this.modelView.modelBuilder.button().withProps({
|
|
||||||
iconPath: IconPathHelper.reset,
|
|
||||||
title: loc.resetToDefault,
|
|
||||||
width: '20px',
|
|
||||||
height: '20px',
|
|
||||||
enabled: true
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.disposables.push(
|
|
||||||
resetParameterButton.onDidClick(async () => {
|
|
||||||
try {
|
|
||||||
await vscode.window.withProgress(
|
|
||||||
{
|
|
||||||
location: vscode.ProgressLocation.Notification,
|
|
||||||
title: loc.updatingInstance(this._postgresModel.info.name),
|
|
||||||
cancellable: false
|
|
||||||
},
|
|
||||||
async (_progress, _token): Promise<void> => {
|
|
||||||
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
|
|
||||||
try {
|
|
||||||
await this._azdataApi.azdata.arc.postgres.server.edit(
|
|
||||||
this._postgresModel.info.name,
|
|
||||||
{ engineSettings: engineSetting.parameterName + '=' },
|
|
||||||
this._postgresModel.engineVersion,
|
|
||||||
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
|
||||||
session);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
await this._postgresModel.refresh();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
|
|
||||||
} catch (error) {
|
|
||||||
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let parameter: ParametersModel = {
|
|
||||||
parameterName: engineSetting.parameterName!,
|
|
||||||
valueContainer: valueContainer,
|
|
||||||
description: engineSetting.description!,
|
|
||||||
resetButton: resetParameterButton
|
|
||||||
};
|
|
||||||
|
|
||||||
return parameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private refreshParametersTable(): void {
|
|
||||||
this._parameters = this._postgresModel._engineSettings.map(engineSetting => this.createParameterComponents(engineSetting));
|
|
||||||
this.parametersTable.data = this._parameters.map(p => [p.parameterName, p.valueContainer, p.description, p.resetButton]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleServiceUpdated(): Promise<void> {
|
|
||||||
if (this._postgresModel.configLastUpdated && !this._postgresModel.engineSettingsLastUpdated) {
|
|
||||||
this.connectToServerButton!.enabled = true;
|
|
||||||
this._parametersTableLoading!.loading = false;
|
|
||||||
} else if (this._postgresModel.engineSettingsLastUpdated) {
|
|
||||||
await this.callGetEngineSettings();
|
|
||||||
this.discardButton!.enabled = false;
|
|
||||||
this.saveButton!.enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,8 @@ export class PostgresPropertiesPage extends DashboardPage {
|
|||||||
private loading?: azdata.LoadingComponent;
|
private loading?: azdata.LoadingComponent;
|
||||||
private keyValueContainer?: KeyValueContainer;
|
private keyValueContainer?: KeyValueContainer;
|
||||||
|
|
||||||
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
|
|
||||||
this.disposables.push(this._postgresModel.onConfigUpdated(
|
this.disposables.push(this._postgresModel.onConfigUpdated(
|
||||||
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
|
||||||
@@ -100,13 +100,12 @@ export class PostgresPropertiesPage extends DashboardPage {
|
|||||||
return [
|
return [
|
||||||
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, endpoint ? `postgresql://postgres@${endpoint.ip}:${endpoint.port}` : ''),
|
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, endpoint ? `postgresql://postgres@${endpoint.ip}:${endpoint.port}` : ''),
|
||||||
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
|
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.status, status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : loc.unknown),
|
new InputKeyValue(this.modelView.modelBuilder, loc.subscriptionId, this._controllerModel.controllerConfig?.spec.settings.azure.subscription ?? ''),
|
||||||
new LinkKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? '', () => controllerDashboard.showDashboard()),
|
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? ''),
|
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.scaleConfiguration ?? ''),
|
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.engineVersion ?? ''),
|
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.resourceGroup, this._controllerModel.controllerConfig?.spec.settings.azure.resourceGroup ?? ''),
|
new TextKeyValue(this.modelView.modelBuilder, loc.resourceGroup, this._controllerModel.controllerConfig?.spec.settings.azure.resourceGroup ?? ''),
|
||||||
new TextKeyValue(this.modelView.modelBuilder, loc.subscriptionId, this._controllerModel.controllerConfig?.spec.settings.azure.subscription ?? '')
|
new LinkKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.name ?? '', () => controllerDashboard.showDashboard()),
|
||||||
|
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.scaleConfiguration ?? ''),
|
||||||
|
new TextKeyValue(this.modelView.modelBuilder, loc.status, status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : loc.unknown),
|
||||||
|
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.engineVersion ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as loc from '../../../localizedConstants';
|
||||||
|
import { IconPathHelper, cssStyles, iconSize } from '../../../constants';
|
||||||
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export type PodHealthModel = {
|
||||||
|
condition: string,
|
||||||
|
details?: azdata.Component,
|
||||||
|
lastUpdate: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum PodConditionType {
|
||||||
|
initialized = 'Initialized',
|
||||||
|
ready = 'Ready',
|
||||||
|
containersReady = 'ContainersReady',
|
||||||
|
podScheduled = 'PodScheduled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostgresResourceHealthPage extends DashboardPage {
|
||||||
|
private podSummaryContainer!: azdata.DivContainer;
|
||||||
|
|
||||||
|
private podConditionsContainer!: azdata.DivContainer;
|
||||||
|
private podConditionsLoading!: azdata.LoadingComponent;
|
||||||
|
private podConditionsTable!: azdata.DeclarativeTableComponent;
|
||||||
|
private podConditionsTableIndexes: Map<string, number[]> = new Map();
|
||||||
|
|
||||||
|
private podDropDown!: azdata.DropDownComponent;
|
||||||
|
private coordinatorPodName!: string;
|
||||||
|
private coordinatorData: PodHealthModel[] = [];
|
||||||
|
private podsData: PodHealthModel[] = [];
|
||||||
|
|
||||||
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
|
||||||
|
super(modelView, dashboard);
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated())));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get title(): string {
|
||||||
|
return loc.resourceHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get id(): string {
|
||||||
|
return 'postgres-resource-health';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get icon(): { dark: string; light: string; } {
|
||||||
|
return IconPathHelper.health;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get container(): azdata.Component {
|
||||||
|
const root = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
const content = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.resourceHealth,
|
||||||
|
CSSStyles: { ...cssStyles.title }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.resourceHealthDescription,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
this.podSummaryContainer = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
|
||||||
|
this.refreshPodSummarySection();
|
||||||
|
|
||||||
|
content.addItem(this.podSummaryContainer);
|
||||||
|
|
||||||
|
// Pod Conditions
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.podsPresent,
|
||||||
|
CSSStyles: { ...cssStyles.title }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.podsUsedDescription,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-top': '10px' }
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
this.podConditionsContainer = this.modelView.modelBuilder.divContainer().component();
|
||||||
|
this.podConditionsTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
||||||
|
width: '100%',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
displayName: loc.condition,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '20%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.details,
|
||||||
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '50%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: {
|
||||||
|
...cssStyles.tableRow,
|
||||||
|
'min-width': '150px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: loc.lastTransition,
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: '30%',
|
||||||
|
headerCssStyles: cssStyles.tableHeader,
|
||||||
|
rowCssStyles: cssStyles.tableRow
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: [this.coordinatorData.map(p => [p.condition, p.details, p.lastUpdate])]
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.podDropDown = this.modelView.modelBuilder.dropDown().withProps({ width: '150px' }).component();
|
||||||
|
this.disposables.push(
|
||||||
|
this.podDropDown.onValueChanged(() => {
|
||||||
|
this.podConditionsTable.setFilter(this.podConditionsTableIndexes.get(String(this.podDropDown.value)));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.podConditionsContainer.addItem(this.podDropDown, { CSSStyles: { 'margin': '10px 0px 10px 0px' } });
|
||||||
|
this.podConditionsContainer.addItem(this.podConditionsTable);
|
||||||
|
this.podConditionsLoading = this.modelView.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.podConditionsContainer)
|
||||||
|
.withProperties<azdata.LoadingComponentProperties>({
|
||||||
|
loading: !this._postgresModel.configLastUpdated
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.refreshPodConditions();
|
||||||
|
|
||||||
|
content.addItem(this.podConditionsLoading, { CSSStyles: cssStyles.text });
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get toolbarContainer(): azdata.ToolbarContainer {
|
||||||
|
// Refresh
|
||||||
|
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
||||||
|
label: loc.refresh,
|
||||||
|
iconPath: IconPathHelper.refresh
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
refreshButton.onDidClick(async () => {
|
||||||
|
refreshButton.enabled = false;
|
||||||
|
try {
|
||||||
|
this.podConditionsLoading!.loading = true;
|
||||||
|
await this._postgresModel.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
refreshButton.enabled = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
|
||||||
|
{ component: refreshButton }
|
||||||
|
]).component();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPodList(): string[] {
|
||||||
|
const podStatus = this._postgresModel.config?.status.podsStatus;
|
||||||
|
let podNames: string[] = [];
|
||||||
|
|
||||||
|
podStatus?.forEach(p => {
|
||||||
|
let podHealthModels: PodHealthModel[] = [];
|
||||||
|
let indexes: number[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
p.conditions.forEach(c => {
|
||||||
|
let message: string;
|
||||||
|
let imageComponent = this.modelView.modelBuilder.image().withProps({
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
iconHeight: '15px',
|
||||||
|
iconWidth: '15px'
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
if (c.status === 'False') {
|
||||||
|
imageComponent.iconPath = IconPathHelper.fail;
|
||||||
|
message = c.message ?? c.reason ?? '';
|
||||||
|
} else {
|
||||||
|
imageComponent.iconPath = IconPathHelper.success;
|
||||||
|
|
||||||
|
if (c.type === PodConditionType.initialized) {
|
||||||
|
message = loc.podInitialized;
|
||||||
|
} else if (c.type === PodConditionType.ready) {
|
||||||
|
message = loc.podReady;
|
||||||
|
} else if (c.type === PodConditionType.containersReady) {
|
||||||
|
message = loc.containerReady;
|
||||||
|
} else if (c.type === PodConditionType.podScheduled) {
|
||||||
|
message = loc.podScheduled;
|
||||||
|
} else {
|
||||||
|
message = c.message ?? c.reason ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionContainer = this.modelView.modelBuilder.flexContainer().withProps({
|
||||||
|
CSSStyles: { 'alignItems': 'center', 'height': '15px' }
|
||||||
|
}).component();
|
||||||
|
conditionContainer.addItem(imageComponent, { CSSStyles: { 'margin-right': '0px' } });
|
||||||
|
conditionContainer.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: message,
|
||||||
|
}).component());
|
||||||
|
|
||||||
|
indexes.push(this.podsData.length);
|
||||||
|
this.podsData.push({
|
||||||
|
condition: c.type,
|
||||||
|
details: conditionContainer,
|
||||||
|
lastUpdate: c.lastTransitionTime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (p.role.toUpperCase() !== loc.coordinator.toUpperCase()) {
|
||||||
|
podNames.push(p.name);
|
||||||
|
} else {
|
||||||
|
this.coordinatorData = podHealthModels;
|
||||||
|
this.coordinatorPodName = p.name;
|
||||||
|
podNames.unshift(p.name);
|
||||||
|
}
|
||||||
|
this.podConditionsTableIndexes.set(p.name, indexes);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.podConditionsTable.data = this.podsData.map(p => [p.condition, p.details, p.lastUpdate]);
|
||||||
|
|
||||||
|
return podNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findPodIssues(): string[] {
|
||||||
|
const podStatus = this._postgresModel.config?.status.podsStatus;
|
||||||
|
let issueCount = 0;
|
||||||
|
let podIssuesDetected: string[] = [];
|
||||||
|
|
||||||
|
podStatus?.forEach(p => {
|
||||||
|
p.conditions.forEach(c => {
|
||||||
|
if (c.status === 'False') {
|
||||||
|
issueCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issueCount > 0) {
|
||||||
|
podIssuesDetected.push(loc.numberOfIssuesDetected(p.name, issueCount));
|
||||||
|
issueCount = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return podIssuesDetected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshPodSummarySection(): void {
|
||||||
|
let podSummaryTitle = this.modelView.modelBuilder.flexContainer().withProps({
|
||||||
|
CSSStyles: { 'alignItems': 'center', 'height': '15px', 'margin-top': '20px' }
|
||||||
|
}).component();
|
||||||
|
if (!this._postgresModel.config) {
|
||||||
|
podSummaryTitle.addItem(this.modelView.modelBuilder.loadingComponent().component(), { CSSStyles: { 'margin-right': '5px' } });
|
||||||
|
podSummaryTitle.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.loading,
|
||||||
|
CSSStyles: { ...cssStyles.title }
|
||||||
|
}).component());
|
||||||
|
this.podSummaryContainer.addItem(podSummaryTitle);
|
||||||
|
} else {
|
||||||
|
let components: azdata.Component[] = [];
|
||||||
|
let imageComponent = this.modelView.modelBuilder.image().withProps({
|
||||||
|
iconPath: IconPathHelper.success,
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
iconHeight: '20px',
|
||||||
|
iconWidth: '20px'
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
let podIssues = this.findPodIssues();
|
||||||
|
if (podIssues.length === 0) {
|
||||||
|
imageComponent.iconPath = IconPathHelper.success;
|
||||||
|
podSummaryTitle.addItem(imageComponent, { CSSStyles: { 'margin-right': '5px' } });
|
||||||
|
podSummaryTitle.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.available,
|
||||||
|
CSSStyles: { ...cssStyles.title, 'margin-left': '0px' }
|
||||||
|
}).component());
|
||||||
|
components.push(podSummaryTitle);
|
||||||
|
components.push(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.noPodIssuesDetected,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-top': '20px' }
|
||||||
|
}).component());
|
||||||
|
} else {
|
||||||
|
imageComponent.iconPath = IconPathHelper.fail;
|
||||||
|
podSummaryTitle.addItem(imageComponent, { CSSStyles: { 'margin-right': '5px' } });
|
||||||
|
podSummaryTitle.addItem(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.issuesDetected,
|
||||||
|
CSSStyles: { ...cssStyles.title }
|
||||||
|
}).component());
|
||||||
|
components.push(podSummaryTitle);
|
||||||
|
components.push(this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: loc.podIssuesDetected,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin-top': '20px 0px 10px 0px' }
|
||||||
|
}).component());
|
||||||
|
components.push(...podIssues.map(i => {
|
||||||
|
return this.modelView.modelBuilder.text().withProps({
|
||||||
|
value: i,
|
||||||
|
CSSStyles: { ...cssStyles.text, 'margin': '0px' }
|
||||||
|
}).component();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this.podSummaryContainer.addItems(components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshPodConditions(): void {
|
||||||
|
if (this._postgresModel.config) {
|
||||||
|
this.podConditionsTableIndexes = new Map();
|
||||||
|
this.podsData = [];
|
||||||
|
this.podDropDown.values = this.createPodList();
|
||||||
|
this.podConditionsTable.setFilter(this.podConditionsTableIndexes.get(this.coordinatorPodName!));
|
||||||
|
this.podConditionsLoading.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConfigUpdated() {
|
||||||
|
this.podSummaryContainer.clearItems();
|
||||||
|
this.refreshPodSummarySection();
|
||||||
|
this.refreshPodConditions();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import { ResourceType } from 'arc';
|
|||||||
import { PostgresModel } from '../../../models/postgresModel';
|
import { PostgresModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
export class PostgresSupportRequestPage extends DashboardPage {
|
export class PostgresSupportRequestPage extends DashboardPage {
|
||||||
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
constructor(protected modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
|
||||||
super(modelView);
|
super(modelView, dashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get title(): string {
|
protected get title(): string {
|
||||||
@@ -44,6 +44,11 @@ export class PostgresSupportRequestPage extends DashboardPage {
|
|||||||
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
|
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
|
||||||
}).component());
|
}).component());
|
||||||
|
|
||||||
|
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||||
|
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().withProperties<azdata.ButtonProperties>({
|
||||||
iconPath: IconPathHelper.support,
|
iconPath: IconPathHelper.support,
|
||||||
label: loc.newSupportRequest,
|
label: loc.newSupportRequest,
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as loc from '../../../localizedConstants';
|
||||||
|
import { IconPathHelper } from '../../../constants';
|
||||||
|
import { PostgresParametersPage } from './postgresParameters';
|
||||||
|
import { PostgresModel, EngineSettingsModel } from '../../../models/postgresModel';
|
||||||
|
|
||||||
|
export class PostgresWorkerNodeParametersPage extends PostgresParametersPage {
|
||||||
|
|
||||||
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, postgresModel: PostgresModel) {
|
||||||
|
super(modelView, dashboard, postgresModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get title(): string {
|
||||||
|
// TODO update to loc.workerNodeParameters
|
||||||
|
return loc.nodeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get id(): string {
|
||||||
|
// TODO update to 'postgres-worker-node-parameters'
|
||||||
|
return 'postgres-nodes-parameters';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get icon(): { dark: string; light: string; } {
|
||||||
|
return IconPathHelper.gearBlue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get description(): string {
|
||||||
|
// TODO update to loc.workerNodesParametersDescription
|
||||||
|
return loc.nodeParametersDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected get engineSettings(): EngineSettingsModel[] {
|
||||||
|
return this._postgresModel.workerNodesEngineSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async saveParameterEdits(engineSettings: string): Promise<void> {
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: engineSettings },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
this._postgresModel.controllerModel.controllerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resetAllParameters(): Promise<void> {
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: `''`, replaceEngineSettings: true },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
this._postgresModel.controllerModel.controllerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resetParameter(parameterName: string): Promise<void> {
|
||||||
|
await this._azdataApi.azdata.arc.postgres.server.edit(
|
||||||
|
this._postgresModel.info.name,
|
||||||
|
{ engineSettings: parameterName + '=' },
|
||||||
|
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
|
||||||
|
this._postgresModel.controllerModel.controllerContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import { InitializingComponent } from '../components/initializingComponent';
|
|||||||
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
||||||
import { getErrorMessage } from '../../common/utils';
|
import { getErrorMessage } from '../../common/utils';
|
||||||
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
|
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
|
||||||
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../../common/kubeUtils';
|
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts, KubeClusterContext } from '../../common/kubeUtils';
|
||||||
import { FilePicker } from '../components/filePicker';
|
import { FilePicker } from '../components/filePicker';
|
||||||
|
|
||||||
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
||||||
@@ -25,24 +25,34 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
protected modelBuilder!: azdata.ModelBuilder;
|
protected modelBuilder!: azdata.ModelBuilder;
|
||||||
protected dialog: azdata.window.Dialog;
|
protected dialog: azdata.window.Dialog;
|
||||||
|
|
||||||
protected urlInputBox!: azdata.InputBoxComponent;
|
protected namespaceInputBox!: azdata.InputBoxComponent;
|
||||||
protected kubeConfigInputBox!: FilePicker;
|
protected kubeConfigInputBox!: FilePicker;
|
||||||
protected clusterContextRadioGroup!: RadioOptionsGroup;
|
protected clusterContextRadioGroup!: RadioOptionsGroup;
|
||||||
protected nameInputBox!: azdata.InputBoxComponent;
|
protected nameInputBox!: azdata.InputBoxComponent;
|
||||||
protected usernameInputBox!: azdata.InputBoxComponent;
|
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||||
protected passwordInputBox!: azdata.InputBoxComponent;
|
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||||
|
protected urlInputBox!: azdata.InputBoxComponent;
|
||||||
|
|
||||||
|
private _kubeClusters: KubeClusterContext[] = [];
|
||||||
|
|
||||||
protected dispose(): void {
|
protected dispose(): void {
|
||||||
this._toDispose.forEach(disposable => disposable.dispose());
|
this._toDispose.forEach(disposable => disposable.dispose());
|
||||||
this._toDispose.length = 0; // clear the _toDispose array
|
this._toDispose.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
|
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
component: this.namespaceInputBox,
|
||||||
|
title: loc.namespace,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: this.urlInputBox,
|
component: this.urlInputBox,
|
||||||
title: loc.controllerUrl,
|
title: loc.controllerUrl,
|
||||||
required: true
|
layout: {
|
||||||
|
info: loc.controllerUrlDescription
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
component: this.kubeConfigInputBox.component(),
|
component: this.kubeConfigInputBox.component(),
|
||||||
title: loc.controllerKubeConfig,
|
title: loc.controllerKubeConfig,
|
||||||
@@ -54,14 +64,17 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
}, {
|
}, {
|
||||||
component: this.nameInputBox,
|
component: this.nameInputBox,
|
||||||
title: loc.controllerName,
|
title: loc.controllerName,
|
||||||
required: false
|
required: false,
|
||||||
|
layout: {
|
||||||
|
info: loc.controllerNameDescription
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
component: this.usernameInputBox,
|
component: this.usernameInputBox,
|
||||||
title: loc.username,
|
title: loc.controllerUsername,
|
||||||
required: true
|
required: true
|
||||||
}, {
|
}, {
|
||||||
component: this.passwordInputBox,
|
component: this.passwordInputBox,
|
||||||
title: loc.password,
|
title: loc.controllerPassword,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -71,11 +84,14 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
protected readonlyFields(): azdata.Component[] { return []; }
|
protected readonlyFields(): azdata.Component[] { return []; }
|
||||||
|
|
||||||
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||||
|
this.namespaceInputBox = this.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
|
value: controllerInfo?.namespace,
|
||||||
|
}).component();
|
||||||
this.urlInputBox = this.modelBuilder.inputBox()
|
this.urlInputBox = this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProps({
|
||||||
value: controllerInfo?.url,
|
value: controllerInfo?.endpoint,
|
||||||
// If we have a model then we're editing an existing connection so don't let them modify the URL
|
placeHolder: loc.controllerUrlPlaceholder,
|
||||||
readOnly: !!controllerInfo
|
|
||||||
}).component();
|
}).component();
|
||||||
this.kubeConfigInputBox = new FilePicker(
|
this.kubeConfigInputBox = new FilePicker(
|
||||||
this.modelBuilder,
|
this.modelBuilder,
|
||||||
@@ -83,22 +99,23 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
(disposable) => this._toDispose.push(disposable)
|
(disposable) => this._toDispose.push(disposable)
|
||||||
);
|
);
|
||||||
this.modelBuilder.inputBox()
|
this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProps({
|
||||||
value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath()
|
value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath()
|
||||||
}).component();
|
}).component();
|
||||||
this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable));
|
this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable));
|
||||||
this.loadRadioGroup(controllerInfo?.kubeClusterContext);
|
this.loadRadioGroup(controllerInfo?.kubeClusterContext);
|
||||||
|
this._toDispose.push(this.clusterContextRadioGroup.onRadioOptionChanged(newContext => this.updateNamespace(newContext)));
|
||||||
this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));
|
this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));
|
||||||
this.nameInputBox = this.modelBuilder.inputBox()
|
this.nameInputBox = this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProps({
|
||||||
value: controllerInfo?.name
|
value: controllerInfo?.name
|
||||||
}).component();
|
}).component();
|
||||||
this.usernameInputBox = this.modelBuilder.inputBox()
|
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProps({
|
||||||
value: controllerInfo?.username
|
value: controllerInfo?.username
|
||||||
}).component();
|
}).component();
|
||||||
this.passwordInputBox = this.modelBuilder.inputBox()
|
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
.withProps({
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
value: password
|
value: password
|
||||||
}).component();
|
}).component();
|
||||||
@@ -114,15 +131,22 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private loadRadioGroup(previousClusterContext?: string): void {
|
private loadRadioGroup(previousClusterContext?: string): void {
|
||||||
this.clusterContextRadioGroup.load(async () => {
|
this.clusterContextRadioGroup.load(() => {
|
||||||
const clusters = await getKubeConfigClusterContexts(this.kubeConfigInputBox.value!);
|
this._kubeClusters = getKubeConfigClusterContexts(this.kubeConfigInputBox.value!);
|
||||||
|
const currentClusterContext = getCurrentClusterContext(this._kubeClusters, previousClusterContext, false);
|
||||||
|
this.namespaceInputBox.value = currentClusterContext.namespace || this.namespaceInputBox.value;
|
||||||
return {
|
return {
|
||||||
values: clusters.map(c => c.name),
|
values: this._kubeClusters.map(c => c.name),
|
||||||
defaultValue: getCurrentClusterContext(clusters, previousClusterContext, false),
|
defaultValue: currentClusterContext.name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateNamespace(currentContextName: string | undefined): void {
|
||||||
|
const currentContext = this._kubeClusters.find(cluster => cluster.name === currentContextName);
|
||||||
|
this.namespaceInputBox.value = currentContext?.namespace;
|
||||||
|
}
|
||||||
|
|
||||||
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||||
this.id = controllerInfo?.id ?? uuid();
|
this.id = controllerInfo?.id ?? uuid();
|
||||||
this.resources = controllerInfo?.resources ?? [];
|
this.resources = controllerInfo?.resources ?? [];
|
||||||
@@ -168,7 +192,8 @@ abstract class ControllerDialogBase extends InitializingComponent {
|
|||||||
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
|
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
url: url,
|
endpoint: url || undefined,
|
||||||
|
namespace: this.namespaceInputBox.value!,
|
||||||
kubeConfigFilePath: this.kubeConfigInputBox.value!,
|
kubeConfigFilePath: this.kubeConfigInputBox.value!,
|
||||||
kubeClusterContext: this.clusterContextRadioGroup.value!,
|
kubeClusterContext: this.clusterContextRadioGroup.value!,
|
||||||
name: this.nameInputBox.value ?? '',
|
name: this.nameInputBox.value ?? '',
|
||||||
@@ -183,7 +208,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
|||||||
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
|
||||||
protected fieldToFocusOn() {
|
protected fieldToFocusOn() {
|
||||||
return this.urlInputBox;
|
return this.namespaceInputBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getComponents() {
|
protected getComponents() {
|
||||||
@@ -209,22 +234,25 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async validate(): Promise<boolean> {
|
public async validate(): Promise<boolean> {
|
||||||
if (!this.urlInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
if (!this.namespaceInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let url = this.urlInputBox.value;
|
let url = this.urlInputBox.value || '';
|
||||||
// Only support https connections
|
if (url) {
|
||||||
if (url.toLowerCase().startsWith('http://')) {
|
// Only support https connections
|
||||||
url = url.replace('http', 'https');
|
if (url.toLowerCase().startsWith('http://')) {
|
||||||
}
|
url = url.replace('http', 'https');
|
||||||
// Append https if they didn't type it in
|
}
|
||||||
if (!url.toLowerCase().startsWith('https://')) {
|
// Append https if they didn't type it in
|
||||||
url = `https://${url}`;
|
if (!url.toLowerCase().startsWith('https://')) {
|
||||||
}
|
url = `https://${url}`;
|
||||||
// Append default port if one wasn't specified
|
}
|
||||||
if (!/.*:\d*$/.test(url)) {
|
// Append default port if one wasn't specified
|
||||||
url = `${url}:30080`;
|
if (!/.*:\d*$/.test(url)) {
|
||||||
|
url = `${url}:30080`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
|
const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
|
||||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
try {
|
try {
|
||||||
@@ -234,7 +262,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
|
|||||||
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dialog.message = {
|
this.dialog.message = {
|
||||||
text: loc.connectToControllerFailed(this.urlInputBox.value, err),
|
text: loc.connectToControllerFailed(this.namespaceInputBox.value, err),
|
||||||
level: azdata.window.MessageLevel.Error
|
level: azdata.window.MessageLevel.Error
|
||||||
};
|
};
|
||||||
return false;
|
return false;
|
||||||
@@ -267,11 +295,16 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
|
|||||||
if (!this.passwordInputBox.value) {
|
if (!this.passwordInputBox.value) {
|
||||||
return false;
|
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;
|
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
try {
|
try {
|
||||||
await azdataApi.azdata.login(
|
await azdataApi.azdata.login(
|
||||||
this.urlInputBox.value!,
|
{
|
||||||
this.usernameInputBox.value!,
|
endpoint: controllerInfo.endpoint,
|
||||||
|
namespace: controllerInfo.namespace
|
||||||
|
},
|
||||||
|
controllerInfo.username,
|
||||||
this.passwordInputBox.value,
|
this.passwordInputBox.value,
|
||||||
{
|
{
|
||||||
'KUBECONFIG': this.kubeConfigInputBox.value!,
|
'KUBECONFIG': this.kubeConfigInputBox.value!,
|
||||||
@@ -293,8 +326,6 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const controllerInfo: ControllerInfo = this.getControllerInfo(this.urlInputBox.value!, false);
|
|
||||||
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
|
||||||
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ControllerModel } from '../../models/controllerModel';
|
|||||||
import { ControllerTreeNode } from './controllerTreeNode';
|
import { ControllerTreeNode } from './controllerTreeNode';
|
||||||
import { TreeNode } from './treeNode';
|
import { TreeNode } from './treeNode';
|
||||||
|
|
||||||
const mementoToken = 'arcDataControllers';
|
const mementoToken = 'arcDataControllers.v2';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TreeDataProvider for the Azure Arc view, which displays a list of registered
|
* The TreeDataProvider for the Azure Arc view, which displays a list of registered
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
vscode.window.showErrorMessage(loc.errorConnectingToController(err));
|
vscode.window.showErrorMessage(loc.errorConnectingToController(err));
|
||||||
try {
|
try {
|
||||||
await this.model.refresh(false, true);
|
await this.model.refresh(false);
|
||||||
this.updateChildren(this.model.registrations);
|
this.updateChildren(this.model.registrations);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!(err instanceof UserCancelledError)) {
|
if (!(err instanceof UserCancelledError)) {
|
||||||
|
|||||||
@@ -182,10 +182,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
||||||
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
|
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
|
||||||
|
|
||||||
"@microsoft/azdata-test@^1.4.0":
|
"@microsoft/azdata-test@^1.5.0":
|
||||||
version "1.4.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@microsoft/azdata-test/-/azdata-test-1.4.0.tgz#a809187ae8a065c518e3a3e2d350883e592853bc"
|
resolved "https://registry.yarnpkg.com/@microsoft/azdata-test/-/azdata-test-1.5.0.tgz#5ffa9ec6b704fea439c63d7dfa46dcfcf3236747"
|
||||||
integrity sha512-iscDA13/XRknRCNauP9OPsSg/ulTrMJOPFA0XMyNG1it3zY8mEJxxFJcNkWTnnEWpOUFvyksvoouzYUNy1fvrQ==
|
integrity sha512-kaDn5geXqrhcZgxCWXSrbXdUpJi5TFmi+sIPDfmhMYJa8uecn9C2rzxn5ZbxBN5cjjYOWF318dERfe+S0CWnlA==
|
||||||
dependencies:
|
dependencies:
|
||||||
http-proxy-agent "^2.1.0"
|
http-proxy-agent "^2.1.0"
|
||||||
https-proxy-agent "^2.2.4"
|
https-proxy-agent "^2.2.4"
|
||||||
|
|||||||
@@ -0,0 +1,486 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"name": "python3",
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"name": "python",
|
||||||
|
"version": "3.6.6",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"file_extension": ".py"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat_minor": 2,
|
||||||
|
"nbformat": 4,
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"\n",
|
||||||
|
"## Deploy Azure SQL Edge to an Azure VM via IoT hub\n",
|
||||||
|
"This notebook will walk you through the end to end setup of Azure SQL Edge.\n",
|
||||||
|
"1. Create an Azure Edge VM as a virtual IoT device, you can go the \"Default Settings\" cell and adjust the vm_size variable based on your needs. Available sizes and pricing information can be found [here](https://docs.microsoft.com/azure/virtual-machines/linux/sizes).\n",
|
||||||
|
"1. Create an Azure IoT hub, you can go to the \"Default Settings\" cell and adjust value of the following variables based on your needs: iot_hub_sku and iot_hub_units. Available SKUs and pricing information can be found [here](https://azure.microsoft.com/pricing/details/iot-hub/).\n",
|
||||||
|
"1. Add the device to the IoT hub\n",
|
||||||
|
"1. Deploy SQL Edge module to the device with optional package file\n",
|
||||||
|
"1. Enable connecting to the SQL Edge instance on the device\n",
|
||||||
|
"\n",
|
||||||
|
"### Dependencies\n",
|
||||||
|
"- Azure CLI. For more information, see [Azure CLI Installation](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest).\n",
|
||||||
|
"\n",
|
||||||
|
"<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "15b8cfc7-dd7f-4db8-9a3c-2151932fe7b5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Check dependencies"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "f78f4ff3-d4c9-4c3e-853f-4add05061eb0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"import sys,os,json,html,getpass,time,ntpath,uuid\n",
|
||||||
|
"\n",
|
||||||
|
"def run_command(command:str, displayCommand:str = \"\", returnObject:bool = False):\n",
|
||||||
|
" print(\"Executing: \" + (displayCommand if displayCommand != \"\" else command))\n",
|
||||||
|
" if returnObject:\n",
|
||||||
|
" output = os.popen(command).read()\n",
|
||||||
|
" print(f'Command successfully executed')\n",
|
||||||
|
" return json.loads(''.join(output))\n",
|
||||||
|
" else:\n",
|
||||||
|
" !{command}\n",
|
||||||
|
" if _exit_code != 0:\n",
|
||||||
|
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n')\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(f'Command successfully executed')\n",
|
||||||
|
"\n",
|
||||||
|
"run_command(command='az --version')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "70b9744f-eb59-44e8-9b35-db590ac4651d",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Install Azure IoT extension for Azure CLI"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "a7f15c68-1725-4caa-b4f7-ddc2b4934883"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"extensions = run_command('az extension list', returnObject=True)\r\n",
|
||||||
|
"extensions = [ext for ext in extensions if ext['name'] == 'azure-cli-iot-ext']\r\n",
|
||||||
|
"if len(extensions) > 0:\r\n",
|
||||||
|
" run_command('az extension remove --name azure-cli-iot-ext')\r\n",
|
||||||
|
"run_command('az extension add --name azure-iot')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "55bb2f96-6f7f-4aa0-9daf-d0f7f9d9243c",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Required information"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "b5dc5586-06e8-44d9-8bc3-2861d510efe5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"azure_subscription_id = os.environ[\"AZDATA_NB_VAR_ASDE_SUBSCRIPTIONID\"]\n",
|
||||||
|
"azure_resource_group = os.environ[\"AZDATA_NB_VAR_ASDE_RESOURCEGROUP\"]\n",
|
||||||
|
"azure_location = os.environ[\"AZDATA_NB_VAR_ASDE_AZURE_LOCATION\"]\n",
|
||||||
|
"sa_password = os.environ[\"AZDATA_NB_VAR_SA_PASSWORD\"]\n",
|
||||||
|
"vm_admin = os.environ[\"AZDATA_NB_VAR_ASDE_VM_ADMIN\"]\n",
|
||||||
|
"vm_password = os.environ[\"AZDATA_NB_VAR_ASDE_VM_PASSWORD\"]\n",
|
||||||
|
"package_path = os.environ[\"AZDATA_NB_VAR_ASDE_PACKAGE_PATH\"]\n",
|
||||||
|
"sql_port = os.environ[\"AZDATA_NB_VAR_ASDE_SQL_PORT\"]\n",
|
||||||
|
"new_rg_flag = os.environ[\"AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP\"]\n",
|
||||||
|
"new_rg_name = os.environ[\"AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP_NAME\"]\n",
|
||||||
|
"\n",
|
||||||
|
"if new_rg_flag == 'true':\n",
|
||||||
|
" azure_resource_group = new_rg_name\n",
|
||||||
|
"print(f'Subscription: {azure_subscription_id}')\n",
|
||||||
|
"print(f'Resource group: {azure_resource_group}')\n",
|
||||||
|
"print(f'Location: {azure_location}')\n",
|
||||||
|
"print(f'VM admin username: {vm_admin}')\n",
|
||||||
|
"print(f'VM admin password: ******')\n",
|
||||||
|
"print(f'SQL Server port: {sql_port}')\n",
|
||||||
|
"print(f'SQL Server sa password: ******')\n",
|
||||||
|
"print(f'Package path: {package_path}')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "dde9388b-f623-4d62-bb74-36a05f5d2ea3",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Default settings"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "2a5755eb-85a7-4237-8d87-04cdab13cf40"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"suffix = time.strftime(\"%y%m%d%H%M%S\", time.localtime())\n",
|
||||||
|
"iot_hub_name = f'hub{suffix}'\n",
|
||||||
|
"iot_hub_sku = 'S1'\n",
|
||||||
|
"iot_hub_units = 4\n",
|
||||||
|
"iot_device_id = f'vm{suffix}'\n",
|
||||||
|
"azure_storage_account = f'sa{suffix}'\n",
|
||||||
|
"storage_account_container = 'sqldatabasepackage'\n",
|
||||||
|
"sql_lcid = '1033'\n",
|
||||||
|
"sql_collation = 'SQL_Latin1_General_CP1_CI_AS'\n",
|
||||||
|
"vm_size = 'Standard_DS1_v2'"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "19ebeaf4-94c9-4d2b-bd9f-e3c6bf7f2dda",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Login to Azure"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "84f57c09-5772-4f7a-a270-4039b8d5b081"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"run_command('az login')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "f9e8ddee-aefa-4951-b767-b318d941d2cd",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Set active Azure subscription"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "59249fa6-f76c-4e5d-bee7-a9ebef6f873e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"if azure_subscription_id != \"\":\n",
|
||||||
|
" run_command(f'az account set --subscription {azure_subscription_id}')\n",
|
||||||
|
"else:\n",
|
||||||
|
" print('Using the default Azure subscription', {azure_subscription_id})\n",
|
||||||
|
"run_command(f'az account show')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "6e085676-2cc5-4af8-819c-fa210244e6c3",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Create resource group"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "67dacbaa-92f4-4d06-90bb-8974964852aa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"rg_exists = run_command(f'az group exists --name {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"\n",
|
||||||
|
"if rg_exists:\n",
|
||||||
|
" print(f'resource group \\\"{azure_resource_group}\\\" already exists.')\n",
|
||||||
|
"else:\n",
|
||||||
|
" run_command(f'az group create --location {azure_location} --name {azure_resource_group}')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "f29b439e-cf05-4c35-aa47-1482ccd653bf",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Create IoT hub"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "e37a04c3-515d-4cb7-99b2-f8bc6167510e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"hub_list = run_command(f'az iot hub list --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"hub_list = [hub for hub in hub_list if hub['name'] == iot_hub_name]\n",
|
||||||
|
"if len(hub_list) == 0:\n",
|
||||||
|
" run_command(f'az iot hub create --name {iot_hub_name} --resource-group {azure_resource_group} --location {azure_location} --sku {iot_hub_sku} --unit {iot_hub_units}')\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(f'IoT hub \\\"{iot_hub_name}\\\" already exists')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "f9f5e4ec-82a5-45df-a408-ddb0fb21847c",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Create storage account and storage account container, then upload the package"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "90ec2b26-0c4a-4aa4-b397-f16b09b454ea"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"storage_account_created = False\n",
|
||||||
|
"if package_path == \"\":\n",
|
||||||
|
" print(f'Package file not provided')\n",
|
||||||
|
" blob_sas = ''\n",
|
||||||
|
"else: \n",
|
||||||
|
" package_name = ntpath.basename(package_path)\n",
|
||||||
|
" storage_accounts = run_command(f'az storage account list --resource-group {azure_resource_group} --subscription {azure_subscription_id}', returnObject=True)\n",
|
||||||
|
" storage_accounts = [storage_account for storage_account in storage_accounts if storage_account['name'] == azure_storage_account]\n",
|
||||||
|
" if len(storage_accounts) == 0:\n",
|
||||||
|
" storage_account_created = True\n",
|
||||||
|
" run_command(f'az storage account create -n {azure_storage_account} -g {azure_resource_group} -l {azure_location} --sku Standard_LRS --kind Storage')\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(f'storage account \\\"{azure_storage_account}\\\" already exists.')\n",
|
||||||
|
"\n",
|
||||||
|
" storage_account_key = run_command(f'az storage account keys list --account-name {azure_storage_account} --resource-group {azure_resource_group}', returnObject=True)[0]['value']\n",
|
||||||
|
" container_exists = run_command(f'az storage container exists --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key --output json', returnObject=True)['exists']\n",
|
||||||
|
" if container_exists:\n",
|
||||||
|
" print(f'storage account container \\\"{storage_account_container}\\\" already exists.')\n",
|
||||||
|
" else:\n",
|
||||||
|
" run_command(f'az storage container create --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key')\n",
|
||||||
|
"\n",
|
||||||
|
" blob_exists = run_command(f'az storage blob exists --container-name {storage_account_container} --name \\\"{package_name}\\\" --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key', returnObject=True)['exists']\n",
|
||||||
|
" if blob_exists:\n",
|
||||||
|
" print(f'blob \\\"{package_name}\\\" already exists.')\n",
|
||||||
|
" else:\n",
|
||||||
|
" run_command(f'az storage blob upload --account-name {azure_storage_account} --container-name {storage_account_container} --name {package_name} --file \\\"{package_path}\\\" --account-key {storage_account_key} --auth-mode key')\n",
|
||||||
|
" now = time.localtime()\n",
|
||||||
|
" expiry = f'{(now.tm_year + 1)}-{now.tm_mon}-{now.tm_mday}'\n",
|
||||||
|
" blob_sas = run_command(f'az storage blob generate-sas --container-name {storage_account_container} --name \\\"{package_name}\\\" --account-name {azure_storage_account} --account-key {storage_account_key} --auth-mode key --full-uri --https-only --permissions r --expiry {expiry}', returnObject=True)"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "7ab2b3ec-0832-40b3-98c0-4aa87320e7ce",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Add the Edge device to the IoT hub"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "fbc5f4ac-dfe0-4543-ace1-49b796251910"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"device_list = run_command(f'az iot hub device-identity list --edge-enabled true --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"device_list = [device for device in device_list if device['deviceId'] == iot_device_id]\n",
|
||||||
|
"if len(device_list) == 0:\n",
|
||||||
|
" run_command(f'az iot hub device-identity create --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group} --edge-enabled true')\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(f'Edge device \\\"{iot_device_id}\\\" already exists.')\n",
|
||||||
|
"connection_string = run_command(f'az iot hub device-identity show-connection-string --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"connection_string = connection_string['connectionString']"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "c183c3e3-8699-4f29-993b-07bf848336e3",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Create an Edge enabled VM as an Edge device"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "41b10249-cd40-4053-b1b0-b02f562789f7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"iot_deploy_result = run_command((f\"az deployment group create \"\r\n",
|
||||||
|
"f\"--resource-group {azure_resource_group} \"\r\n",
|
||||||
|
"f\"--template-uri \\\"https://aka.ms/iotedge-vm-deploy\\\" \"\r\n",
|
||||||
|
"f\"--parameters vmSize={vm_size} \"\r\n",
|
||||||
|
"f\"--parameters dnsLabelPrefix={iot_device_id} \"\r\n",
|
||||||
|
"f\"--parameters adminUsername={vm_admin} \"\r\n",
|
||||||
|
"f\"--parameters deviceConnectionString={connection_string} \"\r\n",
|
||||||
|
"f\"--parameters authenticationType=sshPublicKey \"\r\n",
|
||||||
|
"f\"--parameters adminPasswordOrKey=\\\"{vm_password}\\\"\"), returnObject=True)\r\n",
|
||||||
|
"vm_resource = [resource for resource in iot_deploy_result['properties']['dependencies'] if resource['resourceType'] == 'Microsoft.Compute/virtualMachines']\r\n",
|
||||||
|
"if len(vm_resource) != 1:\r\n",
|
||||||
|
" sys.exit('Failed to deploy the IoT Edge VM')\r\n",
|
||||||
|
"vm_name = vm_resource[0]['resourceName']\r\n",
|
||||||
|
"nsg_name = vm_name.replace('vm-','nsg-')\r\n",
|
||||||
|
"ip_address = run_command(f'az vm show -d -g {azure_resource_group} -n {vm_name} --query publicIps', returnObject=True)\r\n",
|
||||||
|
"run_command(f'az network nsg rule create --name \\\"SQL\\\" --nsg-name {nsg_name} --priority 100 --resource-group {azure_resource_group} --access Allow --description \\\"Allow SQL\\\" --destination-address-prefixes \\\"*\\\" --destination-port-ranges {sql_port} --direction Inbound --source-address-prefixes Internet --protocol Tcp')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "c8590c65-b274-460d-9659-97e81d2fd3ea",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Deploy Azure SQL Edge to the device"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "ec46957f-0795-4c75-804d-f8a7ecb26382"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"manifest = '{\\\"modulesContent\\\":{\\\"$edgeAgent\\\":{\\\"properties.desired\\\":{\\\"modules\\\":{\\\"AzureSQLEdge\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azure-sql-edge\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"CapAdd\\\\\\\":[\\\\\\\"SYS_PTRACE\\\\\\\"],\\\\\\\"Binds\\\\\\\":[\\\\\\\"sqlvolume:/sqlvolume\\\\\\\"],\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"1433/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"<SQL_Port>\\\\\\\"}]},\\\\\\\"Mounts\\\\\\\":[{\\\\\\\"Type\\\\\\\":\\\\\\\"volume\\\\\\\",\\\\\\\"Source\\\\\\\":\\\\\\\"sqlvolume\\\\\\\",\\\\\\\"Target\\\\\\\":\\\\\\\"/var/opt/mssql\\\\\\\"}]},\\\\\\\"User\\\\\\\":\\\\\\\"0:0\\\\\\\",\\\\\\\"Env\\\\\\\":[\\\\\\\"MSSQL_AGENT_ENABLED=TRUE\\\\\\\",\\\\\\\"ClientTransportType=AMQP_TCP_Only\\\\\\\",\\\\\\\"PlanId=asde-developer-on-iot-edge\\\\\\\"]}\\\"},\\\"type\\\":\\\"docker\\\",\\\"version\\\":\\\"1.0\\\",\\\"env\\\":{\\\"ACCEPT_EULA\\\":{\\\"value\\\":\\\"Y\\\"},\\\"SA_PASSWORD\\\":{\\\"value\\\":\\\"<Default_SQL_SA_Password>\\\"},\\\"MSSQL_LCID\\\":{\\\"value\\\":\\\"<SQL_LCID>\\\"},\\\"MSSQL_COLLATION\\\":{\\\"value\\\":\\\"<SQL_Collation>\\\"}<PACKAGE_INFO>},\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}},\\\"runtime\\\":{\\\"settings\\\":{\\\"minDockerVersion\\\":\\\"v1.25\\\"},\\\"type\\\":\\\"docker\\\"},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"systemModules\\\":{\\\"edgeAgent\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-agent:1.0\\\",\\\"createOptions\\\":\\\"\\\"},\\\"type\\\":\\\"docker\\\"},\\\"edgeHub\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-hub:1.0\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"443/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"443\\\\\\\"}],\\\\\\\"5671/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"5671\\\\\\\"}],\\\\\\\"8883/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"8883\\\\\\\"}]}}}\\\"},\\\"type\\\":\\\"docker\\\",\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}}}},\\\"$edgeHub\\\":{\\\"properties.desired\\\":{\\\"routes\\\":{},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"storeAndForwardConfiguration\\\":{\\\"timeToLiveSecs\\\":7200}}},\\\"AzureSQLEdge\\\":{\\\"properties.desired\\\":{\\\"ASAJobInfo\\\":\\\"<Optional_ASA_Job_SAS_URL>\\\"}}}}'\n",
|
||||||
|
"package_info = '' if blob_sas == ''else ',\\\"MSSQL_PACKAGE\\\":{\\\"value\\\":\\\"'+blob_sas+'\\\"}'\n",
|
||||||
|
"manifest = manifest.replace('<PACKAGE_INFO>', package_info).replace('<Default_SQL_SA_Password>',sa_password).replace('<SQL_LCID>',sql_lcid).replace('<SQL_Port>',sql_port).replace('<SQL_Collation>',sql_collation)\n",
|
||||||
|
"file_name = f'{uuid.uuid4().hex}.json'\n",
|
||||||
|
"manifest_file = open(file_name, 'w')\n",
|
||||||
|
"manifest_file.write(manifest)\n",
|
||||||
|
"manifest_file.close()\n",
|
||||||
|
"run_command(f'az iot edge set-modules --device-id \\\"{iot_device_id}\\\" --hub-name \\\"{iot_hub_name}\\\" --content \\\"{file_name}\\\" --resource-group {azure_resource_group}')\n",
|
||||||
|
"os.remove(file_name)"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "81a86ff6-5a83-48be-8be7-654d152eea89",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### **Connect to Azure SQL Edge instance in Azure Data Studio**\n",
|
||||||
|
"Click the link below to connect to the Azure SQL Edge instance, it might take a couple minutes for the service to start."
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "3bdfa537-a749-45c4-b219-57d296c22739"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"from IPython.display import *\n",
|
||||||
|
"connectionParameter = '{\"serverName\":\"' + f'{ip_address},{sql_port}' + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\": \"sa\",\"password\":' + json.dumps(sa_password) + '}'\n",
|
||||||
|
"display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to the Azure SQL Edge instance</font></a><br/>'))\n",
|
||||||
|
"display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The Azure SQL Edge instance password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "8bc29cce-96a7-4a78-89af-5c73a6431c24",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"if storage_account_created:\r\n",
|
||||||
|
" delete_storage_account_command = \"run_command(f'az storage account delete -n {azure_storage_account} -g {azure_resource_group} --yes')\"\r\n",
|
||||||
|
" display(HTML('<span style=\"color:red\"><font size=\"2\">NOTE: A storage account was created to host the package file, you can delete it after the database is created and populated successfully. To delete the storage account, copy the following code to a new code cell and run the cell.</font></span>'))\r\n",
|
||||||
|
" display(HTML('<span><font size=\"2\">'+delete_storage_account_command+'</font></span>'))"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "8b74ac43-a871-4d28-832d-e6da586f6d3a",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"name": "python3",
|
"name": "python3",
|
||||||
"display_name": "Python 3"
|
"display_name": "Python 3",
|
||||||
|
"language": "python"
|
||||||
},
|
},
|
||||||
"language_info": {
|
"language_info": {
|
||||||
"name": "python",
|
"name": "python",
|
||||||
@@ -162,21 +163,15 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"suffix = time.strftime(\"%y%m%d%H%M%S\", time.localtime())\n",
|
"suffix = time.strftime(\"%y%m%d%H%M%S\", time.localtime())\n",
|
||||||
"network_security_group = f'nsg{suffix}'\n",
|
|
||||||
"public_ip_address_name = f'ip{suffix}'\n",
|
|
||||||
"iot_hub_name = f'hub{suffix}'\n",
|
"iot_hub_name = f'hub{suffix}'\n",
|
||||||
"iot_hub_sku = 'S1'\n",
|
"iot_hub_sku = 'S1'\n",
|
||||||
"iot_hub_units = 4\n",
|
"iot_hub_units = 4\n",
|
||||||
"iot_device_id = f'vm{suffix}'\n",
|
"iot_device_id = f'vm{suffix}'\n",
|
||||||
"vm_size = 'Standard_DS3_v2'\n",
|
|
||||||
"vnet_name = f'net{suffix}'\n",
|
|
||||||
"subnet_name = f'subnet{suffix}'\n",
|
|
||||||
"subnet_address_prefix = '10.0.0.0/24'\n",
|
|
||||||
"vnet_address_prefix = '10.0.0.0/16'\n",
|
|
||||||
"azure_storage_account = f'sa{suffix}'\n",
|
"azure_storage_account = f'sa{suffix}'\n",
|
||||||
"storage_account_container = 'sqldatabasepackage'\n",
|
"storage_account_container = 'sqldatabasepackage'\n",
|
||||||
"sql_lcid = '1033'\n",
|
"sql_lcid = '1033'\n",
|
||||||
"sql_collation = 'SQL_Latin1_General_CP1_CI_AS'"
|
"sql_collation = 'SQL_Latin1_General_CP1_CI_AS'\n",
|
||||||
|
"vm_size = 'Standard_DS1_v2'"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "19ebeaf4-94c9-4d2b-bd9f-e3c6bf7f2dda",
|
"azdata_cell_guid": "19ebeaf4-94c9-4d2b-bd9f-e3c6bf7f2dda",
|
||||||
@@ -265,6 +260,34 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"execution_count": null
|
"execution_count": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"source": [
|
||||||
|
"### Create IoT hub"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "e37a04c3-515d-4cb7-99b2-f8bc6167510e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"hub_list = run_command(f'az iot hub list --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"hub_list = [hub for hub in hub_list if hub['name'] == iot_hub_name]\n",
|
||||||
|
"if len(hub_list) == 0:\n",
|
||||||
|
" run_command(f'az iot hub create --name {iot_hub_name} --resource-group {azure_resource_group} --location {azure_location} --sku {iot_hub_sku} --unit {iot_hub_units}')\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(f'IoT hub \\\"{iot_hub_name}\\\" already exists')"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "f9f5e4ec-82a5-45df-a408-ddb0fb21847c",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
@@ -319,25 +342,26 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"source": [
|
"source": [
|
||||||
"### Create network security group"
|
"### Add the Edge device to the IoT hub"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "b308771b-138a-40ce-a9d3-1d15094d537b"
|
"azdata_cell_guid": "fbc5f4ac-dfe0-4543-ace1-49b796251910"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"nsg_list = run_command(f'az network nsg list --resource-group {azure_resource_group}', returnObject=True)\n",
|
"device_list = run_command(f'az iot hub device-identity list --edge-enabled true --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
"nsg_list = [nsg for nsg in nsg_list if nsg['name'] == network_security_group]\n",
|
"device_list = [device for device in device_list if device['deviceId'] == iot_device_id]\n",
|
||||||
"if len(nsg_list) == 0:\n",
|
"if len(device_list) == 0:\n",
|
||||||
" run_command(f'az network nsg create --name {network_security_group} --resource-group {azure_resource_group} --location {azure_location}')\n",
|
" run_command(f'az iot hub device-identity create --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group} --edge-enabled true')\n",
|
||||||
" run_command(f'az network nsg rule create --name \\\"SQL\\\" --nsg-name {network_security_group} --priority 100 --resource-group {azure_resource_group} --access Allow --description \\\"Allow SQL\\\" --destination-address-prefixes \\\"*\\\" --destination-port-ranges {sql_port} --direction Inbound --source-address-prefixes Internet --protocol Tcp')\n",
|
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" print(f'Network security group \\\"{network_security_group}\\\" already exists.')"
|
" print(f'Edge device \\\"{iot_device_id}\\\" already exists.')\n",
|
||||||
|
"connection_string = run_command(f'az iot hub device-identity show-connection-string --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
||||||
|
"connection_string = connection_string['connectionString']"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "99cbb95c-b109-4b2e-909b-ff71a62754fb",
|
"azdata_cell_guid": "c183c3e3-8699-4f29-993b-07bf848336e3",
|
||||||
"tags": [
|
"tags": [
|
||||||
"hide_input"
|
"hide_input"
|
||||||
]
|
]
|
||||||
@@ -357,103 +381,25 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"vm_list = run_command(f'az vm list --resource-group {azure_resource_group}', returnObject=True)\n",
|
"iot_deploy_result = run_command((f\"az deployment group create \"\r\n",
|
||||||
"vm_list = [vm for vm in vm_list if vm['name'] == iot_device_id]\n",
|
"f\"--resource-group {azure_resource_group} \"\r\n",
|
||||||
"if len(vm_list) == 0:\n",
|
"f\"--template-uri \\\"https://aka.ms/iotedge-vm-deploy\\\" \"\r\n",
|
||||||
" vm_image = run_command(f'az vm image list --all --location {azure_location} --offer iot_edge_vm_ubuntu --publisher microsoft_iot_edge --sku ubuntu_1604_edgeruntimeonly', returnObject=True)\n",
|
"f\"--parameters vmSize={vm_size} \"\r\n",
|
||||||
" image_urn = vm_image[0]['urn']\n",
|
"f\"--parameters dnsLabelPrefix={iot_device_id} \"\r\n",
|
||||||
" run_command(f'az vm image terms accept --urn {image_urn}')\n",
|
"f\"--parameters adminUsername={vm_admin} \"\r\n",
|
||||||
" vm_password_placeholder = '<admin_password>'\n",
|
"f\"--parameters deviceConnectionString={connection_string} \"\r\n",
|
||||||
" create_vm_command_template = f'az vm create --name {iot_device_id} --resource-group {azure_resource_group} --admin-username {vm_admin} --admin-password {vm_password_placeholder} --authentication-type password --image {image_urn} --location {azure_location} --nsg {network_security_group} --public-ip-address \\\"{public_ip_address_name}\\\" --public-ip-address-allocation static --public-ip-sku Standard --size {vm_size} --subnet {subnet_name} --subnet-address-prefix \\\"{subnet_address_prefix}\\\" --vnet-name {vnet_name} --vnet-address-prefix \\\"{vnet_address_prefix}\\\"'\n",
|
"f\"--parameters authenticationType=password \"\r\n",
|
||||||
" run_command(create_vm_command_template.replace(vm_password_placeholder, vm_password), displayCommand=create_vm_command_template.replace(vm_password_placeholder, '******'))\n",
|
"f\"--parameters adminPasswordOrKey=\\\"{vm_password}\\\"\"), returnObject=True)\r\n",
|
||||||
"else:\n",
|
"vm_resource = [resource for resource in iot_deploy_result['properties']['dependencies'] if resource['resourceType'] == 'Microsoft.Compute/virtualMachines']\r\n",
|
||||||
" print(f'VM \\\"{iot_device_id}\\\" already exists, skipping the vm creation.')\n",
|
"if len(vm_resource) != 1:\r\n",
|
||||||
"ip_address = run_command(f'az vm show -d -g {azure_resource_group} -n {iot_device_id} --query publicIps', returnObject=True)"
|
" sys.exit('Failed to deploy the IoT Edge VM')\r\n",
|
||||||
|
"vm_name = vm_resource[0]['resourceName']\r\n",
|
||||||
|
"nsg_name = vm_name.replace('vm-','nsg-')\r\n",
|
||||||
|
"ip_address = run_command(f'az vm show -d -g {azure_resource_group} -n {vm_name} --query publicIps', returnObject=True)\r\n",
|
||||||
|
"run_command(f'az network nsg rule create --name \\\"SQL\\\" --nsg-name {nsg_name} --priority 100 --resource-group {azure_resource_group} --access Allow --description \\\"Allow SQL\\\" --destination-address-prefixes \\\"*\\\" --destination-port-ranges {sql_port} --direction Inbound --source-address-prefixes Internet --protocol Tcp')"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "157fc38f-cf2a-40c6-9c9e-88f45cc5c62f",
|
"azdata_cell_guid": "c8590c65-b274-460d-9659-97e81d2fd3ea",
|
||||||
"tags": [
|
|
||||||
"hide_input"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"source": [
|
|
||||||
"### Create IoT hub"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "e37a04c3-515d-4cb7-99b2-f8bc6167510e"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"hub_list = run_command(f'az iot hub list --resource-group {azure_resource_group}', returnObject=True)\n",
|
|
||||||
"hub_list = [hub for hub in hub_list if hub['name'] == iot_hub_name]\n",
|
|
||||||
"if len(hub_list) == 0:\n",
|
|
||||||
" run_command(f'az iot hub create --name {iot_hub_name} --resource-group {azure_resource_group} --location {azure_location} --sku {iot_hub_sku} --unit {iot_hub_units}')\n",
|
|
||||||
"else:\n",
|
|
||||||
" print(f'IoT hub \\\"{iot_hub_name}\\\" already exists')"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "f9f5e4ec-82a5-45df-a408-ddb0fb21847c",
|
|
||||||
"tags": [
|
|
||||||
"hide_input"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"source": [
|
|
||||||
"### Add the Edge device to the IoT hub"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "fbc5f4ac-dfe0-4543-ace1-49b796251910"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"device_list = run_command(f'az iot hub device-identity list --edge-enabled true --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
|
||||||
"device_list = [device for device in device_list if device['deviceId'] == iot_device_id]\n",
|
|
||||||
"if len(device_list) == 0:\n",
|
|
||||||
" run_command(f'az iot hub device-identity create --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group} --edge-enabled true')\n",
|
|
||||||
"else:\n",
|
|
||||||
" print(f'Edge device \\\"{iot_device_id}\\\" already exists.')"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "c183c3e3-8699-4f29-993b-07bf848336e3",
|
|
||||||
"tags": [
|
|
||||||
"hide_input"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"execution_count": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"source": [
|
|
||||||
"### Configure Edge on the device"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "069db017-9169-499a-839b-9cd73ea7d01e"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"source": [
|
|
||||||
"connection_string = run_command(f'az iot hub device-identity show-connection-string --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
|
|
||||||
"connection_string = connection_string['connectionString']\n",
|
|
||||||
"script = f'/etc/iotedge/configedge.sh \\'{connection_string}\\''\n",
|
|
||||||
"run_command(f'az vm run-command invoke -g {azure_resource_group} -n {iot_device_id} --command-id RunShellScript --script \\\"{script}\\\"')"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"azdata_cell_guid": "9ec1e31a-79aa-49f4-a0e5-16f8d7c2dd21",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"hide_input"
|
"hide_input"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "asde-deployment",
|
"name": "asde-deployment",
|
||||||
"displayName": "%extension-displayName%",
|
"displayName": "%extension-displayName%",
|
||||||
"description": "%extension-description%",
|
"description": "%extension-description%",
|
||||||
"version": "0.4.1",
|
"version": "0.4.2",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
|
"aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "*",
|
"vscode": "*",
|
||||||
"azdata": "*"
|
"azdata": ">=1.25.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -43,9 +43,13 @@
|
|||||||
"displayName": "%sql-edge-remote-display-name%"
|
"displayName": "%sql-edge-remote-display-name%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azure-create-new",
|
"name": "azure-create-new-password-auth",
|
||||||
"displayName": "%sql-edge-azure-display-name%"
|
"displayName": "%sql-edge-azure-display-name%"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "azure-create-new-sshkey-auth",
|
||||||
|
"displayName": "%sql-edge-azure-sshkey-display-name%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "azure-single-device",
|
"name": "azure-single-device",
|
||||||
"displayName": "%sql-edge-azure-single-device-display-name%"
|
"displayName": "%sql-edge-azure-single-device-display-name%"
|
||||||
@@ -59,6 +63,7 @@
|
|||||||
],
|
],
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
|
"name": "sql-edge_local",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"notebook": "./notebooks/edge/deploy-sql-edge-local.ipynb",
|
"notebook": "./notebooks/edge/deploy-sql-edge-local.ipynb",
|
||||||
"title": "%sql-edge-local-title%",
|
"title": "%sql-edge-local-title%",
|
||||||
@@ -152,6 +157,7 @@
|
|||||||
"when": "type=local"
|
"when": "type=local"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "sql-edge_remote",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"notebook": "./notebooks/edge/deploy-sql-edge-remote.ipynb",
|
"notebook": "./notebooks/edge/deploy-sql-edge-remote.ipynb",
|
||||||
"title": "%sql-edge-remote-title%",
|
"title": "%sql-edge-remote-title%",
|
||||||
@@ -269,6 +275,7 @@
|
|||||||
"when": "type=remote"
|
"when": "type=remote"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "sql-edge-azure-create-new-password-auth",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"notebook": "./notebooks/edge/deploy-sql-edge-azure.ipynb",
|
"notebook": "./notebooks/edge/deploy-sql-edge-azure.ipynb",
|
||||||
"title": "%sql-edge-azure-title%",
|
"title": "%sql-edge-azure-title%",
|
||||||
@@ -396,9 +403,132 @@
|
|||||||
"version": "2.13.0"
|
"version": "2.13.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "type=azure-create-new"
|
"when": "type=azure-create-new-password-auth"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "sql-edge-azure-create-new-sshkey-auth",
|
||||||
|
"dialog": {
|
||||||
|
"notebook": "./notebooks/edge/deploy-sql-edge-azure-sshkey.ipynb",
|
||||||
|
"title": "%sql-edge-azure-title%",
|
||||||
|
"name": "sql-edge-azure-dialog",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"title": "",
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"title": "%azure-info-section-title%",
|
||||||
|
"collapsible": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"subscriptionVariableName": "AZDATA_NB_VAR_ASDE_SUBSCRIPTIONID",
|
||||||
|
"resourceGroupVariableName": "AZDATA_NB_VAR_ASDE_RESOURCEGROUP",
|
||||||
|
"type": "azure_account",
|
||||||
|
"required": true,
|
||||||
|
"allowNewResourceGroup": true,
|
||||||
|
"newResourceGroupFlagVariableName": "AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP",
|
||||||
|
"newResourceGroupNameVariableName": "AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP_NAME"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "azure_locations",
|
||||||
|
"label": "%azure_location%",
|
||||||
|
"defaultValue": "westus",
|
||||||
|
"required": true,
|
||||||
|
"locationVariableName": "AZDATA_NB_VAR_ASDE_AZURE_LOCATION",
|
||||||
|
"locations": [
|
||||||
|
"australiaeast",
|
||||||
|
"australiasoutheast",
|
||||||
|
"brazilsouth",
|
||||||
|
"canadacentral",
|
||||||
|
"canadaeast",
|
||||||
|
"centralindia",
|
||||||
|
"centralus",
|
||||||
|
"eastasia",
|
||||||
|
"eastus",
|
||||||
|
"eastus2",
|
||||||
|
"francecentral",
|
||||||
|
"japaneast",
|
||||||
|
"japanwest",
|
||||||
|
"koreacentral",
|
||||||
|
"koreasouth",
|
||||||
|
"northcentralus",
|
||||||
|
"northeurope",
|
||||||
|
"southcentralus",
|
||||||
|
"southindia",
|
||||||
|
"southeastasia",
|
||||||
|
"uksouth",
|
||||||
|
"ukwest",
|
||||||
|
"westcentralus",
|
||||||
|
"westeurope",
|
||||||
|
"westus",
|
||||||
|
"westus2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%vm_admin%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ASDE_VM_ADMIN",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%vm_ssh_public_key%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ASDE_VM_PASSWORD",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "%sqlserver-info-section-title%",
|
||||||
|
"collapsible": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%docker-sql-password-field%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SA_PASSWORD",
|
||||||
|
"type": "sql_password",
|
||||||
|
"userName": "sa",
|
||||||
|
"confirmationRequired": true,
|
||||||
|
"confirmationLabel": "%docker-confirm-sql-password-field%",
|
||||||
|
"defaultValue": "",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%docker-sql-port-field%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ASDE_SQL_PORT",
|
||||||
|
"type": "number",
|
||||||
|
"defaultValue": 31433,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%package_path%",
|
||||||
|
"description": "%package_path_description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ASDE_PACKAGE_PATH",
|
||||||
|
"type": "file_picker",
|
||||||
|
"required": false,
|
||||||
|
"filter": {
|
||||||
|
"displayName": "%package-files%",
|
||||||
|
"fileTypes": [
|
||||||
|
"zip",
|
||||||
|
"bacpac",
|
||||||
|
"dacpac"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"requiredTools": [
|
||||||
|
{
|
||||||
|
"name": "azure-cli",
|
||||||
|
"version": "2.13.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"when": "type=azure-create-new-sshkey-auth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sql-edge_azure-single-device",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"notebook": "./notebooks/edge/deploy-sql-edge-single-device.ipynb",
|
"notebook": "./notebooks/edge/deploy-sql-edge-single-device.ipynb",
|
||||||
"title": "%sql-edge-azure-single-device-title%",
|
"title": "%sql-edge-azure-single-device-title%",
|
||||||
@@ -489,6 +619,7 @@
|
|||||||
"when": "type=azure-single-device"
|
"when": "type=azure-single-device"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "sql-edge_azure-multi-device",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"notebook": "./notebooks/edge/deploy-sql-edge-multi-device.ipynb",
|
"notebook": "./notebooks/edge/deploy-sql-edge-multi-device.ipynb",
|
||||||
"title": "%sql-edge-azure-multi-device-title%",
|
"title": "%sql-edge-azure-multi-device-title%",
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
"edge-remote-target-field": "Name or IP address",
|
"edge-remote-target-field": "Name or IP address",
|
||||||
"edge-remote-username-field": "Username",
|
"edge-remote-username-field": "Username",
|
||||||
"edge-remote-password-field": "Password",
|
"edge-remote-password-field": "Password",
|
||||||
"sql-edge-azure-display-name": "New Azure IoT Hub and VM",
|
"sql-edge-azure-display-name": "New Azure IoT Hub and VM (password authentication)",
|
||||||
|
"sql-edge-azure-sshkey-display-name": "New Azure IoT Hub and VM (ssh public key authentication)",
|
||||||
"sql-edge-azure-title": "Deploy Azure SQL Edge to a new Azure VM via IoT hub",
|
"sql-edge-azure-title": "Deploy Azure SQL Edge to a new Azure VM via IoT hub",
|
||||||
"azure_subscription_id": "Subscription id",
|
"azure_subscription_id": "Subscription id",
|
||||||
"azure_resource_group": "Resource group",
|
"azure_resource_group": "Resource group",
|
||||||
@@ -48,5 +49,6 @@
|
|||||||
"sql-edge-azure-multi-device-display-name": "Multiple devices of an Azure IoT Hub",
|
"sql-edge-azure-multi-device-display-name": "Multiple devices of an Azure IoT Hub",
|
||||||
"sql-edge-azure-multi-device-title": "Deploy Azure SQL Edge to multiple Azure IoT devices",
|
"sql-edge-azure-multi-device-title": "Deploy Azure SQL Edge to multiple Azure IoT devices",
|
||||||
"device-target-condition": "Target condition",
|
"device-target-condition": "Target condition",
|
||||||
"device-target-condition-learn-more": "Learn more about target condition"
|
"device-target-condition-learn-more": "Learn more about target condition",
|
||||||
|
"vm_ssh_public_key": "SSH public key"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "azdata",
|
"name": "azdata",
|
||||||
"displayName": "%azdata.displayName%",
|
"displayName": "%azdata.displayName%",
|
||||||
"description": "%azdata.description%",
|
"description": "%azdata.description%",
|
||||||
"version": "0.5.1",
|
"version": "0.6.2",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
@@ -107,7 +107,12 @@
|
|||||||
"when": "azdata.found"
|
"when": "azdata.found"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"resourceDeploymentOptionsSources": [
|
||||||
|
{
|
||||||
|
"id": "arc.controller.config.profiles"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
|||||||
@@ -5,13 +5,26 @@
|
|||||||
|
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { IAzdataTool, isEulaAccepted, promptForEula } from './azdata';
|
import { IAzdataTool, isEulaAccepted, MIN_AZDATA_VERSION, promptForEula } from './azdata';
|
||||||
import Logger from './common/logger';
|
import Logger from './common/logger';
|
||||||
import { NoAzdataError } from './common/utils';
|
import { NoAzdataError } from './common/utils';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
import { AzdataToolService } from './services/azdataToolService';
|
import { AzdataToolService } from './services/azdataToolService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
async function validateAzdata(azdata: IAzdataTool | undefined, eulaAccepted: boolean): Promise<void> {
|
||||||
|
throwIfNoAzdataOrEulaNotAccepted(azdata, eulaAccepted);
|
||||||
|
await throwIfRequiredVersionMissing(azdata);
|
||||||
|
}
|
||||||
|
|
||||||
export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
|
export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
|
||||||
throwIfNoAzdata(azdata);
|
throwIfNoAzdata(azdata);
|
||||||
if (!eulaAccepted) {
|
if (!eulaAccepted) {
|
||||||
@@ -20,6 +33,13 @@ export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
export function throwIfNoAzdata(localAzdata: IAzdataTool | undefined): asserts localAzdata {
|
||||||
if (!localAzdata) {
|
if (!localAzdata) {
|
||||||
Logger.log(loc.noAzdata);
|
Logger.log(loc.noAzdata);
|
||||||
@@ -55,47 +75,47 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
profileName?: string,
|
profileName?: string,
|
||||||
storageClass?: string,
|
storageClass?: string,
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||||
session?: azdataExt.AzdataSession) => {
|
azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
endpoint: {
|
endpoint: {
|
||||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.dc.endpoint.list(additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.dc.endpoint.list(additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.dc.config.list(additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.dc.config.list(additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.dc.config.show(additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.dc.config.show(additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.postgres.server.delete(name, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.postgres.server.delete(name, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.postgres.server.list(additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.postgres.server.list(additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.postgres.server.show(name, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.postgres.server.show(name, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
edit: async (
|
edit: async (
|
||||||
name: string,
|
name: string,
|
||||||
@@ -112,31 +132,30 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
replaceEngineSettings?: boolean;
|
replaceEngineSettings?: boolean;
|
||||||
workers?: number;
|
workers?: number;
|
||||||
},
|
},
|
||||||
engineVersion?: string,
|
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||||
session?: azdataExt.AzdataSession) => {
|
azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, engineVersion, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.postgres.server.edit(name, args, additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sql: {
|
sql: {
|
||||||
mi: {
|
mi: {
|
||||||
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.sql.mi.delete(name, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.sql.mi.delete(name, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.sql.mi.list(additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.sql.mi.list(additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
|
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.sql.mi.show(name, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.sql.mi.show(name, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
edit: async (
|
edit: async (
|
||||||
name: string,
|
name: string,
|
||||||
@@ -148,11 +167,11 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
noWait?: boolean;
|
noWait?: boolean;
|
||||||
},
|
},
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||||
session?: azdataExt.AzdataSession
|
azdataContext?: string
|
||||||
) => {
|
) => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.arc.sql.mi.edit(name, args, additionalEnvVars, session);
|
return azdataToolService.localAzdata!.arc.sql.mi.edit(name, args, additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,13 +181,9 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
|
|||||||
throwIfNoAzdata(azdataToolService.localAzdata);
|
throwIfNoAzdata(azdataToolService.localAzdata);
|
||||||
return azdataToolService.localAzdata.getPath();
|
return azdataToolService.localAzdata.getPath();
|
||||||
},
|
},
|
||||||
login: async (endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => {
|
login: async (endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
|
||||||
return azdataToolService.localAzdata.login(endpoint, username, password, additionalEnvVars);
|
return azdataToolService.localAzdata!.login(endpointOrNamespace, username, password, additionalEnvVars, azdataContext);
|
||||||
},
|
|
||||||
acquireSession: async (endpoint: string, username: string, password: string, additionEnvVars?: azdataExt.AdditionalEnvVars) => {
|
|
||||||
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
|
|
||||||
return azdataToolService.localAzdata?.acquireSession(endpoint, username, password, additionEnvVars);
|
|
||||||
},
|
},
|
||||||
getSemVersion: async () => {
|
getSemVersion: async () => {
|
||||||
await localAzdataDiscovered;
|
await localAzdataDiscovered;
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataRele
|
|||||||
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
||||||
import { HttpClient } from './common/httpClient';
|
import { HttpClient } from './common/httpClient';
|
||||||
import Logger from './common/logger';
|
import Logger from './common/logger';
|
||||||
import { Deferred } from './common/promise';
|
|
||||||
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
|
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
|
||||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum required azdata CLI version for this extension to function properly
|
||||||
|
*/
|
||||||
|
export const MIN_AZDATA_VERSION = new SemVer('20.3.2');
|
||||||
|
|
||||||
export const enum AzdataDeployOption {
|
export const enum AzdataDeployOption {
|
||||||
dontPrompt = 'dontPrompt',
|
dontPrompt = 'dontPrompt',
|
||||||
prompt = 'prompt'
|
prompt = 'prompt'
|
||||||
@@ -32,20 +36,7 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
|
|||||||
* @param args The args to pass to azdata
|
* @param args The args to pass to azdata
|
||||||
* @param parseResult A function used to parse out the raw result into the desired shape
|
* @param parseResult A function used to parse out the raw result into the desired shape
|
||||||
*/
|
*/
|
||||||
executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>>
|
executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>>
|
||||||
}
|
|
||||||
|
|
||||||
class AzdataSession implements azdataExt.AzdataSession {
|
|
||||||
|
|
||||||
private _session = new Deferred<void>();
|
|
||||||
|
|
||||||
public sessionEnded(): Promise<void> {
|
|
||||||
return this._session.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
this._session.resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,9 +45,6 @@ class AzdataSession implements azdataExt.AzdataSession {
|
|||||||
export class AzdataTool implements azdataExt.IAzdataApi {
|
export class AzdataTool implements azdataExt.IAzdataApi {
|
||||||
|
|
||||||
private _semVersion: SemVer;
|
private _semVersion: SemVer;
|
||||||
private _currentSession: azdataExt.AzdataSession | undefined = undefined;
|
|
||||||
private _currentlyExecutingCommands: Deferred<void>[] = [];
|
|
||||||
private _queuedCommands: { deferred: Deferred<void>, session?: azdataExt.AzdataSession }[] = [];
|
|
||||||
|
|
||||||
constructor(private _path: string, version: string) {
|
constructor(private _path: string, version: string) {
|
||||||
this._semVersion = new SemVer(version);
|
this._semVersion = new SemVer(version);
|
||||||
@@ -90,7 +78,7 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
profileName?: string,
|
profileName?: string,
|
||||||
storageClass?: string,
|
storageClass?: string,
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||||
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||||
const args = ['arc', 'dc', 'create',
|
const args = ['arc', 'dc', 'create',
|
||||||
'--namespace', namespace,
|
'--namespace', namespace,
|
||||||
'--name', name,
|
'--name', name,
|
||||||
@@ -104,32 +92,32 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
if (storageClass) {
|
if (storageClass) {
|
||||||
args.push('--storage-class', storageClass);
|
args.push('--storage-class', storageClass);
|
||||||
}
|
}
|
||||||
return this.executeCommand<void>(args, additionalEnvVars, session);
|
return this.executeCommand<void>(args, additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
endpoint: {
|
endpoint: {
|
||||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
|
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
|
||||||
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, session);
|
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
|
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
|
||||||
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, session);
|
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
|
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
|
||||||
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, session);
|
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, azdataContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||||
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, session);
|
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
|
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
|
||||||
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, session);
|
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
|
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, session);
|
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
edit: (
|
edit: (
|
||||||
name: string,
|
name: string,
|
||||||
@@ -146,9 +134,8 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
replaceEngineSettings?: boolean,
|
replaceEngineSettings?: boolean,
|
||||||
workers?: number
|
workers?: number
|
||||||
},
|
},
|
||||||
engineVersion?: string,
|
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||||
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||||
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
|
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
|
||||||
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
||||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||||
@@ -161,21 +148,20 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
if (args.port) { argsArray.push('--port', args.port.toString()); }
|
if (args.port) { argsArray.push('--port', args.port.toString()); }
|
||||||
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
|
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
|
||||||
if (args.workers) { argsArray.push('--workers', args.workers.toString()); }
|
if (args.workers) { argsArray.push('--workers', args.workers.toString()); }
|
||||||
if (engineVersion) { argsArray.push('--engine-version', engineVersion); }
|
return this.executeCommand<void>(argsArray, additionalEnvVars, azdataContext);
|
||||||
return this.executeCommand<void>(argsArray, additionalEnvVars, session);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sql: {
|
sql: {
|
||||||
mi: {
|
mi: {
|
||||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
|
||||||
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, session);
|
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
|
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
|
||||||
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, session);
|
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
|
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, session);
|
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars, azdataContext);
|
||||||
},
|
},
|
||||||
edit: (
|
edit: (
|
||||||
name: string,
|
name: string,
|
||||||
@@ -186,8 +172,7 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
memoryRequest?: string,
|
memoryRequest?: string,
|
||||||
noWait?: boolean,
|
noWait?: boolean,
|
||||||
},
|
},
|
||||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
additionalEnvVars?: azdataExt.AdditionalEnvVars
|
||||||
session?: azdataExt.AzdataSession
|
|
||||||
): Promise<azdataExt.AzdataOutput<void>> => {
|
): Promise<azdataExt.AzdataOutput<void>> => {
|
||||||
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
|
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
|
||||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||||
@@ -195,59 +180,22 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||||
return this.executeCommand<void>(argsArray, additionalEnvVars, session);
|
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public async login(endpoint: string, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<azdataExt.AzdataOutput<void>> {
|
public async login(endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
|
||||||
// Since login changes the context we want to wait until all currently executing commands are finished before this is executed
|
const args = ['login', '-u', username];
|
||||||
while (this._currentlyExecutingCommands.length > 0) {
|
if (endpointOrNamespace.endpoint) {
|
||||||
await this._currentlyExecutingCommands[0];
|
args.push('-e', endpointOrNamespace.endpoint);
|
||||||
}
|
} else if (endpointOrNamespace.namespace) {
|
||||||
// Logins need to be done outside the session aware logic so call impl directly
|
args.push('--namespace', endpointOrNamespace.namespace);
|
||||||
return this.executeCommandImpl<void>(['login', '-e', endpoint, '-u', username], Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async acquireSession(endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataSession> {
|
|
||||||
const session = new AzdataSession();
|
|
||||||
session.sessionEnded().then(async () => {
|
|
||||||
// Wait for all commands running for this session to end
|
|
||||||
while (this._currentlyExecutingCommands.length > 0) {
|
|
||||||
await this._currentlyExecutingCommands[0].promise;
|
|
||||||
}
|
|
||||||
this._currentSession = undefined;
|
|
||||||
// Start our next command now that we're all done with this session
|
|
||||||
// TODO: Should we check if the command has a session that hasn't started? That should never happen..
|
|
||||||
// TODO: Look into kicking off multiple commands
|
|
||||||
this._queuedCommands.shift()?.deferred.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
// We're not in a session or waiting on anything so just set the current session right now
|
|
||||||
if (!this._currentSession && this._queuedCommands.length === 0) {
|
|
||||||
this._currentSession = session;
|
|
||||||
} else {
|
} else {
|
||||||
// We're in a session or another command is executing so add this to the end of the queued commands and wait our turn
|
throw new Error(loc.endpointOrNamespaceRequired);
|
||||||
const deferred = new Deferred<void>();
|
|
||||||
deferred.promise.then(() => {
|
|
||||||
this._currentSession = session;
|
|
||||||
// We've started a new session so look at all our queued commands and start
|
|
||||||
// the ones for this session now.
|
|
||||||
this._queuedCommands = this._queuedCommands.filter(c => {
|
|
||||||
if (c.session === this._currentSession) {
|
|
||||||
c.deferred.resolve();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._queuedCommands.push({ deferred, session: undefined });
|
|
||||||
await deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
return this.executeCommand<void>(args, Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }), azdataContext);
|
||||||
await this.login(endpoint, username, password, additionalEnvVars);
|
|
||||||
return session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,34 +213,16 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<R>> {
|
|
||||||
if (this._currentSession && this._currentSession !== session) {
|
|
||||||
const deferred = new Deferred<void>();
|
|
||||||
this._queuedCommands.push({ deferred, session: session });
|
|
||||||
await deferred.promise;
|
|
||||||
}
|
|
||||||
const executingDeferred = new Deferred<void>();
|
|
||||||
this._currentlyExecutingCommands.push(executingDeferred);
|
|
||||||
try {
|
|
||||||
return await this.executeCommandImpl<R>(args, additionalEnvVars);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this._currentlyExecutingCommands = this._currentlyExecutingCommands.filter(c => c !== executingDeferred);
|
|
||||||
executingDeferred.resolve();
|
|
||||||
// If there isn't an active session and we still have queued commands then we have to manually kick off the next one
|
|
||||||
if (this._queuedCommands.length > 0 && !this._currentSession) {
|
|
||||||
this._queuedCommands.shift()?.deferred.resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the specified azdata command. This is NOT session-aware so should only be used for calls that don't care about a session
|
* Executes the specified azdata command.
|
||||||
* @param args The args to pass to azdata
|
* @param args The args to pass to azdata
|
||||||
* @param additionalEnvVars Additional environment variables to set for this execution
|
* @param additionalEnvVars Additional environment variables to set for this execution
|
||||||
*/
|
*/
|
||||||
private async executeCommandImpl<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>> {
|
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>> {
|
||||||
try {
|
try {
|
||||||
|
if (azdataContext) {
|
||||||
|
args = args.concat('--controller-context', azdataContext);
|
||||||
|
}
|
||||||
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
||||||
return {
|
return {
|
||||||
logs: <string[]>output.log,
|
logs: <string[]>output.log,
|
||||||
@@ -442,8 +372,22 @@ export async function checkAndInstallAzdata(userRequested: boolean = false): Pro
|
|||||||
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
|
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
|
||||||
if (currentAzdata !== undefined) {
|
if (currentAzdata !== undefined) {
|
||||||
const newSemVersion = await discoverLatestAvailableAzdataVersion();
|
const newSemVersion = await discoverLatestAvailableAzdataVersion();
|
||||||
if (newSemVersion.compare(await currentAzdata.getSemVersion()) === 1) {
|
const currentSemVersion = await currentAzdata.getSemVersion();
|
||||||
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, (await currentAzdata.getSemVersion()).raw));
|
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);
|
return await promptToUpdateAzdata(newSemVersion.raw, userRequested);
|
||||||
} else {
|
} else {
|
||||||
Logger.log(loc.currentlyInstalledVersionIsLatest((await currentAzdata.getSemVersion()).raw));
|
Logger.log(loc.currentlyInstalledVersionIsLatest((await currentAzdata.getSemVersion()).raw));
|
||||||
@@ -504,39 +448,65 @@ async function promptToInstallAzdata(userRequested: boolean = false): Promise<bo
|
|||||||
* @param newVersion - provides the new version that the user will be prompted to update to
|
* @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
|
* @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.
|
* 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): Promise<boolean> {
|
async function promptToUpdateAzdata(newVersion: string, userRequested: boolean = false, required = false): Promise<boolean> {
|
||||||
let response: string | undefined = loc.yes;
|
if (required) {
|
||||||
const config = <AzdataDeployOption>getConfig(azdataUpdateKey);
|
let response: string | undefined = loc.yes;
|
||||||
if (userRequested) {
|
|
||||||
Logger.show();
|
const responses = [loc.yes, loc.no];
|
||||||
Logger.log(loc.userRequestedUpdate);
|
Logger.log(loc.promptForRequiredAzdataUpdateLog(MIN_AZDATA_VERSION.raw, newVersion));
|
||||||
}
|
response = await vscode.window.showInformationMessage(loc.promptForRequiredAzdataUpdate(MIN_AZDATA_VERSION.raw, newVersion), ...responses);
|
||||||
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));
|
Logger.log(loc.userResponseToUpdatePrompt(response));
|
||||||
}
|
if (response === loc.yes) {
|
||||||
if (response === loc.doNotAskAgain) {
|
try {
|
||||||
await setConfig(azdataUpdateKey, AzdataDeployOption.dontPrompt);
|
await updateAzdata();
|
||||||
} else if (response === loc.yes) {
|
vscode.window.showInformationMessage(loc.azdataUpdated(newVersion));
|
||||||
try {
|
Logger.log(loc.azdataUpdated(newVersion));
|
||||||
await updateAzdata();
|
return true;
|
||||||
vscode.window.showInformationMessage(loc.azdataUpdated(newVersion));
|
} catch (err) {
|
||||||
Logger.log(loc.azdataUpdated(newVersion));
|
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
|
||||||
return true;
|
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
|
||||||
} catch (err) {
|
vscode.window.showWarningMessage(loc.updateError(err));
|
||||||
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
|
Logger.log(loc.updateError(err));
|
||||||
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
|
}
|
||||||
vscode.window.showWarningMessage(loc.updateError(err));
|
}
|
||||||
Logger.log(loc.updateError(err));
|
} 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) {
|
||||||
|
try {
|
||||||
|
await updateAzdata();
|
||||||
|
vscode.window.showInformationMessage(loc.azdataUpdated(newVersion));
|
||||||
|
Logger.log(loc.azdataUpdated(newVersion));
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
/**
|
/**
|
||||||
* Deferred promise
|
* Deferred promise
|
||||||
*/
|
*/
|
||||||
export class Deferred<T> {
|
export class Deferred<T = void> {
|
||||||
promise: Promise<T>;
|
promise: Promise<T>;
|
||||||
resolve!: (value?: T | PromiseLike<T>) => void;
|
resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
reject!: (reason?: any) => void;
|
reject!: (reason?: any) => void;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
this.resolve = <any>resolve;
|
this.resolve = resolve;
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,11 @@ export const promptLog = (logEntry: string) => localize('azdata.promptLog', "Pro
|
|||||||
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 promptForAzdataInstall = localize('azdata.couldNotFindAzdataWithPrompt', "Could not find Azure Data CLI, install it now? If not then some features will not be able to function.");
|
||||||
export const promptForAzdataInstallLog = promptLog(promptForAzdataInstall);
|
export const promptForAzdataInstallLog = promptLog(promptForAzdataInstall);
|
||||||
export const promptForAzdataUpdate = (version: string): string => localize('azdata.promptForAzdataUpdate', "A new version of Azure Data CLI ( {0} ) is available, do you wish to update to it now?", version);
|
export const promptForAzdataUpdate = (version: string): string => localize('azdata.promptForAzdataUpdate', "A new version of Azure Data CLI ( {0} ) is available, do you wish to update to it now?", version);
|
||||||
|
export const 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 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 extension to function, some features may not work correctly until that version or higher is installed.", requiredVersion);
|
||||||
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
||||||
export const installError = (err: any): string => localize('azdata.installError', "Error installing Azure Data CLI: {0}", err.message ?? err);
|
export const installError = (err: any): string => localize('azdata.installError', "Error installing Azure Data CLI: {0}", err.message ?? err);
|
||||||
export const updateError = (err: any): string => localize('azdata.updateError', "Error updating 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);
|
||||||
@@ -66,3 +69,4 @@ export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => l
|
|||||||
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
||||||
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
|
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
|
||||||
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
|
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
|
||||||
|
export const endpointOrNamespaceRequired = localize('azdata.endpointOrNamespaceRequired', "Either an endpoint or a namespace must be specified");
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ describe('api', function (): void {
|
|||||||
it('succeed when azdata present and EULA accepted', async function (): Promise<void> {
|
it('succeed when azdata present and EULA accepted', async function (): Promise<void> {
|
||||||
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
|
||||||
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
|
||||||
const azdataTool = new AzdataTool('', '1.0.0');
|
const azdataTool = new AzdataTool('', '99.0.0');
|
||||||
const azdataToolService = new AzdataToolService();
|
const azdataToolService = new AzdataToolService();
|
||||||
azdataToolService.localAzdata = azdataTool;
|
azdataToolService.localAzdata = azdataTool;
|
||||||
// Not using a mock here because it'll hang when resolving mocked objects
|
// Not using a mock here because it'll hang when resolving mocked objects
|
||||||
@@ -60,7 +60,7 @@ describe('api', function (): void {
|
|||||||
sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
|
sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
|
||||||
// Version needs to be valid so it can be parsed correctly
|
// Version needs to be valid so it can be parsed correctly
|
||||||
if (args[0] === '--version') {
|
if (args[0] === '--version') {
|
||||||
return { stdout: `1.0.0`, stderr: '' };
|
return { stdout: `99.0.0`, stderr: '' };
|
||||||
}
|
}
|
||||||
console.log(args[0]);
|
console.log(args[0]);
|
||||||
return { stdout: `{ }`, stderr: '' };
|
return { stdout: `{ }`, stderr: '' };
|
||||||
@@ -96,15 +96,8 @@ describe('api', function (): void {
|
|||||||
async function assertApiCalls(api: azdataExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
|
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.getPath(), 'getPath');
|
||||||
await assertCallback(api.azdata.getSemVersion(), 'getSemVersion');
|
await assertCallback(api.azdata.getSemVersion(), 'getSemVersion');
|
||||||
await assertCallback(api.azdata.login('', '', ''), 'login');
|
await assertCallback(api.azdata.login({ endpoint: 'https://127.0.0.1' }, '', ''), 'login');
|
||||||
await assertCallback((async () => {
|
await assertCallback(api.azdata.login({ namespace: 'namespace' }, '', ''), 'login');
|
||||||
let session: azdataExt.AzdataSession | undefined;
|
|
||||||
try {
|
|
||||||
session = await api.azdata.acquireSession('', '', '');
|
|
||||||
} finally {
|
|
||||||
session?.dispose();
|
|
||||||
}
|
|
||||||
})(), 'acquireSession');
|
|
||||||
await assertCallback(api.azdata.version(), 'version');
|
await assertCallback(api.azdata.version(), 'version');
|
||||||
|
|
||||||
await assertCallback(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
|
await assertCallback(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
|
||||||
@@ -117,7 +110,7 @@ describe('api', function (): void {
|
|||||||
await assertCallback(api.azdata.arc.sql.mi.list(), 'arc sql mi 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.delete(''), 'arc sql mi delete');
|
||||||
await assertCallback(api.azdata.arc.sql.mi.show(''), 'arc sql mi show');
|
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.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.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.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.show(''), 'arc sql postgres server show');
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdataExt from 'azdata-ext';
|
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
@@ -17,9 +16,8 @@ import * as fs from 'fs';
|
|||||||
import { AzdataReleaseInfo } from '../azdataReleaseInfo';
|
import { AzdataReleaseInfo } from '../azdataReleaseInfo';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import { eulaAccepted } from '../constants';
|
import { eulaAccepted } from '../constants';
|
||||||
import { sleep } from './testUtils';
|
|
||||||
|
|
||||||
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0');
|
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', azdata.MIN_AZDATA_VERSION.raw);
|
||||||
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
|
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,120 +220,10 @@ describe('azdata', function () {
|
|||||||
const endpoint = 'myEndpoint';
|
const endpoint = 'myEndpoint';
|
||||||
const username = 'myUsername';
|
const username = 'myUsername';
|
||||||
const password = 'myPassword';
|
const password = 'myPassword';
|
||||||
await azdataTool.login(endpoint, username, password);
|
await azdataTool.login({ endpoint: endpoint }, username, password);
|
||||||
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
|
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('acquireSession', function (): void {
|
|
||||||
it('calls login', async function (): Promise<void> {
|
|
||||||
const endpoint = 'myEndpoint';
|
|
||||||
const username = 'myUsername';
|
|
||||||
const password = 'myPassword';
|
|
||||||
const session = await azdataTool.acquireSession(endpoint, username, password);
|
|
||||||
session.dispose();
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('command executed under current session completes', async function (): Promise<void> {
|
|
||||||
const session = await azdataTool.acquireSession('', '', '');
|
|
||||||
try {
|
|
||||||
await azdataTool.arc.dc.config.show(undefined, session);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 0);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
|
|
||||||
});
|
|
||||||
it('multiple commands executed under current session completes', async function (): Promise<void> {
|
|
||||||
const session = await azdataTool.acquireSession('', '', '');
|
|
||||||
try {
|
|
||||||
// Kick off multiple commands at the same time and then ensure that they both complete
|
|
||||||
await Promise.all([
|
|
||||||
azdataTool.arc.dc.config.show(undefined, session),
|
|
||||||
azdataTool.arc.sql.mi.list(undefined, session)
|
|
||||||
]);
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 0);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
|
|
||||||
});
|
|
||||||
it('command executed without session context is queued up until session is closed', async function (): Promise<void> {
|
|
||||||
const session = await azdataTool.acquireSession('', '', '');
|
|
||||||
let nonSessionCommand: Promise<any> | undefined = undefined;
|
|
||||||
try {
|
|
||||||
// Start one command in the current session
|
|
||||||
await azdataTool.arc.dc.config.show(undefined, session);
|
|
||||||
// Verify that the command isn't executed until after the session is disposed
|
|
||||||
let isFulfilled = false;
|
|
||||||
nonSessionCommand = azdataTool.arc.sql.mi.list().then(() => isFulfilled = true);
|
|
||||||
await sleep(2000);
|
|
||||||
should(isFulfilled).equal(false, 'The command should not be completed yet');
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
await nonSessionCommand;
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 0);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
|
|
||||||
});
|
|
||||||
it('multiple commands executed without session context are queued up until session is closed', async function (): Promise<void> {
|
|
||||||
const session = await azdataTool.acquireSession('', '', '');
|
|
||||||
let nonSessionCommand1: Promise<any> | undefined = undefined;
|
|
||||||
let nonSessionCommand2: Promise<any> | undefined = undefined;
|
|
||||||
try {
|
|
||||||
// Start one command in the current session
|
|
||||||
await azdataTool.arc.dc.config.show(undefined, session);
|
|
||||||
// Verify that neither command is completed until the session is closed
|
|
||||||
let isFulfilled = false;
|
|
||||||
nonSessionCommand1 = azdataTool.arc.sql.mi.list().then(() => isFulfilled = true);
|
|
||||||
nonSessionCommand2 = azdataTool.arc.postgres.server.list().then(() => isFulfilled = true);
|
|
||||||
await sleep(2000);
|
|
||||||
should(isFulfilled).equal(false, 'The commands should not be completed yet');
|
|
||||||
} finally {
|
|
||||||
session.dispose();
|
|
||||||
}
|
|
||||||
await Promise.all([nonSessionCommand1, nonSessionCommand2]);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 0);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'list'], 3);
|
|
||||||
});
|
|
||||||
it('attempting to acquire a second session while a first is still active queues the second session', async function (): Promise<void> {
|
|
||||||
const firstSession = await azdataTool.acquireSession('', '', '');
|
|
||||||
let sessionPromise: Promise<azdataExt.AzdataSession> | undefined = undefined;
|
|
||||||
let secondSessionCommand: Promise<any> | undefined = undefined;
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
// Start one command in the current session
|
|
||||||
await azdataTool.arc.dc.config.show(undefined, firstSession);
|
|
||||||
// Verify that none of the commands for the second session are completed before the first is disposed
|
|
||||||
let isFulfilled = false;
|
|
||||||
sessionPromise = azdataTool.acquireSession('', '', '');
|
|
||||||
sessionPromise.then(session => {
|
|
||||||
isFulfilled = true;
|
|
||||||
secondSessionCommand = azdataTool.arc.sql.mi.list(undefined, session).then(() => isFulfilled = true);
|
|
||||||
});
|
|
||||||
await sleep(2000);
|
|
||||||
should(isFulfilled).equal(false, 'The commands should not be completed yet');
|
|
||||||
} finally {
|
|
||||||
firstSession.dispose();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
(await sessionPromise)?.dispose();
|
|
||||||
}
|
|
||||||
should(secondSessionCommand).not.equal(undefined, 'The second command should have been queued already');
|
|
||||||
await secondSessionCommand!;
|
|
||||||
|
|
||||||
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 0);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['login'], 2);
|
|
||||||
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('version', async function (): Promise<void> {
|
it('version', async function (): Promise<void> {
|
||||||
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
|
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
|
||||||
await azdataTool.version();
|
await azdataTool.version();
|
||||||
@@ -473,6 +361,7 @@ describe('azdata', function () {
|
|||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
||||||
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
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> {
|
it('successful update', async function (): Promise<void> {
|
||||||
@@ -577,7 +466,6 @@ describe('azdata', function () {
|
|||||||
|
|
||||||
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
||||||
it('finds latest available version of azdata successfully', async function (): Promise<void> {
|
it('finds latest available version of azdata successfully', async function (): Promise<void> {
|
||||||
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(releaseJson));
|
|
||||||
await azdata.discoverLatestAvailableAzdataVersion();
|
await azdata.discoverLatestAvailableAzdataVersion();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -706,7 +594,6 @@ async function testWin32UnsuccessfulUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testLinuxSuccessfulUpdate(userRequested = false) {
|
async function testLinuxSuccessfulUpdate(userRequested = false) {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
||||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
||||||
@@ -740,7 +627,6 @@ async function testDarwinSuccessfulUpdate(userRequested = false) {
|
|||||||
|
|
||||||
|
|
||||||
async function testWin32SuccessfulUpdate(userRequested = false) {
|
async function testWin32SuccessfulUpdate(userRequested = false) {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
|
||||||
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
|
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
|
||||||
@@ -748,7 +634,6 @@ async function testWin32SuccessfulUpdate(userRequested = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testLinuxSkippedUpdate() {
|
async function testLinuxSkippedUpdate() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
||||||
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
|
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
|
||||||
@@ -778,14 +663,12 @@ async function testDarwinSkippedUpdateDontPrompt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32SkippedUpdateDontPrompt() {
|
async function testWin32SkippedUpdateDontPrompt() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
|
should(executeSudoCommandStub.notCalled).be.true(`executeSudoCommand should not have been called ${executeSudoCommandStub.getCalls().join(os.EOL)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testLinuxSkippedUpdateDontPrompt() {
|
async function testLinuxSkippedUpdateDontPrompt() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
|
||||||
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
|
||||||
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
await azdata.checkAndUpdateAzdata(oldAzdataMock);
|
||||||
@@ -816,7 +699,6 @@ async function testDarwinSkippedUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32SkippedUpdate() {
|
async function testWin32SkippedUpdate() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
await azdata.checkAndUpdateAzdata(currentAzdataMock);
|
||||||
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
|
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
|
||||||
@@ -849,7 +731,6 @@ async function testLinuxSkippedInstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32SkippedInstall() {
|
async function testWin32SkippedInstall() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
sinon.stub(childProcess, 'executeCommand')
|
sinon.stub(childProcess, 'executeCommand')
|
||||||
.onFirstCall()
|
.onFirstCall()
|
||||||
@@ -863,7 +744,6 @@ async function testWin32SkippedInstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function testWin32SuccessfulInstall() {
|
async function testWin32SuccessfulInstall() {
|
||||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
|
||||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
|
||||||
.onFirstCall()
|
.onFirstCall()
|
||||||
|
|||||||
@@ -18,7 +18,3 @@ export async function assertRejected(promise: Promise<any>, message: string): Pr
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
60
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
60
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
@@ -17,7 +17,7 @@ declare module 'azdata-ext' {
|
|||||||
name = 'Microsoft.azdata'
|
name = 'Microsoft.azdata'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdditionalEnvVars = { [key: string]: string};
|
export type AdditionalEnvVars = { [key: string]: string };
|
||||||
|
|
||||||
export interface ErrorWithLink extends Error {
|
export interface ErrorWithLink extends Error {
|
||||||
messageWithLink: string;
|
messageWithLink: string;
|
||||||
@@ -160,7 +160,7 @@ declare module 'azdata-ext' {
|
|||||||
|
|
||||||
export interface PostgresServerShowResult {
|
export interface PostgresServerShowResult {
|
||||||
apiVersion: string, // "arcdata.microsoft.com/v1alpha1"
|
apiVersion: string, // "arcdata.microsoft.com/v1alpha1"
|
||||||
kind: string, // "postgresql-12"
|
kind: string, // "postgresql"
|
||||||
metadata: {
|
metadata: {
|
||||||
creationTimestamp: string, // "2020-08-19T20:25:11Z"
|
creationTimestamp: string, // "2020-08-19T20:25:11Z"
|
||||||
generation: number, // 1
|
generation: number, // 1
|
||||||
@@ -177,7 +177,8 @@ declare module 'azdata-ext' {
|
|||||||
}[],
|
}[],
|
||||||
settings: {
|
settings: {
|
||||||
default: { [key: string]: string } // { "max_connections": "101", "work_mem": "4MB" }
|
default: { [key: string]: string } // { "max_connections": "101", "work_mem": "4MB" }
|
||||||
}
|
},
|
||||||
|
version: string // "12"
|
||||||
},
|
},
|
||||||
scale: {
|
scale: {
|
||||||
shards: number, // 1 (shards was renamed to workers, kept here for backwards compatibility)
|
shards: number, // 1 (shards was renamed to workers, kept here for backwards compatibility)
|
||||||
@@ -222,6 +223,17 @@ declare module 'azdata-ext' {
|
|||||||
state: string, // "Ready"
|
state: string, // "Ready"
|
||||||
logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:pg1'))
|
logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:pg1'))
|
||||||
metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=pg1
|
metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=pg1
|
||||||
|
podsStatus: {
|
||||||
|
conditions: {
|
||||||
|
lastTransitionTime: string, // "2020-08-19T17:05:39Z"
|
||||||
|
message?: string, // "containers with unready status: [fluentbit postgres telegraf]"
|
||||||
|
reason?: string, // "ContainersNotReady"
|
||||||
|
status: string, // "True"
|
||||||
|
type: string // "Ready"
|
||||||
|
}[],
|
||||||
|
name: string, // "pg-instancew-0",
|
||||||
|
role: string // "worker"
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,25 +245,27 @@ declare module 'azdata-ext' {
|
|||||||
code?: number
|
code?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AzdataSession extends vscode.Disposable { }
|
export interface EndpointOrNamespace {
|
||||||
|
endpoint?: string,
|
||||||
|
namespace?: string
|
||||||
|
}
|
||||||
export interface IAzdataApi {
|
export interface IAzdataApi {
|
||||||
arc: {
|
arc: {
|
||||||
dc: {
|
dc: {
|
||||||
create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
|
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: {
|
endpoint: {
|
||||||
list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcEndpointListResult[]>>
|
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcEndpointListResult[]>>
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcConfigListResult[]>>,
|
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigListResult[]>>,
|
||||||
show(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcConfigShowResult>>
|
show(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigShowResult>>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
delete(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
|
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||||
list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<PostgresServerListResult[]>>,
|
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerListResult[]>>,
|
||||||
show(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<PostgresServerShowResult>>,
|
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerShowResult>>,
|
||||||
edit(
|
edit(
|
||||||
name: string,
|
name: string,
|
||||||
args: {
|
args: {
|
||||||
@@ -267,17 +281,16 @@ declare module 'azdata-ext' {
|
|||||||
replaceEngineSettings?: boolean,
|
replaceEngineSettings?: boolean,
|
||||||
workers?: number
|
workers?: number
|
||||||
},
|
},
|
||||||
engineVersion?: string,
|
|
||||||
additionalEnvVars?: AdditionalEnvVars,
|
additionalEnvVars?: AdditionalEnvVars,
|
||||||
session?: AzdataSession
|
azdataContext?: string
|
||||||
): Promise<AzdataOutput<void>>
|
): Promise<AzdataOutput<void>>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sql: {
|
sql: {
|
||||||
mi: {
|
mi: {
|
||||||
delete(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
|
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||||
list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<SqlMiListResult[]>>,
|
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiListResult[]>>,
|
||||||
show(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<SqlMiShowResult>>,
|
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiShowResult>>,
|
||||||
edit(
|
edit(
|
||||||
name: string,
|
name: string,
|
||||||
args: {
|
args: {
|
||||||
@@ -288,22 +301,13 @@ declare module 'azdata-ext' {
|
|||||||
noWait?: boolean,
|
noWait?: boolean,
|
||||||
},
|
},
|
||||||
additionalEnvVars?: AdditionalEnvVars,
|
additionalEnvVars?: AdditionalEnvVars,
|
||||||
session?: AzdataSession
|
azdataContext?: string
|
||||||
): Promise<AzdataOutput<void>>
|
): Promise<AzdataOutput<void>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPath(): Promise<string>,
|
getPath(): Promise<string>,
|
||||||
login(endpoint: string, username: string, password: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzdataOutput<void>>,
|
login(endpointOrNamespace: EndpointOrNamespace, username: string, password: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
|
||||||
/**
|
|
||||||
* Acquires a session for the specified controller, which will log in to the specified controller and then block all other commands
|
|
||||||
* that are not part of the original session from executing until the session is released (disposed).
|
|
||||||
* @param endpoint
|
|
||||||
* @param username
|
|
||||||
* @param password
|
|
||||||
* @param additionalEnvVars
|
|
||||||
*/
|
|
||||||
acquireSession(endpoint: string, username: string, password: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzdataSession>,
|
|
||||||
/**
|
/**
|
||||||
* The semVersion corresponding to this installation of azdata. version() method should have been run
|
* 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
|
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||||
|
|||||||
@@ -317,6 +317,11 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"resourceDeploymentValueProviders": [
|
||||||
|
{
|
||||||
|
"id": "subscription-id-to-tenant-id"
|
||||||
|
}
|
||||||
|
],
|
||||||
"hasAzureResourceProviders": true
|
"hasAzureResourceProviders": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import {
|
|||||||
AzureAccount,
|
AzureAccount,
|
||||||
AzureAccountProviderMetadata,
|
AzureAccountProviderMetadata,
|
||||||
AzureAuthType,
|
AzureAuthType,
|
||||||
Deferred,
|
|
||||||
Resource,
|
Resource,
|
||||||
Tenant
|
Tenant
|
||||||
} from '../interfaces';
|
} from 'azurecore';
|
||||||
|
import { Deferred } from '../interfaces';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
|
|
||||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||||
@@ -161,7 +161,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
|||||||
const tenant = account.properties.tenants.find(t => t.id === tenantId);
|
const tenant = account.properties.tenants.find(t => t.id === tenantId);
|
||||||
|
|
||||||
if (!tenant) {
|
if (!tenant) {
|
||||||
throw new AzureAuthError(localize('azure.tenantNotFound', "Specifed tenant with ID '{0}' not found.", tenantId), `Tenant ${tenantId} not found.`, undefined);
|
throw new AzureAuthError(localize('azure.tenantNotFound', "Specified tenant with ID '{0}' not found.", tenantId), `Tenant ${tenantId} not found.`, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedTokens = await this.getSavedToken(tenant, resource, account.key);
|
const cachedTokens = await this.getSavedToken(tenant, resource, account.key);
|
||||||
@@ -248,7 +248,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
|||||||
|
|
||||||
if (response.data.error) {
|
if (response.data.error) {
|
||||||
Logger.error('Response error!', response.data);
|
Logger.error('Response error!', response.data);
|
||||||
throw new AzureAuthError(localize('azure.responseError', "Token retrival failed with an error. Open developer tools to view the error"), 'Token retrival failed', undefined);
|
throw new AzureAuthError(localize('azure.responseError', "Token retrieval failed with an error. Open developer tools to view the error"), 'Token retrieval failed', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessTokenString = response.data.access_token;
|
const accessTokenString = response.data.access_token;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { AuthorizationCodePostData, AzureAuth, OAuthTokenResponse } from './azureAuth';
|
import { AuthorizationCodePostData, AzureAuth, OAuthTokenResponse } from './azureAuth';
|
||||||
import { AzureAccountProviderMetadata, AzureAuthType, Deferred, Resource, Tenant } from '../interfaces';
|
import { AzureAccountProviderMetadata, AzureAuthType, Resource, Tenant } from 'azurecore';
|
||||||
|
import { Deferred } from '../interfaces';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ import {
|
|||||||
AzureAccountProviderMetadata,
|
AzureAccountProviderMetadata,
|
||||||
AzureAuthType,
|
AzureAuthType,
|
||||||
Tenant,
|
Tenant,
|
||||||
Resource,
|
Resource
|
||||||
Deferred,
|
} from 'azurecore';
|
||||||
// Tenant,
|
import { Deferred } from '../interfaces';
|
||||||
// Subscription
|
|
||||||
} from '../interfaces';
|
|
||||||
|
|
||||||
import { SimpleTokenCache } from '../simpleTokenCache';
|
import { SimpleTokenCache } from '../simpleTokenCache';
|
||||||
import { Logger } from '../../utils/Logger';
|
import { Logger } from '../../utils/Logger';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import * as nls from 'vscode-nls';
|
|||||||
import {
|
import {
|
||||||
AzureAccountProviderMetadata,
|
AzureAccountProviderMetadata,
|
||||||
AzureAuthType,
|
AzureAuthType,
|
||||||
Deferred,
|
|
||||||
AzureAccount
|
AzureAccount
|
||||||
} from './interfaces';
|
} from 'azurecore';
|
||||||
|
import { Deferred } from './interfaces';
|
||||||
|
|
||||||
import { SimpleTokenCache } from './simpleTokenCache';
|
import { SimpleTokenCache } from './simpleTokenCache';
|
||||||
import { Logger } from '../utils/Logger';
|
import { Logger } from '../utils/Logger';
|
||||||
@@ -107,14 +107,14 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
|
|||||||
return this._getSecurityToken(account, resource);
|
return this._getSecurityToken(account, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountSecurityToken(account: azdata.Account, tenant: string, resource: azdata.AzureResource): Thenable<Token | undefined> {
|
getAccountSecurityToken(account: azdata.Account, tenantId: string, resource: azdata.AzureResource): Thenable<Token | undefined> {
|
||||||
return this._getAccountSecurityToken(account, tenant, resource);
|
return this._getAccountSecurityToken(account, tenantId, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getAccountSecurityToken(account: azdata.Account, tenant: string, resource: azdata.AzureResource): Promise<Token | undefined> {
|
private async _getAccountSecurityToken(account: azdata.Account, tenantId: string, resource: azdata.AzureResource): Promise<Token | undefined> {
|
||||||
await this.initCompletePromise;
|
await this.initCompletePromise;
|
||||||
const azureAuth = this.getAuthMethod(undefined);
|
const azureAuth = this.getAuthMethod(undefined);
|
||||||
return azureAuth?.getAccountSecurityToken(account, tenant, resource);
|
return azureAuth?.getAccountSecurityToken(account, tenantId, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Promise<MultiTenantTokenResponse | undefined> {
|
private async _getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Promise<MultiTenantTokenResponse | undefined> {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import * as vscode from 'vscode';
|
|||||||
import { SimpleTokenCache } from './simpleTokenCache';
|
import { SimpleTokenCache } from './simpleTokenCache';
|
||||||
import providerSettings from './providerSettings';
|
import providerSettings from './providerSettings';
|
||||||
import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider';
|
import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider';
|
||||||
import { AzureAccountProviderMetadata, ProviderSettings } from './interfaces';
|
import { AzureAccountProviderMetadata } from 'azurecore';
|
||||||
|
import { ProviderSettings } from './interfaces';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
let localize = nls.loadMessageBundle();
|
let localize = nls.loadMessageBundle();
|
||||||
|
|||||||
@@ -3,129 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azurecore from 'azurecore';
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a tenant (an Azure Active Directory instance) to which a user has access
|
|
||||||
*/
|
|
||||||
export interface Tenant {
|
|
||||||
/**
|
|
||||||
* Globally unique identifier of the tenant
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display name of the tenant
|
|
||||||
*/
|
|
||||||
displayName: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier of the user in the tenant
|
|
||||||
*/
|
|
||||||
userId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The category the user has set their tenant to (e.g. Home Tenant)
|
|
||||||
*/
|
|
||||||
tenantCategory?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a resource exposed by an Azure Active Directory
|
|
||||||
*/
|
|
||||||
export interface Resource {
|
|
||||||
/**
|
|
||||||
* Identifier of the resource
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Endpoint url used to access the resource
|
|
||||||
*/
|
|
||||||
endpoint: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource ID for azdata
|
|
||||||
*/
|
|
||||||
azureResourceId?: azdata.AzureResource
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents settings for an AAD account provider
|
|
||||||
*/
|
|
||||||
interface Settings {
|
|
||||||
/**
|
|
||||||
* Host of the authority
|
|
||||||
*/
|
|
||||||
host?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier of the client application
|
|
||||||
*/
|
|
||||||
clientId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the Microsoft resource management resource
|
|
||||||
*/
|
|
||||||
microsoftResource?: Resource
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the AAD graph resource
|
|
||||||
*/
|
|
||||||
graphResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the MS graph resource
|
|
||||||
*/
|
|
||||||
msGraphResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the Azure resource management resource
|
|
||||||
*/
|
|
||||||
armResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the SQL Azure resource
|
|
||||||
*/
|
|
||||||
sqlResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the OSS RDBMS resource
|
|
||||||
*/
|
|
||||||
ossRdbmsResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the Azure Key Vault resource
|
|
||||||
*/
|
|
||||||
azureKeyVaultResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information that describes the Azure Dev Ops resource
|
|
||||||
*/
|
|
||||||
azureDevOpsResource?: Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
|
||||||
* instead of querying the tenants endpoint of the armResource
|
|
||||||
*/
|
|
||||||
adTenants?: string[];
|
|
||||||
|
|
||||||
// AuthorizationCodeGrantFlowSettings //////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An optional site ID that brands the interactive aspect of sign in
|
|
||||||
*/
|
|
||||||
siteId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect URI that is used to signify the end of the interactive aspect of sign it
|
|
||||||
*/
|
|
||||||
redirectUri?: string;
|
|
||||||
|
|
||||||
scopes?: string[]
|
|
||||||
|
|
||||||
portalEndpoint?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of configuration key with the metadata to instantiate the account provider
|
* Mapping of configuration key with the metadata to instantiate the account provider
|
||||||
@@ -139,44 +17,7 @@ export interface ProviderSettings {
|
|||||||
/**
|
/**
|
||||||
* Metadata for the provider
|
* Metadata for the provider
|
||||||
*/
|
*/
|
||||||
metadata: AzureAccountProviderMetadata;
|
metadata: azurecore.AzureAccountProviderMetadata;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension of account provider metadata to override settings type for Azure account providers
|
|
||||||
*/
|
|
||||||
export interface AzureAccountProviderMetadata extends azdata.AccountProviderMetadata {
|
|
||||||
/**
|
|
||||||
* Azure specific account provider settings.
|
|
||||||
*/
|
|
||||||
settings: Settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AzureAuthType {
|
|
||||||
AuthCodeGrant = 0,
|
|
||||||
DeviceCode = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Properties specific to an Azure account
|
|
||||||
*/
|
|
||||||
interface AzureAccountProperties {
|
|
||||||
/**
|
|
||||||
* Auth type of azure used to authenticate this account.
|
|
||||||
*/
|
|
||||||
azureAuthType?: AzureAuthType
|
|
||||||
|
|
||||||
providerSettings: AzureAccountProviderMetadata;
|
|
||||||
/**
|
|
||||||
* Whether or not the account is a Microsoft account
|
|
||||||
*/
|
|
||||||
isMsAccount: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of tenants (aka directories) that the account belongs to
|
|
||||||
*/
|
|
||||||
tenants: Tenant[];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Subscription {
|
export interface Subscription {
|
||||||
@@ -185,16 +26,6 @@ export interface Subscription {
|
|||||||
displayName: string
|
displayName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Override of the Account type to enforce properties that are AzureAccountProperties
|
|
||||||
*/
|
|
||||||
export interface AzureAccount extends azdata.Account {
|
|
||||||
/**
|
|
||||||
* AzureAccountProperties specifically used for Azure accounts
|
|
||||||
*/
|
|
||||||
properties: AzureAccountProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token returned from a request for an access token
|
* Token returned from a request for an access token
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class AzureDataGridProvider implements azdata.DataGridProvider {
|
|||||||
{ id: 'icon', type: 'image', field: 'iconPath', name: '', width: 25, sortable: false, filterable: false, resizable: false, tooltip: loc.typeIcon },
|
{ id: 'icon', type: 'image', field: 'iconPath', name: '', width: 25, sortable: false, filterable: false, resizable: false, tooltip: loc.typeIcon },
|
||||||
{ id: 'name', type: 'text', field: 'name', name: loc.name, width: 150 },
|
{ id: 'name', type: 'text', field: 'name', name: loc.name, width: 150 },
|
||||||
{ id: 'type', type: 'text', field: 'typeDisplayName', name: loc.resourceType, width: 150 },
|
{ id: 'type', type: 'text', field: 'typeDisplayName', name: loc.resourceType, width: 150 },
|
||||||
{ id: 'type', type: 'text', field: 'resourceGroup', name: loc.resourceGroup, width: 150 },
|
{ id: 'resourceGroup', type: 'text', field: 'resourceGroup', name: loc.resourceGroup, width: 150 },
|
||||||
{ id: 'location', type: 'text', field: 'locationDisplayName', name: loc.location, width: 150 },
|
{ id: 'location', type: 'text', field: 'locationDisplayName', name: loc.location, width: 150 },
|
||||||
{ id: 'subscriptionId', type: 'text', field: 'subscriptionName', name: loc.subscription, width: 150 }
|
{ id: 'subscriptionId', type: 'text', field: 'subscriptionName', name: loc.subscription, width: 150 }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -71,6 +71,39 @@ declare module 'azureResource' {
|
|||||||
export interface AzureResourceResourceGroup extends AzureResource {
|
export interface AzureResourceResourceGroup extends AzureResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AzureLocation {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
displayName: string,
|
||||||
|
regionalDisplayName: string,
|
||||||
|
metadata: {
|
||||||
|
regionType: string,
|
||||||
|
regionCategory: string,
|
||||||
|
geographyGroup: string,
|
||||||
|
longitude: number,
|
||||||
|
latitude: number,
|
||||||
|
physicalLocation: string,
|
||||||
|
pairedRegion: {
|
||||||
|
name: string,
|
||||||
|
id: string,
|
||||||
|
}[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureSqlManagedInstance extends AzureGraphResource {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ManagedDatabase {
|
||||||
|
id: string,
|
||||||
|
location: string,
|
||||||
|
name: string,
|
||||||
|
properties: {
|
||||||
|
sourceDatabaseId: string,
|
||||||
|
status: string
|
||||||
|
},
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface AzureResourceDatabase extends AzureSqlResource {
|
export interface AzureResourceDatabase extends AzureSqlResource {
|
||||||
serverName: string;
|
serverName: string;
|
||||||
serverFullName: string;
|
serverFullName: string;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { AzureResourceTreeProvider } from './tree/treeProvider';
|
|||||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService } from '../azureResource/interfaces';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService } from '../azureResource/interfaces';
|
||||||
import { AzureResourceServiceNames } from './constants';
|
import { AzureResourceServiceNames } from './constants';
|
||||||
import { AzureAccount, Tenant } from '../account-provider/interfaces';
|
import { AzureAccount, Tenant } from 'azurecore';
|
||||||
import { FlatAccountTreeNode } from './tree/flatAccountTreeNode';
|
import { FlatAccountTreeNode } from './tree/flatAccountTreeNode';
|
||||||
import { ConnectionDialogTreeProvider } from './tree/connectionDialogTreeProvider';
|
import { ConnectionDialogTreeProvider } from './tree/connectionDialogTreeProvider';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as msRest from '@azure/ms-rest-js';
|
|||||||
import { Account } from 'azdata';
|
import { Account } from 'azdata';
|
||||||
|
|
||||||
import { azureResource } from 'azureResource';
|
import { azureResource } from 'azureResource';
|
||||||
import { AzureAccount, Tenant } from '../account-provider/interfaces';
|
import { AzureAccount, Tenant } from 'azurecore';
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionService {
|
export interface IAzureResourceSubscriptionService {
|
||||||
getSubscriptions(account: Account, credential: msRest.ServiceClientCredentials, tenantId: string): Promise<azureResource.AzureResourceSubscription[]>;
|
getSubscriptions(account: Account, credential: msRest.ServiceClientCredentials, tenantId: string): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|||||||
import * as WS from 'ws';
|
import * as WS from 'ws';
|
||||||
|
|
||||||
import { IAzureTerminalService } from '../interfaces';
|
import { IAzureTerminalService } from '../interfaces';
|
||||||
import { AzureAccount, Tenant } from '../../account-provider/interfaces';
|
import { AzureAccount, Tenant } from 'azurecore';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
|||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||||
import { AzureAccount } from '../../account-provider/interfaces';
|
import { AzureAccount } from 'azurecore';
|
||||||
|
|
||||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
|||||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||||
import { AzureAccount } from '../../account-provider/interfaces';
|
import { AzureAccount } from 'azurecore';
|
||||||
import { AzureResourceService } from '../resourceService';
|
import { AzureResourceService } from '../resourceService';
|
||||||
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
|||||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod } from 'azurecore';
|
import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult } from 'azurecore';
|
||||||
import { azureResource } from 'azureResource';
|
import { azureResource } from 'azureResource';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
@@ -142,6 +142,40 @@ export async function getResourceGroups(appContext: AppContext, account?: azdata
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getLocations(appContext: AppContext, account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors: boolean = false): Promise<GetLocationsResult> {
|
||||||
|
const result: GetLocationsResult = { locations: [], errors: [] };
|
||||||
|
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants) || !subscription) {
|
||||||
|
const error = new Error(invalidAzureAccount);
|
||||||
|
if (!ignoreErrors) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
result.errors.push(error);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
await Promise.all(account.properties.tenants.map(async (tenant: { id: string; }) => {
|
||||||
|
try {
|
||||||
|
const path = `/subscriptions/${subscription.id}/locations?api-version=2020-01-01`;
|
||||||
|
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors);
|
||||||
|
result.locations.push(...response.response.data.value);
|
||||||
|
result.errors.push(...response.errors);
|
||||||
|
} catch (err) {
|
||||||
|
const error = new Error(localize('azure.accounts.getLocations.queryError', "Error fetching locations for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}",
|
||||||
|
account.displayInfo.displayName,
|
||||||
|
account.displayInfo.userId,
|
||||||
|
subscription.id,
|
||||||
|
subscription.name,
|
||||||
|
tenant.id,
|
||||||
|
err instanceof Error ? err.message : err));
|
||||||
|
console.warn(error);
|
||||||
|
if (!ignoreErrors) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
result.errors.push(error);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export async function runResourceQuery<T extends azureResource.AzureGraphResource>(
|
export async function runResourceQuery<T extends azureResource.AzureGraphResource>(
|
||||||
account: azdata.Account,
|
account: azdata.Account,
|
||||||
subscriptions: azureResource.AzureResourceSubscription[],
|
subscriptions: azureResource.AzureResourceSubscription[],
|
||||||
@@ -395,6 +429,15 @@ export async function makeHttpRequest(account: azdata.Account, subscription: azu
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getManagedDatabases(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, managedInstance: azureResource.AzureSqlManagedInstance, ignoreErrors: boolean): Promise<GetManagedDatabasesResult> {
|
||||||
|
const path = `/subscriptions/${subscription.id}/resourceGroups/${managedInstance.resourceGroup}/providers/Microsoft.Sql/managedInstances/${managedInstance.name}/databases?api-version=2020-02-02-preview`;
|
||||||
|
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors);
|
||||||
|
return {
|
||||||
|
databases: response?.response?.data?.value ?? [],
|
||||||
|
errors: response.errors ? response.errors : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getBlobContainers(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise<GetBlobContainersResult> {
|
export async function getBlobContainers(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise<GetBlobContainersResult> {
|
||||||
const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/blobServices/default/containers?api-version=2019-06-01`;
|
const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/blobServices/default/containers?api-version=2019-06-01`;
|
||||||
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors);
|
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors);
|
||||||
|
|||||||
175
extensions/azurecore/src/azurecore.d.ts
vendored
175
extensions/azurecore/src/azurecore.d.ts
vendored
@@ -17,6 +17,175 @@ declare module 'azurecore' {
|
|||||||
name = 'Microsoft.azurecore'
|
name = 'Microsoft.azurecore'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override of the Account type to enforce properties that are AzureAccountProperties
|
||||||
|
*/
|
||||||
|
export interface AzureAccount extends azdata.Account {
|
||||||
|
/**
|
||||||
|
* AzureAccountProperties specifically used for Azure accounts
|
||||||
|
*/
|
||||||
|
properties: AzureAccountProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties specific to an Azure account
|
||||||
|
*/
|
||||||
|
export interface AzureAccountProperties {
|
||||||
|
/**
|
||||||
|
* Auth type of azure used to authenticate this account.
|
||||||
|
*/
|
||||||
|
azureAuthType?: AzureAuthType
|
||||||
|
|
||||||
|
providerSettings: AzureAccountProviderMetadata;
|
||||||
|
/**
|
||||||
|
* Whether or not the account is a Microsoft account
|
||||||
|
*/
|
||||||
|
isMsAccount: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of tenants (aka directories) that the account belongs to
|
||||||
|
*/
|
||||||
|
tenants: Tenant[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum AzureAuthType {
|
||||||
|
AuthCodeGrant = 0,
|
||||||
|
DeviceCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of account provider metadata to override settings type for Azure account providers
|
||||||
|
*/
|
||||||
|
export interface AzureAccountProviderMetadata extends azdata.AccountProviderMetadata {
|
||||||
|
/**
|
||||||
|
* Azure specific account provider settings.
|
||||||
|
*/
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents settings for an AAD account provider
|
||||||
|
*/
|
||||||
|
interface Settings {
|
||||||
|
/**
|
||||||
|
* Host of the authority
|
||||||
|
*/
|
||||||
|
host?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of the client application
|
||||||
|
*/
|
||||||
|
clientId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the Microsoft resource management resource
|
||||||
|
*/
|
||||||
|
microsoftResource?: Resource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the AAD graph resource
|
||||||
|
*/
|
||||||
|
graphResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the MS graph resource
|
||||||
|
*/
|
||||||
|
msGraphResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the Azure resource management resource
|
||||||
|
*/
|
||||||
|
armResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the SQL Azure resource
|
||||||
|
*/
|
||||||
|
sqlResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the OSS RDBMS resource
|
||||||
|
*/
|
||||||
|
ossRdbmsResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the Azure Key Vault resource
|
||||||
|
*/
|
||||||
|
azureKeyVaultResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the Azure Dev Ops resource
|
||||||
|
*/
|
||||||
|
azureDevOpsResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
||||||
|
* instead of querying the tenants endpoint of the armResource
|
||||||
|
*/
|
||||||
|
adTenants?: string[];
|
||||||
|
|
||||||
|
// AuthorizationCodeGrantFlowSettings //////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional site ID that brands the interactive aspect of sign in
|
||||||
|
*/
|
||||||
|
siteId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect URI that is used to signify the end of the interactive aspect of sign it
|
||||||
|
*/
|
||||||
|
redirectUri?: string;
|
||||||
|
|
||||||
|
scopes?: string[]
|
||||||
|
|
||||||
|
portalEndpoint?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a resource exposed by an Azure Active Directory
|
||||||
|
*/
|
||||||
|
export interface Resource {
|
||||||
|
/**
|
||||||
|
* Identifier of the resource
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint url used to access the resource
|
||||||
|
*/
|
||||||
|
endpoint: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource ID for azdata
|
||||||
|
*/
|
||||||
|
azureResourceId?: azdata.AzureResource
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a tenant (an Azure Active Directory instance) to which a user has access
|
||||||
|
*/
|
||||||
|
export interface Tenant {
|
||||||
|
/**
|
||||||
|
* Globally unique identifier of the tenant
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name of the tenant
|
||||||
|
*/
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of the user in the tenant
|
||||||
|
*/
|
||||||
|
userId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The category the user has set their tenant to (e.g. Home Tenant)
|
||||||
|
*/
|
||||||
|
tenantCategory?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of the Azure datacenter regions. See https://docs.microsoft.com/dotnet/api/microsoft.azure.management.resourcemanager.fluent.core.region
|
* Enumeration of the Azure datacenter regions. See https://docs.microsoft.com/dotnet/api/microsoft.azure.management.resourcemanager.fluent.core.region
|
||||||
*/
|
*/
|
||||||
@@ -76,7 +245,9 @@ declare module 'azurecore' {
|
|||||||
export interface IExtension {
|
export interface IExtension {
|
||||||
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly?: boolean): Promise<GetSubscriptionsResult>;
|
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly?: boolean): Promise<GetSubscriptionsResult>;
|
||||||
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Promise<GetResourceGroupsResult>;
|
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Promise<GetResourceGroupsResult>;
|
||||||
|
getLocations(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Promise<GetLocationsResult>;
|
||||||
getSqlManagedInstances(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlManagedInstancesResult>;
|
getSqlManagedInstances(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlManagedInstancesResult>;
|
||||||
|
getManagedDatabases(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, managedInstance: azureResource.AzureSqlManagedInstance, ignoreErrors?: boolean): Promise<GetManagedDatabasesResult>;
|
||||||
getSqlServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlServersResult>;
|
getSqlServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlServersResult>;
|
||||||
getSqlVMServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlVMServersResult>;
|
getSqlVMServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlVMServersResult>;
|
||||||
getStorageAccounts(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetStorageAccountResult>;
|
getStorageAccounts(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetStorageAccountResult>;
|
||||||
@@ -106,7 +277,9 @@ declare module 'azurecore' {
|
|||||||
|
|
||||||
export type GetSubscriptionsResult = { subscriptions: azureResource.AzureResourceSubscription[], errors: Error[] };
|
export type GetSubscriptionsResult = { subscriptions: azureResource.AzureResourceSubscription[], errors: Error[] };
|
||||||
export type GetResourceGroupsResult = { resourceGroups: azureResource.AzureResourceResourceGroup[], errors: Error[] };
|
export type GetResourceGroupsResult = { resourceGroups: azureResource.AzureResourceResourceGroup[], errors: Error[] };
|
||||||
export type GetSqlManagedInstancesResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
export type GetLocationsResult = { locations: azureResource.AzureLocation[], errors: Error[] };
|
||||||
|
export type GetSqlManagedInstancesResult = { resources: azureResource.AzureSqlManagedInstance[], errors: Error[] };
|
||||||
|
export type GetManagedDatabasesResult = { databases: azureResource.ManagedDatabase[], errors: Error[] };
|
||||||
export type GetSqlServersResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
export type GetSqlServersResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
||||||
export type GetSqlVMServersResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
export type GetSqlVMServersResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
||||||
export type GetStorageAccountResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
export type GetStorageAccountResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user