Compare commits

...

386 Commits

Author SHA1 Message Date
brian-harris
4a45ba7cf2 Listing filtered locations for migration service public preview. (#16731) (#16742)
* Filtering regions for public preview

* rename var

* reverting back to const

Co-authored-by: Aasim Khan <aasimkhan30@gmail.com>
2021-08-12 22:52:19 -07:00
brian-harris
3b23809846 filter assessment results by MigationTargetType (#16701) (#16741) 2021-08-12 22:52:04 -07:00
brian-harris
0ff54a11df improve migration context loading (#16696) (#16740) 2021-08-12 22:51:49 -07:00
Benjin Dubishar
ce547fa4b6 Correcting bug to intentionally read from cached list of projects (#16762) (#16768)
* Correcting to intentionally read from cached list of projects

* using undefined for "workspace not checked yet" rather than empty array
2021-08-12 19:45:58 -07:00
brian-harris
d47b5f1afb Dev/brih/bugs/update readme public preview (#16729) (#16738)
* initial readme udpates

* update wording, bump version
2021-08-12 15:05:05 -07:00
Charles Gagnon
66ef175501 [Port] Fix modelview container removal issue (#16753) (#16757)
* Fix modelview container removal issue (#16753)

* include azdata fix
2021-08-12 14:05:24 -07:00
Karl Burtram
f92db4ae76 Use preview welcome page if enabled (#16734) (#16735)
* Use preview welcome page if enabled

* Fix typo in comment
2021-08-12 11:19:53 -07:00
Benjin Dubishar
c520c009a5 Fix incorrect .NET error (#16694) (#16717)
* Fix .NET Install state for error conditions

* Address comment- remove redundant constructor

Co-authored-by: Sakshi Sharma <57200045+SakshiS-harma@users.noreply.github.com>
2021-08-11 13:29:42 -07:00
Benjin Dubishar
fd148e557b Corrects workspace project tree refresh behavior for adding new projects to the workspace (#16650) (#16710)
* bugfix and updates

* PR feedback

* Deferred promise for project disk scan

* fix casing

* fixing race condition on extension activation, test failure
2021-08-11 12:22:17 -07:00
Benjin Dubishar
b0d3d06b5d Update pop-up to be an error message instead of informational message (#16660) (#16715)
* Update pop-up to be an error message

* Update message text

* Address comment

* Address comment

Co-authored-by: Sakshi Sharma <57200045+SakshiS-harma@users.noreply.github.com>
2021-08-11 12:21:30 -07:00
Aasim Khan
2fbfb2ad61 Updating mssql to get azure dashboard fixes: (#16658) (#16675)
1. making cpucount and phy mem props optional in ServerInfo
2. Moving the 2 props to azdata.proposed
2021-08-11 10:41:03 -07:00
Aditya Bist
4b12216c07 fix tasks (#16668) (#16685)
(cherry picked from commit 6122f96607)
2021-08-11 10:40:18 -07:00
brian-harris
98dc71a08b updating sts to get migration service fixes (#16627) (#16641)
Co-authored-by: Aasim Khan <aasimkhan30@gmail.com>
2021-08-10 09:35:28 -07:00
Alex Ma
05f8bb94f3 Port of langpack resources and source files from main for 1.32 (#16642)
* Massive update to localized XLFs (#16634)

* update to langpack source files 8/9/2021
2021-08-09 20:55:57 -07:00
Leila Lali
de027df955 Fixed a bug with loading model list (#16640) (#16645) 2021-08-09 18:10:04 -07:00
Alex Ma
4a4888891b [Loc] added continue message to sql-migration (#16625) 2021-08-09 09:53:52 -07:00
stuti149
0b2d353cd2 Validation in database selection wizard page added (#16561) 2021-08-09 21:39:09 +05:30
csigs
2dd4232a40 LEGO: check in for main to temporary branch. (#16616) 2021-08-07 16:38:22 -07:00
Aasim Khan
dc0ccba767 Changing to resource group level api to fetch dms (#16614) 2021-08-06 23:58:57 -07:00
brian-harris
a4bccb6e6c cutover cancel migration command state management (#16613) 2021-08-06 23:28:43 -07:00
Aasim Khan
f1b9931116 Fixed assessment error handling (#16610)
Displaying striped files in sts
2021-08-06 23:01:56 -07:00
Alex Ma
0f229b3444 [Loc] update to sqldatabaseprojects (#16612) 2021-08-06 21:03:19 -07:00
csigs
1a97516cb6 LEGO: check in for main to temporary branch. (#16611) 2021-08-06 20:53:59 -07:00
Sakshi Sharma
9ee941eb12 Error out for unsupported dotnet versions for sql db projects (#16428)
* Merge conflict resolution

* Throw error for unsupported versions of Dotnet

* Fix for darwin

* Fix for all platforms

* Address comments

* Fix extensionsGaller.json

* Address comments

* Update default installation path for linux

* Fix test

* Revert default installation location change for Linux

* Address comments

* Removed extra try-catch block
2021-08-06 18:41:34 -07:00
Alex Ma
512c2d3ddc [Loc] update sql-migration with latest strings (#16606) 2021-08-06 16:12:16 -07:00
Alan Ren
57f5e04590 fix accessibility issue (#16603) 2021-08-06 16:04:14 -07:00
Rachel Kim
fddfb72e10 Update Sql Migration extension strings (#16588) 2021-08-06 14:44:26 -07:00
Justin M
90ac21ccfb Bumped ToolsService version from 110 to 119. (#16587) 2021-08-06 14:18:23 -07:00
lewis-sanchez
310d651df8 Fix query database drop-down rendering location after resize (#16567)
* Fix query database drop-down rendering location after resize

* Revert "Fix query database drop-down rendering location after resize"

This reverts commit 62481a93e696d4fa79ce40ecef3a2567a65dd20c.

* Fixes drop down list rendering issues by closing the list on window
resize

* comment providing the explanation for new listener and checks if list is visible before hiding
2021-08-06 12:51:10 -07:00
Aasim Khan
63c461dca8 Fixed: (#16594)
Summary label
DMS creation retry times
DMS dropdown resetting
2021-08-06 12:12:53 -07:00
Alex Ma
6c40f52f35 update to langpack strings for code complete (#16597) 2021-08-06 11:44:09 -07:00
brian-harris
013ce71166 fix assessment db selection (#16591) 2021-08-06 11:27:24 -07:00
Alex Ma
3a4caa64d2 [Loc] update to localized xlf resources prior to code complete check in (#16595) 2021-08-06 11:17:04 -07:00
csigs
aeabb325f6 LEGO: check in for main to temporary branch. (#16593) 2021-08-06 09:31:37 -07:00
Charles Gagnon
b8da94f9ef Add Create Proj from DB to VS Code context menus (#16584)
* Add Create Proj from DB to VS Code context menus

* Fix error

* add comment

* use constant
2021-08-06 08:59:35 -07:00
csigs
e6f356accc LEGO: check in for main to temporary branch. (#16589) 2021-08-06 02:24:05 -07:00
Rachel Kim
991b9af198 Add tooltip to input box on disabled dropdowns (#16468) 2021-08-05 18:02:00 -07:00
Charles Gagnon
09a4dfa5b0 Fix pipeline when no tests are ran (#16586) 2021-08-05 16:19:46 -07:00
csigs
8ed463b4d7 LEGO: check in for main to temporary branch. (#16585) 2021-08-05 15:10:21 -07:00
Alex Ma
3b0cf9db50 [Loc] Update for sql-database-projects, sql-migration, and sql (#16583) 2021-08-05 13:58:53 -07:00
Lucy Zhang
9f761c44c4 Add incremental grid loading option to notebooks (#16577)
* render grids incrementally

* add loading spinner

* fix loading animation

* move rendergrids to notebook editor component

* add setting for incremental grid loading

* check configuration

* just use setTimeout to queue grids

* remove extra line

* add setter/getter for isloading

* add comment
2021-08-05 13:44:26 -07:00
brian-harris
c6308b77df Fix bugbash SQL-Migration extension migration load issues (#16575)
* fix migration refresh issues

* merge latest
2021-08-05 13:24:02 -07:00
Kim Santiago
33baaa475d Insert sql binding into Azure function quick pick (#16553)
* initial quick pick

* move constants

* remove commented code for now

* addressing comments

* update name

* update name in other places

* remove azure functions from name and rename file
2021-08-05 12:11:30 -07:00
Alex Ma
35983659b1 [Loc] Another update for sql-database-projects xlf (#16581) 2021-08-05 11:17:40 -07:00
Charles Gagnon
2b103a79c3 Add Create Project from Database quickpick (#16572)
* Add Create Project from Database quickpick

* fix test

* pr comments

* Add comment
2021-08-05 10:34:49 -07:00
Alex Ma
99b5c5ce8c [Loc] Update for sql-migration and azurecore (#16574) 2021-08-04 18:32:49 -07:00
Karl Burtram
84d85ba43e Bump tar in notebook extension (#16573)
* Bump tar in notebook extension

* Bump to latest tar
2021-08-04 17:00:24 -07:00
Justin M
7b709b37cd Changed AzureMonitorLogs extension icon for gallery (#16566) 2021-08-04 16:55:18 -07:00
Aasim Khan
3d7edd2d6a Revamping cutover page based on new mockups (#16547)
* WIP

* Fixing some table issues

* updating package.json

* Fixing readable time

* fixing display string

* Handling null case in get12hourtime util method
2021-08-04 16:21:01 -07:00
csigs
cf97ced7f1 LEGO: check in for main to temporary branch. (#16571) 2021-08-04 15:20:24 -07:00
Charles Gagnon
0060b0e27b Update service-downloader (#16565) 2021-08-04 14:29:54 -07:00
Christopher Suh
47b249a7b1 keytar ci build fix (#16555) 2021-08-04 11:52:45 -07:00
Justin M
c4ef48dcd8 Changed README.md and changed name of azuremonitorlogs container label. (#16530) 2021-08-04 10:54:58 -07:00
Alex Ma
366fe7c162 [Loc] added in changes to sql.xlf (#16564) 2021-08-04 09:57:09 -07:00
Charles Gagnon
d189805bcc Add marked.js parse differences tests (#16554)
* Add marked.js parse differences tests

* cleanup
2021-08-04 09:07:24 -07:00
csigs
aef4474a08 LEGO: check in for main to temporary branch. (#16563) 2021-08-04 08:44:18 -07:00
csigs
5e34982fd9 LEGO: check in for main to temporary branch. (#16559) 2021-08-04 08:43:42 -07:00
goyal-anjali
43158a60e3 Add error details to error banner in migration cutover dialog (#16494)
* Add error details to error banner in migration cutover dialog

* Adding method functionality to copy migration details
2021-08-04 15:53:37 +05:30
lewis-sanchez
e3d672cea1 Add a new developer to the list of developers for onboarding purposes (#16400) 2021-08-03 23:31:25 -07:00
Daniel Grajeda
0567141bc4 Notebook Views Actions (#16207)
This adds the actions currently needed by the views
2021-08-03 22:52:27 -07:00
goyal-anjali
6985d95300 Fixed 'UI hanging on clearing dropdown error' for database backup page (#16541)
This PR fixes
ADS UI - Clear resource group drop down box and click Next on the Database Backup Wizard - Cannot read property 'displayName' ERROR
2021-08-04 10:42:08 +05:30
Daniel Grajeda
e2dd257fa9 Add Notebook Views dropdown (#16228)
This adds the entry point to NVs. It is currently hidden behind a feature flag, which can be enabled in the settings.
2021-08-03 21:15:11 -07:00
Charles Gagnon
da7585eb44 Update arc required engine version (#16546)
* Update arc required engine version

* Update CODEOWNERS
2021-08-03 16:38:47 -07:00
csigs
2f8d00af56 LEGO: check in for main to temporary branch. (#16552) 2021-08-03 15:04:44 -07:00
Alex Ma
7cdb21cca5 [Loc] update to sql-database-projects strings (#16543) 2021-08-03 10:18:19 -07:00
Alexander Ivanov
b35e78a07f Automatically add intermediate folders for SQL project items. (#16332)
* Automatically add intermediate folders for SQL project items.

While using the SQL database projects through the API, I noticed that project may end up in somewhat inconsistent state, where files will be added to the project, but their parent folders will not. This in turn resulted in failure to remove these folders from project - they will show up in the UI tree, but deleting them will cause an error. In order to align with how Visual Studio manages the projects, this change will ensure that all intermediate folders are present in the project, when new files or folders are added.

While this change improves project "correctness" when accessing it through SQL projects extension APIs, there is still a possibility that someone will open an "incorrect" previously created project. This change does not address it and folder removal may still fail.

* Update the code to never throw on duplicate items when adding files and folders to project.

After a conversation with the sqlproj owners, we agreed that there are no scenarios that would prompt us to throw an error, if duplicate item is being added to the project. Ultimately, the goal of such a request would be to have an item in the project file, which is already present, therefore the call becomes a no-op.

This allowed me to simplify the new code that was ensuring all intermediate folders are present in the project when adding files and folders.
2021-08-03 09:49:11 -07:00
csigs
052cb54199 LEGO: check in for main to temporary branch. (#16540) 2021-08-03 09:39:31 -07:00
csigs
42b1a10fec LEGO: check in for main to temporary branch. (#16536) 2021-08-03 09:39:02 -07:00
csigs
d28d77dbfc LEGO: check in for main to temporary branch. (#16534) 2021-08-03 09:38:38 -07:00
Brian Bergeron
aadf2ae081 pass wizard args to az cli (#16532)
Co-authored-by: Brian Bergeron <brberger@microsoft.com>
2021-08-03 09:06:16 -07:00
Alex Ma
760cf01022 [Loc] another update to resource-deployment and sql-migration (#16531) 2021-08-02 17:41:26 -07:00
csigs
97978cbe81 LEGO: check in for main to temporary branch. (#16529) 2021-08-02 16:26:51 -07:00
Aasim Khan
466193adbe Fixing the redacted migration service id by encoding it to base64 (#16528) 2021-08-02 16:26:09 -07:00
rajeshka
56ad631478 Fixed UI regression (#16526)
* Add CodeQL Analysis workflow (#10195)

* Add CodeQL Analysis workflow

* Fix path

* Fixed UI regression in main

* removed the file which should not been included

* Addressed PR comments

* Addressed PR

Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
2021-08-02 15:21:05 -07:00
brian-harris
919cc732b7 add new support request buttons (#16045)
* add new support request buttons

* hide feedback and new support incedent commands from command palette
2021-08-02 13:29:27 -07:00
Charles Gagnon
244e27c2de Remove infrastructure from resource-deployment (#16520) 2021-08-02 13:14:33 -07:00
Alex Ma
0a181a1ba8 [Loc] bumped version for langpacks (#16527) 2021-08-02 12:21:27 -07:00
Alex Ma
045dc3e558 update to langpacks8-2-2021 (#16525) 2021-08-02 11:15:58 -07:00
Barbara Valdez
30393a1f1b Integrate drag and drop api (#16500)
* Initial implementation of drag and drop api (#122239)

* Add drag and drop controller (#123542)

* Tree data transfer dnd (#128666)

* add drop method to sql files

Co-authored-by: Alex Ross <alros@microsoft.com>
2021-08-02 10:43:52 -07:00
Alan Ren
748bb53173 handle the keyboard event properly in hyperlink (#16508)
* handle the keyboard event properly in hyperlink

* fix a couple more issues
2021-08-02 10:39:36 -07:00
Alex Ma
373a519f25 Update for Language XLFs for August Release (#16522) 2021-08-02 10:38:54 -07:00
Charles Gagnon
3af2b4a13d azdataToolOld -> azdataTool (#16517) 2021-08-02 10:28:46 -07:00
Alex Ma
91676afd0d small update to arc and resource-deployment (#16521) 2021-08-02 10:02:28 -07:00
Candice Ye
914fe8fc29 Replacing all azdata with az (#16502)
* Changed azdata to az in azcli extension and resource-deployment, and some arc. Removed user, pass, url from controller connect blade. Commented out tests. Ported over work from old branch.

* Changed unit tests, all unit tests passing. Changed parameters to new ones, fixed some Controller Connect issues.

* Connect data controller and create dc working.

* Changed az back to azdata in necessary places in resource-deployment.

* Changed notebook values and added namespace to some params.

* Added some changes from PR to this branch

* Changed azdata.ts to az.ts and changed subscription parameter

* Brought over changes from azcli PR into this branch.

* added endpoint, username, password to getIsPassword

* Changed notebooks to use proper az params, hard coded in some values to verify it is working, removed some variableNames from package.json.

* Changed -sc to --storage-class in notebook

* Added namespace to SQL deploy, deleted dc create in api

* Deleted more dc create code and uncommented findAz() with unfinished work on Do Not Ask Again.

* Removed (preview) from extensions/arc and extensions/azcli excluding preview:true in package.json

* Commented out install/update prompts until DoNotAskAgain is implemented

* Fixed bugs: JSON Output errors are now being caught, --infrastructure now has a required UI component with dropdown options, config page loads properly, SQL create flags use full names instead of shortnames.

* Adds validation to pg extensions and bug fixes (#16486)

* Extensions

* Server parameters

* Change locaiton of postgres extensions, pr fixes

* Change location of list

* List spacing

* Commented out Don't Ask Again prompt implementation.

* Uncommented header of a test file.

* Added Azure CLI arcdata extension to Prerequisites

* Reverted package.json and yarn.lock

* Took away casting of stderr and stdout in executeCommand.

* Deleted override function for initializeFields in connectControllerDialog.ts

* Removed fakeAzApi for testing and added back in (Preview)

* Removed en-us from python notebook links.

* Deleted azdata tool from tool tests in resource-deployment

* Deleted another instance of azdata in tool test

* Add back in azdata tooltype

* Remove en-us

* Replaced AzdataTool in typings

* Reverting adding azdata tool back in

* Changed Azdata to AzdataToolOld

* Added back azdata tool type

* Added AzdataToolOld to tool types

* fix test

Co-authored-by: Candice Ye <canye@microsoft.com>
Co-authored-by: nasc17 <nasc@microsoft.com>
Co-authored-by: nasc17 <69922333+nasc17@users.noreply.github.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
2021-08-01 15:12:24 -07:00
Alex Ma
65cc61fdbd update for xlfs 8/1/2021 (#16514) 2021-08-01 11:05:21 -07:00
Charles Gagnon
83af84774a Sql Proj VS Code fixes (#16506)
* Sql Proj VS Code fixes

* remove comment
2021-08-01 00:24:28 -07:00
Rachel Kim
b3e9428898 Enable offline migration mode on sql migration extension (#16459) 2021-07-30 23:15:09 -07:00
Charles Gagnon
2427cbe3c6 Remove unused commands from vs code sql proj (#16507) 2021-07-30 19:23:40 -07:00
Vasu Bhog
179678b495 Refactor Notebook Link Handling (#16473)
* add keep absolute paths instead convert setting

* update tests/config

* refactor links in NotebookLinkHandler
2021-07-30 19:20:38 -07:00
Charles Gagnon
a7c1bcaf93 Fix azdata API check when webpacked (#16505) 2021-07-30 16:06:50 -07:00
Charles Gagnon
3362462142 Add alert for description when modal message is shown (#16483)
* Add alert for description when modal message is shown

* Fix hygiene
2021-07-30 15:02:20 -07:00
brian-harris
87cc568493 update extension package to v 0.1.5 (#16504) 2021-07-30 14:40:03 -07:00
nasc17
8a67f87090 Enable information tip buttons so they can be focused on (#16496)
* Enable buttons so they can be focused

* Test

* Add text description

* Add description to parameters info
2021-07-30 14:28:39 -07:00
Charles Gagnon
47151435e7 Fix expanded state announce for dialog headers (#16499)
* Fix expanded state announce for dialog headers

* update comment
2021-07-30 12:39:38 -07:00
goyal-anjali
30dffdf696 Name added for more info link - accessibility (#16397) 2021-07-31 00:14:53 +05:30
Charles Gagnon
68a22421f7 Fix some ModelView API definitions (#16497)
* Remove duplicate title property

* More fixes

* Removed a few unnecessary extends
2021-07-30 11:44:45 -07:00
csigs
d6fd64c5eb LEGO: check in for main to temporary branch. (#16493) 2021-07-30 11:38:55 -07:00
Charles Gagnon
245ae5b9ee Fix listview component accessibility (#16492)
* Add aria label to list component

* Fix accessibility for listview component
2021-07-30 10:44:53 -07:00
Vasu Bhog
788c84a1ee fix email else condition (#16491) 2021-07-29 18:37:06 -07:00
csigs
aafe0876bb LEGO: check in for main to temporary branch. (#16487) 2021-07-29 18:10:33 -07:00
brian-harris
e3c7e06983 SQL-Migration extension - fix refresh reentrancy hang (#16482)
* Fix application hang on refresh

* fix re-entrancy issue with refresh

* adding additional refresh checking
2021-07-29 16:44:56 -07:00
Alex Ma
251d250523 added change to sql-migration (#16484) 2021-07-29 13:10:06 -07:00
csigs
f4d0bdc784 LEGO: check in for main to temporary branch. (#16481) 2021-07-29 11:28:19 -07:00
Aasim Khan
0bbbb91adf Trying to fix the scrollbar bug (#16477) 2021-07-29 10:19:28 -07:00
Aasim Khan
39f65b1881 Adding wizard step telemetry for sql migration wizard (#16469)
* Adding wizard step telemetry for sql migration wizard

* Refactoring event name

* refactoring function name
2021-07-29 10:14:45 -07:00
Aasim Khan
0e9d956ee5 Adding support for multi cloud in azure rest api calls (#16454) 2021-07-29 10:14:15 -07:00
Aasim Khan
01671b118d Adding aria label to all the migration tables (#16476) 2021-07-29 09:20:58 -07:00
chayaverma7
ef3d2e7d99 Updating Change log.md for Hotfix v1.31.1 (#16480)
* Updating Change log.md for Hotfix v1.31.1

* Update CHANGELOG.md

* Update CHANGELOG.md
2021-07-29 21:21:19 +05:30
Charles Gagnon
1b073c6748 Package VS Code extensions (#16465)
* Package VS Code extensions

* fixes

* Add back in path

* remove extra indents

* Remove extra parens

* try fix
2021-07-29 08:33:46 -07:00
csigs
daa897936b LEGO: check in for main to temporary branch. (#16474) 2021-07-29 00:29:16 -07:00
csigs
eaf9757565 LEGO: check in for main to temporary branch. (#16471) 2021-07-28 15:38:07 -07:00
Aasim Khan
d1d858090c Fixing the resource name passed to create sql migration api (#16470) 2021-07-28 15:36:06 -07:00
brian-harris
c178b6327a add warnings labels for blocking assessment issues (#16460)
* Add CodeQL Analysis workflow (#10195)

* Add CodeQL Analysis workflow

* Fix path

* add assessment warnings info

* add migration assessment blocking issue warnings

* update grid column widths

* remove unexpected change

* adding learn more link

Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
2021-07-28 12:51:20 -07:00
nasc17
c32c09e1a7 Postgres Compute and Storage page accessibility fixes. (#16203)
* focus, required, aria

* Fix logic for focusing buttons

* Remove buttons focus
2021-07-28 12:28:40 -07:00
Charles Gagnon
25e237fa35 Update CODEOWNERS (#16464)
* Update CODEOWNERS

* newline
2021-07-28 11:37:48 -07:00
Charles Gagnon
118d03c151 Disable az cli prompt (#16466) 2021-07-28 09:18:38 -07:00
Charles Gagnon
27c86e3c45 Fix connect dialog color contrast (#16453) 2021-07-28 08:52:49 -07:00
Alex Ma
07ad50670e update to sql-database-projects (#16461) 2021-07-27 21:48:24 -07:00
csigs
8afd420971 LEGO: check in for main to temporary branch. (#16458) 2021-07-27 20:52:23 -07:00
csigs
5ff102d531 LEGO: check in for main to temporary branch. (#16450)
Co-authored-by: Karl Burtram <karlb@microsoft.com>
2021-07-27 20:51:53 -07:00
Cory Rivera
63a65f5821 Remove quotes from inserted markdown links (#16457) 2021-07-27 16:40:20 -07:00
Charles Gagnon
2d8e0d648a Fix SQL/Notebook editors opening as plaintext (#16442)
* Register overrides at startup

* Always wait for extensions

* Fix compile errors
2021-07-27 13:46:16 -07:00
Aasim Khan
7a35d4aeeb Adding support for sending request headers in azure core rest request API (#16443)
* Adding support for sending additional headers in azure http requests

* Added session ids in all azure rest calls

* Fixed param name

* Adding default parameter value for request headers
2021-07-27 12:36:50 -07:00
Candice Ye
35207a1e04 Added azcli extension only (#16415)
* Changed azdata to az in azcli extension and resource-deployment, and some arc. Removed user, pass, url from controller connect blade. Commented out tests. Ported over work from old branch.

* Changed unit tests, all unit tests passing. Changed parameters to new ones, fixed some Controller Connect issues.

* Connect data controller and create dc working.

* Changed az back to azdata in necessary places in resource-deployment.

* Changed notebook values and added namespace to some params.

* Reverted all changes that are not in azcli. Also deleted some unused variables in azcli constants.ts and some tests.

* Fixed package.json

* Deleted en-us from links, changed az. to azcli.arc in package.json

* Addressed PR comments.

Co-authored-by: Candice Ye <canye@microsoft.com>
2021-07-27 12:12:15 -07:00
Lucy Zhang
7c14ec2b6d Add intellisense checks in code cell smoke test (#16362)
* check colorization and completion suggestions

* check for suggestions widget

* make selector more specific
2021-07-27 11:02:52 -07:00
Justin M
f8da3cc32a China / Germany Cloud Fix (#15746)
* Added missing resources to chinaAzureSettings and germanyAzureSettings in providerSettings

* Update providerSettings.ts

* Fixed typo in mooncake armResource

* Fixed host for China Cloud in ProviderSettings
2021-07-27 10:24:15 -07:00
Kim Santiago
f01e9e2fc0 add api to open new project dialog with only one project type with filtered target platforms (#16315) 2021-07-27 10:03:44 -07:00
Charles Gagnon
b2c203eaef Hook up sql proj publish (#16444) 2021-07-27 09:32:42 -07:00
Lucy Zhang
24349885d3 fix accept fn for waitForTextContent (#16449) 2021-07-27 09:15:35 -07:00
csigs
2e9eff7ffc LEGO: check in for main to temporary branch. (#16447) 2021-07-27 09:08:35 -07:00
Charles Gagnon
6a08af4d9a Fix link accessibilty issues (#16419)
* Fix link accessibilty issues

* Move comment
2021-07-26 14:55:33 -07:00
Charles Gagnon
a0f56890b5 Add TextType property (#16421) 2021-07-26 13:10:17 -07:00
Aasim Khan
70fc6bd43d Adding Assessment Telemetry in SQL Migration (#15935)
* Adding telemetry for assessment

* Removing dms loading fix

* Fixing PR

* removing collection of account id

* moving database warning count to the server assessment event

* Removing individual warning and issue events

* Adding aggregates for database issues and warnings

* removing extra line

* Adding other telemetry events in sql migration

* Fixed changes made in the PR

* Fixing attribute names

* Consolidating issues and errors in 1 event

* Converting dependencies to dev depenedencies

* Adding a catch for the telemetry function

* moving the non type uuid to non dev

* Made hashmap code cleaner

* Fixing cutover start time

* Fixing object creation

* Reverting back to old method.

* Modifying the date time to js objects

* Converting issues and warnings to json object
2021-07-26 12:26:37 -07:00
stuti149
c60bcc0d0d added name in developer.ts (#16437) 2021-07-26 21:23:27 +05:30
csigs
c3c6d8ee8c LEGO: check in for main to temporary branch. (#16440) 2021-07-26 08:44:14 -07:00
csigs
9a71846e22 LEGO: check in for main to temporary branch. (#16439) 2021-07-26 08:43:53 -07:00
csigs
e4db31b334 LEGO: check in for main to temporary branch. (#16434) 2021-07-25 14:51:24 -07:00
csigs
63dc94009e LEGO: check in for main to temporary branch. (#16432) 2021-07-25 14:50:25 -07:00
Alex Ma
77d397ce18 update to arc connection string (#16433) 2021-07-25 08:44:39 -07:00
csigs
6aeea8f1df LEGO: check in for main to temporary branch. (#16429) 2021-07-25 08:27:47 -07:00
csigs
fdb426cda5 LEGO: check in for main to temporary branch. (#16426)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-25 08:27:04 -07:00
Alex Ma
f72a252fb0 removed newspace (#16431) 2021-07-25 08:25:49 -07:00
csigs
6fcfa93329 LEGO: Pull request from lego/hb_04604851-bac4-4681-9f74-73de611d6e48_20210724153241691 to main (#16425)
* LEGO: check in for main to temporary branch.

* Update sql-migration.xlf.lcl

* Update sql-migration.xlf.lcl

Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-25 08:20:13 -07:00
csigs
c4ce3bef8d LEGO: check in for main to temporary branch. (#16424)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-25 08:08:12 -07:00
csigs
9fda448303 LEGO: check in for main to temporary branch. (#16423)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-25 08:06:00 -07:00
csigs
01aefe7b9f LEGO: check in for main to temporary branch. (#16418) 2021-07-25 08:00:20 -07:00
Christopher C
cb8c4b80d0 wording, removing ref to "bootstrap" (#15187)
* wording, removing ref to "bootstrap"

* removing configure-ag nb; unfinished
2021-07-23 16:58:43 -07:00
Christopher C
668ab43865 Update README.md (#14859)
* Update README.md

* wording
2021-07-23 16:57:53 -07:00
Charles Gagnon
be0edf9606 Add aria label to connection string pages (#16420) 2021-07-23 16:55:02 -07:00
Vasu Bhog
151522013f Notebook Markdown email rendering fix (#16417)
* fix email WYSIWYG rendering
2021-07-23 15:24:34 -07:00
Charles Gagnon
66c62fcce3 Fix relative path check in HtmlMarkdownConverter (#16192) 2021-07-23 12:33:45 -07:00
Alex Ma
699648ff6d added cancelling for sql-migration (#16416) 2021-07-23 09:55:19 -07:00
csigs
7968d51172 LEGO: check in for main to temporary branch. (#16414) 2021-07-23 09:19:16 -07:00
csigs
2e8d9c50d4 LEGO: check in for main to temporary branch. (#16409) 2021-07-23 09:18:57 -07:00
Tony Xia
880cfc3b59 clipbaord -> clipboard (#16410) 2021-07-23 08:35:40 -07:00
Tony Xia
c2d45fa01f materialzied -> materialized (#16411) 2021-07-23 08:34:58 -07:00
Tony Xia
c0dd781d77 Fixed a minor typo in comment (#16412) 2021-07-23 08:34:25 -07:00
brian-harris
df5ed2c889 Dev/brih/hotfix status page (#16407)
* add dispose pattern, fix migration status enum

* format strings

* add dispose handler to more events
2021-07-22 22:20:10 -07:00
Charles Gagnon
107023c7d0 Fix update typings pipeline to work for us (#16404)
* Update publish types pipeline

* update yarn tool

* Use fork

* Remove hardcoded

* Sync up with upstream

* update email
2021-07-22 16:47:08 -07:00
Kim Santiago
ce4fa98691 bump sql database projects version to 0.12.0 (#16401) 2021-07-22 15:53:48 -07:00
Charles Gagnon
b2a9074a25 Fix azdata.d.ts linting issues (#16405) 2021-07-22 15:53:18 -07:00
csigs
c082b572d5 LEGO: check in for main to temporary branch. (#16402) 2021-07-22 15:37:44 -07:00
Charles Gagnon
0509f8f0c3 Separate connect and listdatabases call for publish (#16391)
* Separate connect and listdatabases call for publish

* add return value
2021-07-22 15:36:16 -07:00
Charles Gagnon
88d28b7d51 Update azdata.d.ts (#16398) 2021-07-22 13:44:42 -07:00
Alex Ma
df177ec779 added more XLFS (#16399) 2021-07-22 13:32:10 -07:00
nasc17
64d432c8e2 Add aria lables to pod drop down and tables of PG resources (#16229)
* Add aria labels to pod drop down on resource health and tables on overview

* Added roles

* Fix strings

* Added role to table headers

* Added heading levels
2021-07-22 12:07:35 -07:00
csigs
380457122c LEGO: check in for main to temporary branch. (#16395) 2021-07-22 10:25:28 -07:00
csigs
7d6d8dbe96 LEGO: check in for main to temporary branch. (#16394) 2021-07-22 10:25:12 -07:00
chayaverma7
f2dcfacc8c Update package.json (#16396)
Update ADS Version after Release to 1.32.0
2021-07-22 22:01:57 +05:30
Daniel Grajeda
fd954ddcb2 Plotly output resize (#16313)
Allow plotly to resize based on the size of the output itself, not only when the window is resized. This allows cells in Notebook Views to adjust based on  resize.
2021-07-21 21:06:26 -07:00
Daniel Grajeda
c5c7ca019d Notebook Views autodash feature (#16238)
The autodash feature in notebook views creates an initial grid layout for users when a view is created. It is intended to reduce the effort required by the user to start editing their view. Instead of displaying every cell and stacking them vertically like the default notebook layout, we use guidelines to determine which cells are worth displaying and how to arrange them.
2021-07-21 20:46:58 -07:00
Christopher Suh
6d4608dd8b SQL Assessment Database Selector (#16030)
* wip

* wip

* database selector table

* fixed db icon

* wip

* fixed assessment results table

* replaced large query

* fix build error

* updated spacing and other fixes

* removed commented code

* added search bar, fix margins, disable checkbox for offline db

* change width of checkbox column

* changed api to require databases

* Revert "changed api to require databases"

This reverts commit 20fe2d8bd223bae90dfb09609225a1781267a01d.

* removed optional flag from databases parameter

* removed icons on assessment dialog page

* formatting changes, fixed search

* bump STS

* bumped extension version number

* one excludeDbs
2021-07-21 17:58:32 -07:00
csigs
f390c4cbc2 LEGO: check in for main to temporary branch. (#16389) 2021-07-21 17:07:32 -07:00
Rachel Kim
d031211693 Add 'hidden' optional property to DeclarativeTableColumn (#16386)
* add setHideColumns to DeclarativeTableComponent

* fixing tests using declarative table

* replace setHiddenColumns on DeclarativeTableComponent with 'hidden' optional property on DeclarativeTableColumn

* remove unnecessary changes
2021-07-21 16:39:21 -07:00
Charles Gagnon
c1f4c50177 Combine project deploy settings into one (#16383) 2021-07-21 16:15:25 -07:00
Daniel Grajeda
87633faaa4 Allow action bar to be hidden on Plotly tables (#16178)
Allow action bar to be hidden on tables
2021-07-21 15:59:15 -07:00
Chris LaFreniere
dd4e87ed41 Mark unstable book test as unstable (#16388) 2021-07-21 15:18:36 -07:00
nasc17
b472539646 Switch withProperties to be withProps (#16380)
* Transition to withProps in arc

* Transition to withProps inputbox

* Transition to withProps in checkbox

* Transition to withProps text

* Transition to withProps in declarative table

* Transition to withProps hyperlink

* Transition to withProps in button

* Transition to withProps radiobutton

* Transition to withProps in input

* Transition to withProps button

* Transition to withProps in text

* Transition to withProps image

* Transition to withProps declare table

* Transition to withProps in table

* Transition to withProps radio button

* Transition to withProps in image

* Transition to withProps radio button

* Transition to withProps in commit

* Transition to withProps div cont

* Transition to withProps in comp

* Transition to withProps radio card

* Transition to withProps in comp icon

* Transition to withProps card

* Transition to withProps list
2021-07-21 11:12:47 -07:00
Justin M
b2a2a48ed6 Increased version for AzureMonitor (#16369) 2021-07-21 10:54:04 -07:00
chayaverma7
a3a06b92e8 Update README.md and changelog.md for release1.31 (#16378)
* Update README.md

* Updated changelog for release 1.31

Co-authored-by: v-srinisa <82409712+v-srinisa@users.noreply.github.com>
2021-07-21 22:59:11 +05:30
nasc17
3b0fff63d4 Switched deprecated data in declarative table to be dataValues (#16381)
* Controller overview datavalues

* Miaa OV datavalues

* data values PG OV

* data values PG OV p2

* data values PG health

* datavalues miaa OV p2
2021-07-21 10:06:27 -07:00
csigs
2bc6a881bd LEGO: check in for main to temporary branch. (#16382) 2021-07-21 09:49:59 -07:00
Charles Gagnon
efa82650f8 List databases for publish quickpick (#16368)
* List databases for publish quickpick

* missed word
2021-07-21 08:36:34 -07:00
csigs
d1892b514f LEGO: check in for main to temporary branch. (#16371) 2021-07-21 00:28:39 -07:00
goyal-anjali
32da4219a9 Fix #15854 refresh button role (#16175)
Fixed #15854 refresh button role

Co-authored-by: Anjali Goyal <anjaligoyal@microsoft.com>
2021-07-20 23:59:32 -07:00
Alex Ma
fb16924f93 added small change to azurecore (#16370) 2021-07-20 17:58:37 -07:00
csigs
94b99c7862 LEGO: check in for main to temporary branch. (#16365) 2021-07-20 16:46:47 -07:00
Justin M
78d905a217 Changed Kernel Name from LogAnalytics to AzureMonitorLogs (#16157)
* Changed Kernel Name from LogAnalytics to AzureMonitorLogs

* Added spaces to Azure Monitor Notebook Kernel Alias
2021-07-20 15:37:13 -07:00
Justin M
be3d966cf0 Fixed container label for Azure Monitor Logs in Azure Viewlet. (#16156) 2021-07-20 15:36:32 -07:00
Justin M
ba6359e1ff Fixed actions in scripting.contribution.ts for Azure Monitor (#16158) 2021-07-20 15:36:13 -07:00
Alex Ma
43cf19e316 Update for langpacks from 1.31 (#16363)
* update for langpacks from 1.31

* changed requirement number

* update for korean
2021-07-20 14:52:40 -07:00
Aasim Khan
1eb03404ad Updating assessment api response object in SQL Migration (#16334)
* Modifying the get assessments  api to match the new specs

* fixing property name

* Updating sts version
2021-07-20 13:55:58 -07:00
Charles Gagnon
a322c5be9d List connections for sql proj publish quickpick (#16233)
* List connections for sql proj publish quickpick

* cleanup
2021-07-20 13:30:32 -07:00
Alex Ma
d663ec6129 Added replacement for name of Portuguese langpack (#16361)
* added replacement for name of Portuguese langpack

* use toLowerCase instead
2021-07-20 12:44:42 -07:00
Aasim Khan
88fd0cae3b Adding ability to set hyperlinks as buttons in extensibility. (#16319)
* Adding option to treat hyperlinks as buttons

* Adding ext host endpoints for is button property in hyperlinks

* Removing isButton flag and plugging in aria role in the hyperlink component

* Removing attribute null check
2021-07-20 11:29:38 -07:00
Alex Ma
3e2bf7b9fa small update to arc and sql (#16358) 2021-07-20 10:34:33 -07:00
Aasim Khan
3ce6f9e78c adding cpu count and ram to server info (#16109) 2021-07-20 09:56:52 -07:00
nasc17
efaf39f96a Changed add/drop , load/unload. Fix unload button (#16316) 2021-07-20 09:49:36 -07:00
csigs
7c82d0291e LEGO: check in for main to temporary branch. (#16355) 2021-07-20 09:41:59 -07:00
csigs
86c2547b22 LEGO: check in for main to temporary branch. (#16354) 2021-07-20 09:41:44 -07:00
csigs
4a51cb2020 LEGO: check in for main to temporary branch. (#16353) 2021-07-20 09:41:33 -07:00
csigs
260fdac944 LEGO: check in for main to temporary branch. (#16352) 2021-07-20 09:41:24 -07:00
csigs
85082dee75 LEGO: check in for main to temporary branch. (#16351) 2021-07-20 09:41:14 -07:00
csigs
a9b338b5c4 LEGO: check in for main to temporary branch. (#16350) 2021-07-20 09:40:51 -07:00
csigs
f92aa1ead8 LEGO: check in for main to temporary branch. (#16349) 2021-07-20 09:40:41 -07:00
csigs
d4e367e4f9 LEGO: check in for main to temporary branch. (#16348) 2021-07-20 09:40:16 -07:00
csigs
57df7e706f LEGO: check in for main to temporary branch. (#16347) 2021-07-20 09:40:04 -07:00
csigs
b58c19684f LEGO: check in for main to temporary branch. (#16346) 2021-07-20 09:39:52 -07:00
csigs
d0c7028d97 LEGO: check in for main to temporary branch. (#16345) 2021-07-20 09:39:36 -07:00
csigs
dc471faa7a LEGO: check in for main to temporary branch. (#16344) 2021-07-20 09:38:31 -07:00
Charles Gagnon
4aaa7eae29 Print out found elements during smoke tests (#16336) 2021-07-20 07:14:41 -07:00
Alan Ren
1000e97091 add toggle button for properties container (#16335) 2021-07-19 23:36:52 -07:00
csigs
9b31e7beac LEGO: check in for main to temporary branch. (#16343) 2021-07-19 22:28:51 -07:00
csigs
f0b158edda LEGO: check in for main to temporary branch. (#16342) 2021-07-19 22:28:43 -07:00
csigs
9f29efba85 LEGO: check in for main to temporary branch. (#16341) 2021-07-19 22:28:33 -07:00
csigs
de0719d91a LEGO: check in for main to temporary branch. (#16340) 2021-07-19 22:28:27 -07:00
csigs
b9041b0afe LEGO: check in for main to temporary branch. (#16339) 2021-07-19 22:28:20 -07:00
csigs
adb0ec3cab LEGO: check in for main to temporary branch. (#16338) 2021-07-19 22:28:09 -07:00
Charles Gagnon
3eefc70cbe Add headingLevel to textComponent (#16320)
* Add headingLevel to textComponent

* fixes

* comment

* Add valid heading level check

* change check

* Heading level type

* one more
2021-07-19 19:32:39 -07:00
csigs
a7c6a98ad9 LEGO: check in for main to temporary branch. (#16331) 2021-07-19 14:41:48 -07:00
csigs
87f5f4edfc LEGO: check in for main to temporary branch. (#16330) 2021-07-19 14:41:42 -07:00
csigs
08d6b71929 LEGO: check in for main to temporary branch. (#16329) 2021-07-19 14:41:35 -07:00
csigs
aba576dd2f LEGO: check in for main to temporary branch. (#16328) 2021-07-19 14:41:30 -07:00
csigs
2c2e2bb984 LEGO: check in for main to temporary branch. (#16327) 2021-07-19 14:41:23 -07:00
csigs
07567d2514 LEGO: check in for main to temporary branch. (#16326) 2021-07-19 14:41:17 -07:00
Alan Ren
9cbcf9e2c6 update badge colors (#16252) 2021-07-19 13:26:07 -07:00
Alex Ma
e0cad0231d Update to Localized XLFS (#16312)
* Update for German Resources

* added strings for Spanish XLFs

* added french resource xlfs

* massive commit on 7/18/21

* another update to extensions and sql
2021-07-19 13:22:07 -07:00
Charles Gagnon
379c60dd27 Reduce the number of onUpdate events fired for dialog buttons (#16311) 2021-07-19 13:20:36 -07:00
Alan Ren
eaba5679d4 fix the icon size issue of info button (#16318) 2021-07-19 12:35:11 -07:00
Alex Ma
fdaf29ccb4 small string to arc added (#16314) 2021-07-19 09:52:23 -07:00
nasc17
fa21781df2 Handle if no extensions listed in PG spec (#16194)
* Handle if not extensions listed in spec, focus buttons

* Set extensions to optional

* Added optional field to settings and scheduling.roles
2021-07-19 09:37:04 -07:00
csigs
f978331e7b LEGO: check in for main to temporary branch. (#16310) 2021-07-19 08:34:37 -07:00
csigs
8aedeab9d4 LEGO: check in for main to temporary branch. (#16309) 2021-07-19 08:34:24 -07:00
csigs
669987ccf3 LEGO: check in for main to temporary branch. (#16308) 2021-07-19 08:34:13 -07:00
csigs
2984244377 LEGO: check in for main to temporary branch. (#16305) 2021-07-18 20:37:44 -07:00
csigs
fbb17c047c LEGO: check in for main to temporary branch. (#16304) 2021-07-18 20:37:35 -07:00
csigs
36e1aeb43d LEGO: check in for main to temporary branch. (#16303) 2021-07-18 20:37:17 -07:00
csigs
62d86163c9 LEGO: check in for main to temporary branch. (#16302) 2021-07-18 20:37:08 -07:00
csigs
5171c37b6a LEGO: check in for main to temporary branch. (#16301) 2021-07-18 20:36:59 -07:00
csigs
05f1f84872 LEGO: check in for main to temporary branch. (#16296) 2021-07-18 17:02:16 -07:00
csigs
3d8fb454c0 LEGO: check in for main to temporary branch. (#16300) 2021-07-18 17:01:32 -07:00
csigs
430e2c9916 LEGO: check in for main to temporary branch. (#16299) 2021-07-18 17:01:27 -07:00
csigs
e026b743ab LEGO: check in for main to temporary branch. (#16298) 2021-07-18 17:01:23 -07:00
csigs
c170fdfc68 LEGO: check in for main to temporary branch. (#16297) 2021-07-18 17:01:18 -07:00
csigs
d0ecb292f7 LEGO: check in for main to temporary branch. (#16295) 2021-07-18 17:01:09 -07:00
Alex Ma
7d3fa81d3a added more migration and profiler core strings (#16293) 2021-07-18 10:18:52 -07:00
csigs
c1e365bdc8 LEGO: check in for main to temporary branch. (#16292) 2021-07-18 09:16:25 -07:00
csigs
c6f8f6ffd0 LEGO: check in for main to temporary branch. (#16291) 2021-07-18 09:16:11 -07:00
csigs
43e9f6da8c LEGO: check in for main to temporary branch. (#16290) 2021-07-18 09:15:57 -07:00
csigs
5fb7f9e452 LEGO: check in for main to temporary branch. (#16289) 2021-07-18 09:13:58 -07:00
csigs
64ea6c569a LEGO: check in for main to temporary branch. (#16288) 2021-07-18 09:13:42 -07:00
csigs
efdf38dbc4 LEGO: check in for main to temporary branch. (#16287) 2021-07-18 09:13:31 -07:00
csigs
1fe6939f92 LEGO: check in for main to temporary branch. (#16286) 2021-07-18 09:13:10 -07:00
csigs
8332fc6b1d LEGO: check in for main to temporary branch. (#16285) 2021-07-18 09:12:57 -07:00
csigs
7669ad0180 LEGO: check in for main to temporary branch. (#16284) 2021-07-18 09:12:43 -07:00
csigs
e6fd98caa8 LEGO: check in for main to temporary branch. (#16283) 2021-07-18 09:11:29 -07:00
csigs
814f5bea3a LEGO: check in for main to temporary branch. (#16282) 2021-07-18 09:11:12 -07:00
csigs
2e81300f9e LEGO: check in for main to temporary branch. (#16281) 2021-07-18 09:10:45 -07:00
csigs
eed495ef64 LEGO: check in for main to temporary branch. (#16280) 2021-07-18 09:10:24 -07:00
csigs
105636dda6 LEGO: check in for main to temporary branch. (#16279) 2021-07-18 09:10:04 -07:00
csigs
f4ef8d1374 LEGO: check in for main to temporary branch. (#16278) 2021-07-18 09:09:28 -07:00
csigs
01a594ea83 LEGO: check in for main to temporary branch. (#16277) 2021-07-18 09:08:34 -07:00
csigs
f0f6aee89b LEGO: check in for main to temporary branch. (#16276) 2021-07-18 09:08:12 -07:00
csigs
36286f7df6 LEGO: check in for main to temporary branch. (#16275) 2021-07-18 09:07:55 -07:00
csigs
ed5a2ee90d LEGO: check in for main to temporary branch. (#16272) 2021-07-18 09:03:59 -07:00
csigs
0483c5c8b2 LEGO: check in for main to temporary branch. (#16271)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 09:01:58 -07:00
csigs
7f614b0fde LEGO: check in for main to temporary branch. (#16270)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 09:00:24 -07:00
csigs
7c5de73fde LEGO: check in for main to temporary branch. (#16269)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 08:58:53 -07:00
csigs
e499fd9e80 LEGO: check in for main to temporary branch. (#16268)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 08:57:09 -07:00
csigs
a8e4d01ff0 LEGO: check in for main to temporary branch. (#16267)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 08:54:58 -07:00
csigs
ba0261eb98 LEGO: check in for main to temporary branch. (#16266)
Co-authored-by: Alex Ma <alma1@microsoft.com>
2021-07-18 08:52:35 -07:00
csigs
dbf15745d0 LEGO: check in for main to temporary branch. (#16265) 2021-07-18 08:39:52 -07:00
csigs
6e4c7a1075 LEGO: check in for main to temporary branch. (#16264) 2021-07-18 08:38:46 -07:00
csigs
c85ad7b7d5 LEGO: check in for main to temporary branch. (#16263) 2021-07-18 08:38:20 -07:00
csigs
b705e1ca61 LEGO: check in for main to temporary branch. (#16262) 2021-07-18 08:37:17 -07:00
csigs
bc1bf60a98 LEGO: check in for main to temporary branch. (#16261) 2021-07-18 08:36:49 -07:00
csigs
6837b4b801 LEGO: check in for main to temporary branch. (#16260) 2021-07-18 08:36:13 -07:00
csigs
f127b7d94a LEGO: check in for main to temporary branch. (#16258) 2021-07-18 08:35:55 -07:00
csigs
d1d9795965 LEGO: check in for main to temporary branch. (#16257) 2021-07-18 08:35:33 -07:00
csigs
83300acb38 LEGO: check in for main to temporary branch. (#16256) 2021-07-18 08:35:15 -07:00
csigs
24b45a9baf LEGO: check in for main to temporary branch. (#16255) 2021-07-18 08:34:49 -07:00
csigs
4e67aa86d8 LEGO: check in for main to temporary branch. (#16254) 2021-07-18 08:34:29 -07:00
csigs
0dbde9e9b1 LEGO: check in for main to temporary branch. (#16253) 2021-07-18 08:34:00 -07:00
Alan Ren
712633fadd set the focus back to table after sort/filter (#16250) 2021-07-17 20:58:51 -07:00
csigs
7a419426ad LEGO: Merge pull request 16273
LEGO: Merge pull request 16273
2021-07-17 14:45:55 -07:00
Barbara Valdez
f17689319c Revert to previous markdown renderer (#16246)
* Add marked files under sql/base/common
2021-07-16 17:51:53 -07:00
Alan Ren
ee0896ea5d use modal confirmation dialog (#16247) 2021-07-16 16:40:13 -07:00
Aasim Khan
acea03ea61 converting toast to modal dialog to grab focus (#16237) 2021-07-16 16:29:32 -07:00
csigs
d88c49702a LEGO: check in for main to temporary branch. (#16244) 2021-07-16 16:04:55 -07:00
csigs
5e29c936d7 LEGO: check in for main to temporary branch. (#16243) 2021-07-16 16:04:44 -07:00
csigs
0474a5ca9e LEGO: check in for main to temporary branch. (#16242) 2021-07-16 16:04:22 -07:00
csigs
d09ba43d15 LEGO: check in for main to temporary branch. (#16241) 2021-07-16 16:04:04 -07:00
csigs
164b100421 LEGO: check in for main to temporary branch. (#16240) 2021-07-16 16:03:55 -07:00
csigs
2d2c568609 LEGO: check in for main to temporary branch. (#16239) 2021-07-16 16:03:43 -07:00
Alex Ma
ff6e377477 Small update to XLFs and small change to extension.webpack (#16232) 2021-07-16 13:43:14 -07:00
Vasu Bhog
63e97caa94 Fix Windows WYSIWYG linking issue (switching to splitview and resolving links in WYSIWYG) (#16133)
* fix relative links not correctly formatted due to marked js

* logic in one place
2021-07-16 13:27:45 -07:00
Alan Ren
eed792f3db fix keyboard focus issues (#16206) 2021-07-16 13:27:15 -07:00
Sakshi Sharma
3847271e67 Update Schema Compare dialog to use folder icon (#16208)
* Update Schema Compare dialog to use folder icon

* Address comment
2021-07-16 12:21:59 -07:00
Alex Ma
f53a06a403 added delete zip files code (#16230)
* added delete zip files code

* removed space
2021-07-16 12:21:23 -07:00
brian-harris
78a144b5ca sql-migration: update migration status page latest design (#16099)
* add migrations status context menu and commands

* add migration status images

* remove test code, add account validation

* fix command registration to occure once

* fix typo
2021-07-16 12:20:19 -07:00
Charles Gagnon
3f047ae15a Disable add remote book test (#16231) 2021-07-16 12:14:07 -07:00
Barbara Valdez
6df69f525c Add conditional operator before checking length (#16180)
* add optional op to sections

* remove check for undefined
2021-07-16 11:38:18 -07:00
Alex Ma
669623a228 Removed async copy in rename function (#16209)
* Removed async copy in rename function

* added comments and removed async
2021-07-16 11:01:31 -07:00
csigs
96efba004d LEGO: check in for main to temporary branch. (#16222) 2021-07-16 08:49:16 -07:00
csigs
dc0eb133f9 LEGO: check in for main to temporary branch. (#16221) 2021-07-16 08:49:07 -07:00
csigs
ac2198c7d3 LEGO: check in for main to temporary branch. (#16220) 2021-07-16 08:48:22 -07:00
csigs
49519232ba LEGO: check in for main to temporary branch. (#16219) 2021-07-16 08:48:08 -07:00
csigs
8c9cc03c89 LEGO: check in for main to temporary branch. (#16218) 2021-07-16 08:47:55 -07:00
csigs
c4830d9efb LEGO: check in for main to temporary branch. (#16217) 2021-07-16 08:47:43 -07:00
csigs
5923793f0c LEGO: check in for main to temporary branch. (#16215) 2021-07-16 08:46:41 -07:00
csigs
126a0383ce LEGO: check in for main to temporary branch. (#16214) 2021-07-16 08:46:20 -07:00
csigs
c70d5b957e LEGO: check in for main to temporary branch. (#16213) 2021-07-16 08:46:03 -07:00
csigs
245156a66c LEGO: check in for main to temporary branch. (#16212) 2021-07-16 08:45:44 -07:00
csigs
ebb02de1c4 LEGO: check in for main to temporary branch. (#16211) 2021-07-16 08:45:30 -07:00
csigs
f662d480e7 LEGO: check in for main to temporary branch. (#16210) 2021-07-16 08:45:11 -07:00
Alan Ren
49dbce5171 make button not focusable when disabled (#16185)
* make button not focusable when disabled

* update comment

* add carbon edit tag
2021-07-15 15:10:54 -07:00
Aasim Khan
b3d8e522f7 Updating import extension (#16190) 2021-07-15 14:55:40 -07:00
csigs
4c421e0a38 LEGO: check in for main to temporary branch. (#16200) 2021-07-15 14:38:32 -07:00
csigs
4275fe89a7 LEGO: check in for main to temporary branch. (#16199) 2021-07-15 14:38:07 -07:00
csigs
9c3d88b64a LEGO: check in for main to temporary branch. (#16198) 2021-07-15 14:37:37 -07:00
csigs
0bf574c227 LEGO: check in for main to temporary branch. (#16197) 2021-07-15 14:37:15 -07:00
Kim Santiago
5059c94adc Add target platform dropdown to new project dialog (#16091)
* add target platform as an option in create project api

* move constant

* WIP

* show versions in dialog and create project with selected version

* validate version

* add error messages

* add test

* change version to target platform

* cleanup

* more cleanup

* use withProps
2021-07-15 13:43:48 -07:00
Charles Gagnon
2a74ad4190 Remove second prompt for azdata install (#16182) 2021-07-15 13:03:51 -07:00
brian-harris
b45f3e7218 add aria-label / name to image only buttons (#16124)
* add aria-label / name to image only buttons

* add correct aria label to image buttons

* add key number to button aria-label, title and action messages to differentient keys
2021-07-15 12:08:59 -07:00
Udeesha Gautam
0ff8786885 Fixing the error order for Project dialog validation (#16141)
* Fixing the error order for Project dialog

* Taking PR review comments and making the change for open project also
2021-07-15 11:15:44 -07:00
Charles Gagnon
1e81b6f054 Fix component unregistering from parent (#16166) 2021-07-15 11:09:56 -07:00
Alex Ma
785839baac changes to sqldatabaseprojects (#16179) 2021-07-15 10:12:40 -07:00
Charles Gagnon
d03fbbc066 Read publish profile using DacFX (#16110)
* Read publish profile using DacFX in VS Code

* Fixes

* complete promise on hide

* comment
2021-07-15 08:53:43 -07:00
csigs
06da33bb3b LEGO: check in for main to temporary branch. (#16177) 2021-07-15 08:41:41 -07:00
csigs
202036ca47 LEGO: check in for main to temporary branch. (#16176) 2021-07-15 08:41:01 -07:00
csigs
8530bf214e LEGO: check in for main to temporary branch. (#16172) 2021-07-15 08:39:41 -07:00
csigs
8faf115329 LEGO: check in for main to temporary branch. (#16171) 2021-07-15 08:39:20 -07:00
csigs
1bd1c64b08 LEGO: check in for main to temporary branch. (#16170) 2021-07-15 08:39:07 -07:00
csigs
68e0f86120 LEGO: check in for main to temporary branch. (#16169) 2021-07-15 08:38:47 -07:00
csigs
43183c90a1 LEGO: check in for main to temporary branch. (#16168) 2021-07-15 08:38:31 -07:00
csigs
b497063482 LEGO: check in for main to temporary branch. (#16167) 2021-07-15 08:38:02 -07:00
Lucy Zhang
63d4cc0e80 don't show python upgrade prompt multiple times (#16080) 2021-07-15 06:50:46 -07:00
csigs
351a55121d LEGO: check in for main to temporary branch. (#16164) 2021-07-14 23:51:13 -07:00
csigs
f8583f53c5 LEGO: check in for main to temporary branch. (#16163) 2021-07-14 23:50:57 -07:00
csigs
39aefa7e29 LEGO: check in for main to temporary branch. (#16162) 2021-07-14 23:50:43 -07:00
csigs
a4ffa64918 LEGO: check in for main to temporary branch. (#16161) 2021-07-14 23:50:24 -07:00
csigs
181cad5b75 LEGO: check in for main to temporary branch. (#16160) 2021-07-14 23:50:09 -07:00
csigs
4fd890e651 LEGO: check in for main to temporary branch. (#16165) 2021-07-14 23:49:31 -07:00
Alex Ma
b5411f0f6f update to arc extension resources (#16154) 2021-07-14 18:36:11 -07:00
Justin M
5496e9ac33 Added missing properties in kustoTreeDataProvider and azuremonitorTreeDataProvider (#16153) 2021-07-14 17:20:12 -07:00
nasc17
5a428b83ae Alter table to be multi-select grid for dropping PG extensions (#16143)
* Check if no extensions currently present before adding

* Added multiselect option for dropping extensions

* Loc file changes
2021-07-14 15:51:42 -07:00
Alex Ma
1308878650 added resource deployment string. (#16151) 2021-07-14 15:37:56 -07:00
csigs
c85e164836 LEGO: check in for main to temporary branch. (#16150) 2021-07-14 14:56:01 -07:00
csigs
c72a194bc3 LEGO: check in for main to temporary branch. (#16149) 2021-07-14 14:54:24 -07:00
csigs
c131c252cd LEGO: check in for main to temporary branch. (#16148) 2021-07-14 14:53:49 -07:00
csigs
72c0d30517 LEGO: check in for main to temporary branch. (#16147) 2021-07-14 14:53:32 -07:00
csigs
ba87c8e245 LEGO: check in for main to temporary branch. (#16146) 2021-07-14 14:53:03 -07:00
csigs
11f5ddf062 LEGO: check in for main to temporary branch. (#16145) 2021-07-14 14:52:29 -07:00
Charles Gagnon
8faa0cf0e2 Update compile pipeline and fix eslint (#16129)
* Update pipelines

* eslint

* Fix layering

* update tsec exemption
2021-07-14 14:43:23 -07:00
Alex Ma
f7bf914bcb added on end function to end of pipe (#16144) 2021-07-14 14:36:52 -07:00
csigs
d5f52ba53d LEGO: check in for main to temporary branch. (#16139) 2021-07-14 13:24:06 -07:00
csigs
f059612bd0 LEGO: check in for main to temporary branch. (#16138) 2021-07-14 13:23:10 -07:00
csigs
52c21a7885 LEGO: check in for main to temporary branch. (#16137) 2021-07-14 13:22:44 -07:00
csigs
743fd9f97c LEGO: check in for main to temporary branch. (#16136) 2021-07-14 13:19:13 -07:00
csigs
e7f614ebf7 LEGO: check in for main to temporary branch. (#16135) 2021-07-14 13:18:21 -07:00
csigs
bd1633c04c LEGO: check in for main to temporary branch. (#16134) 2021-07-14 13:16:00 -07:00
Aditya Bist
5524a3659c Secondary actions (#16122)
* data menu shows up

* clean up code

* remove dead code

* string literal

* add menu item instead

* remove unused code
2021-07-14 12:35:10 -07:00
Alex Ma
2785538afb Update for sql, azurecore, and notebook (#16130) 2021-07-14 10:46:22 -07:00
csigs
0438e9cd41 LEGO: check in for main to temporary branch. (#16127) 2021-07-14 08:44:08 -07:00
csigs
601a4aaed1 LEGO: check in for main to temporary branch. (#16123) 2021-07-13 23:45:55 -07:00
Charles Gagnon
a0f46fec65 Add openQueryDocument API (#16117)
* Add openQueryDocument API

* Remove open call

* Change try name
2021-07-13 17:56:35 -07:00
Alex Ma
a7311764be Fixed tabs to match older locproject files (#16114) 2021-07-13 17:36:28 -07:00
csigs
7fc69cc4d5 LEGO: check in for main to temporary branch. (#16115) 2021-07-13 14:57:40 -07:00
Alex Ma
32a5ec3cd0 Update for existing localized XLFs. (#16107)
* update to existing german extensions

* Update for existing Spanish Resources

* Added updates to existing French Resources

* update for Italian Resources

* Update for existing Japanese resources

* update for Korean resources

* update for Portuguese resources

* russian resources updated

* update for chinese resources
2021-07-13 14:48:34 -07:00
Kim Santiago
b4ab73a636 bump ADS dependency for sql database projects (#16092) 2021-07-13 11:46:02 -07:00
Justin M
625d4bc4bf Added generic icon for azuremonitor extension (#16104) 2021-07-13 11:32:53 -07:00
Aasim Khan
45d664fea2 Adding blob containers and storage key api to azure core (#16103) 2021-07-13 11:10:08 -07:00
goyal-anjali
cf05dc0016 Developer added (#16101)
Co-authored-by: Anjali Goyal <anjaligoyal@microsoft.com>
2021-07-13 09:44:21 -07:00
Charles Gagnon
5c17529e40 Fix focus element on connect dialog (#16085) 2021-07-12 16:34:06 -07:00
Alan Ren
f5e38482c3 left align the account information (#16086) 2021-07-12 16:10:50 -07:00
Alex Ma
e3dc417df4 Added Sorted Core Strings (#16081)
* added sorted sql xlf and sorting function for i18n

* compiled i18n.js

* fixed space

* simplified i18n.json
2021-07-12 15:58:38 -07:00
Kim Santiago
affe3a838b Add target platform as an option in create project api (#16035)
* add target platform as an option in create project api

* add test

* move constant
2021-07-12 15:14:33 -07:00
Charles Gagnon
9fc2cff654 Fix opening new untitled query (#16078) 2021-07-12 14:37:53 -07:00
Karl Burtram
32ba55b7ed Remove duplicate Getting Started menu (#16077) 2021-07-12 14:03:52 -07:00
Aasim Khan
8b383294f7 Fixing some assessment styling (#16050)
* Fixing some assessment styling

* Adding styling capabilities to loading component.

* Changing assessment loader height from px to %

* using margin-top instead of height to align loader
2021-07-12 12:51:20 -07:00
Justin M
2b8ae507aa Set azuremonitor extension ID to 83 (#16058) 2021-07-12 10:46:04 -07:00
Barbara Valdez
f7fc145b0c Set notebook path before moving. (#16055)
* set the original path when moving a single notebook

* save original file path before attempting to move in case of error_
)
2021-07-12 10:32:15 -07:00
Alan Ren
6baf2ee982 make dropdown arrow larger to meet accessibility requirement (#127839) (#16073)
* make dropdown arrow bold

* Update style.css
2021-07-12 10:23:44 -07:00
Charles Gagnon
ba20cdb885 Add accessibility information for MIAA deployment link (#16064)
* Add accessibility information for MIAA deployment link

* optional
2021-07-12 10:08:59 -07:00
Alex Ma
d8433ddbf4 small update to english XLFs (#16062) 2021-07-12 09:53:47 -07:00
Alex Ma
1c1da18f45 fix for errors in LCL files (#16072)
* fix for errors in LCL files

* remove newline
2021-07-12 09:49:24 -07:00
1110 changed files with 368648 additions and 111799 deletions

View File

@@ -12,6 +12,8 @@
**/vscode-api-tests/testWorkspace2/**
**/extensions/**/out/**
**/extensions/**/build/**
**/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts
**/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts
**/extensions/markdown-language-features/media/**
**/extensions/markdown-language-features/notebook-out/**
**/extensions/typescript-basics/test/colorize-fixtures/**

View File

@@ -581,7 +581,9 @@
"iconv-lite-umd",
"jschardet",
"@angular/*",
"rxjs/**"
"rxjs/**",
"sanitize-html",
"ansi_up"
]
},
{
@@ -738,12 +740,12 @@
"rxjs/**",
"ng2-charts",
"chart.js",
"plotly.js-dist-min",
"plotly.js",
"angular2-grid",
"html-to-image",
"html-query-plan",
"turndown",
"gridstack",
"gridstack/**",
"mark.js",
"vscode-textmate",
"vscode-oniguruma",

9
.github/CODEOWNERS vendored
View File

@@ -3,10 +3,12 @@
# Syntax can be found here: https://docs.github.com/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
/extensions/admin-tool-ext-win @Charles-Gagnon
/extensions/arc/ @Charles-Gagnon
/extensions/azdata/ @Charles-Gagnon
/extensions/arc/ @Charles-Gagnon @swells @candiceye
/extensions/azcli/ @Charles-Gagnon @swells @candiceye
/extensions/azdata/ @Charles-Gagnon @swells @candiceye
/extensions/big-data-cluster/ @Charles-Gagnon
/extensions/dacpac/ @kisantia
/extensions/notebook @azure-data-studio-notebook-devs
/extensions/query-history/ @Charles-Gagnon
/extensions/resource-deployment/ @Charles-Gagnon
/extensions/schema-compare/ @kisantia
@@ -14,3 +16,6 @@
/extensions/mssql/config.json @Charles-Gagnon @alanrenmsft @kburtram
/src/sql/*.d.ts @alanrenmsft @Charles-Gagnon
/src/sql/workbench/browser/modelComponents @Charles-Gagnon @alanrenmsft
/src/sql/workbench/api @Charles-Gagnon @alanrenmsft
/src/sql/**/notebook @azure-data-studio-notebook-devs

View File

@@ -204,6 +204,14 @@ jobs:
- name: Compile and Download
run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
# This is required for keytar unittests, otherwise we hit
# https://github.com/atom/node-keytar/issues/76
- name: Create temporary keychain
run: |
security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
security default-keychain -s $RUNNER_TEMP/buildagent.keychain
security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
- name: Run Unit Tests (Electron)
run: DISPLAY=:10 ./scripts/test.sh

View File

@@ -1,11 +1,29 @@
# Change Log
## Version 1.31.1
* Release date: July 29, 2021
* Release status: General Availability
## Hotfix Release
- Fix for [#16436 Database Connection Toolbar Missing](https://github.com/microsoft/azuredatastudio/issues/16436)
## Version 1.31.0
* Release date: July 21, 2021
* Release status: General Availability
* New Notebook Features:
* WYSIWYG link improvements
* Extension Updates:
* Import
* SandDance
* SQL Database Projects
* Bug Fixes
* Accessibility bug fixes
## Version 1.30.0
* Release date: June 17, 2021
* Release status: General Availability
* New Notebook Features:
* Show book's notebook TOC title in pinned notebooks view
* Add new book icon
* Add new book icon
* Update Python to 3.8.10
* Query Editor Features:
* Added filtering/sorting feature for query result grid in query editor and notebook, the feature can be invoked from the column headers. Note that this feature is only available when you enable the preview features
@@ -14,7 +32,7 @@
* SQL Database Projects
* Machine Learning
* Bug Fixes
* Fix WYSIWYG Table cell adding new line in table cell
* Fix WYSIWYG Table cell adding new line in table cell
## Version 1.29.0
* Release date: May 19, 2021

View File

@@ -131,10 +131,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](LICENSE.txt).
[win-user]: https://go.microsoft.com/fwlink/?linkid=2165736
[win-system]: https://go.microsoft.com/fwlink/?linkid=2165737
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2165838
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2165942
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2165841
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2165842
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2165738
[win-user]: https://go.microsoft.com/fwlink/?linkid=2168181
[win-system]: https://go.microsoft.com/fwlink/?linkid=2168180
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2168436
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2168435
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2168338
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2168271
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2168339

View File

@@ -30,6 +30,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
getmac: https://github.com/bevry/getmac
graceful-fs: https://github.com/isaacs/node-graceful-fs
gridstack: https://github.com/gridstack/gridstack.js
html-to-image: https://github.com/bubkoo/html-to-image
html-query-plan: https://github.com/JustinPealing/html-query-plan
http-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
https-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
@@ -520,6 +521,32 @@ SOFTWARE.
=========================================
END OF gridstack NOTICES AND INFORMATION
%% html-to-image NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (c) 2017 W.Y.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
END OF html-to-image NOTICES AND INFORMATION
%% html-query-plan NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)

View File

@@ -171,7 +171,7 @@ steps:
done
displayName: Archive Logs
continueOnError: true
condition: succeededOrFailed()
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
- script: |
set -e

View File

@@ -11,25 +11,25 @@ steps:
inputs:
versionSpec: "14.x"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
inputs:
versionSpec: "1.x"
- bash: |
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
CHANNEL="G1C14HJ2F"
# - bash: |
# TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
# CHANNEL="G1C14HJ2F"
if [ "$TAG_VERSION" == "1.999.0" ]; then
MESSAGE="<!here>. Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local."
# if [ "$TAG_VERSION" == "1.999.0" ]; then
# MESSAGE="<!here>. Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local."
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
-H 'Content-type: application/json; charset=utf-8' \
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
https://slack.com/api/chat.postMessage
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
# -H 'Content-type: application/json; charset=utf-8' \
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
# https://slack.com/api/chat.postMessage
exit 1
fi
displayName: Check 1.999.0 tag
# exit 1
# fi
# displayName: Check 1.999.0 tag
- bash: |
# Install build dependencies
@@ -37,47 +37,54 @@ steps:
node build/azure-pipelines/publish-types/check-version.js
displayName: Check version
# {{SQL CARBON EDIT}} Modify to fit our own scenario - specifically currently we need to use a fork of the repo since we don't
# have an account with push access to DT
- bash: |
git config --global user.email "vscode@microsoft.com"
git config --global user.name "VSCode"
git config --global user.email "azuredatastudio@microsoft.com"
git config --global user.name "Azure Data Studio"
git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1
git clone https://$(GITHUB_TOKEN)@$(REPO) --depth=1
node build/azure-pipelines/publish-types/update-types.js
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
cd DefinitelyTyped
# Sync up to latest from the DT repo
git remote add upstream https://github.com/DefinitelyTyped/DefinitelyTyped.git
git merge upstream/master
git push origin
git diff --color | cat
git add -A
git status
git checkout -b "vscode-types-$TAG_VERSION"
git commit -m "VS Code $TAG_VERSION Extension API"
git push origin "vscode-types-$TAG_VERSION"
git checkout -b "azdata-types-$TAG_VERSION"
git commit -m "Azure Data Studio $TAG_VERSION Extension API"
git push origin "azdata-types-$TAG_VERSION"
displayName: Push update to DefinitelyTyped
- bash: |
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
CHANNEL="G1C14HJ2F"
# - bash: |
# TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
# CHANNEL="G1C14HJ2F"
MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame champion, please open this link, examine changes and create a PR:"
LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details."
MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
# MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame champion, please open this link, examine changes and create a PR:"
# LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details."
# MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
-H 'Content-type: application/json; charset=utf-8' \
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
https://slack.com/api/chat.postMessage
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
# -H 'Content-type: application/json; charset=utf-8' \
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE"'"}' \
# https://slack.com/api/chat.postMessage
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
-H 'Content-type: application/json; charset=utf-8' \
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \
https://slack.com/api/chat.postMessage
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
# -H 'Content-type: application/json; charset=utf-8' \
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$LINK"'"}' \
# https://slack.com/api/chat.postMessage
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
-H 'Content-type: application/json; charset=utf-8' \
--data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \
https://slack.com/api/chat.postMessage
# curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
# -H 'Content-type: application/json; charset=utf-8' \
# --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \
# https://slack.com/api/chat.postMessage
displayName: Send message on Slack
# displayName: Send message on Slack

View File

@@ -13,11 +13,11 @@ try {
.execSync('git describe --tags `git rev-list --tags --max-count=1`')
.toString()
.trim();
const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vs/vscode.d.ts`;
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts');
const dtsUri = `https://raw.githubusercontent.com/microsoft/azuredatastudio/${tag}/src/sql/azdata.d.ts`; // {{SQL CARBON EDIT}} Use our typings
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/azdata/index.d.ts'); // {{SQL CARBON EDIT}} Use our typings
cp.execSync(`curl ${dtsUri} --output ${outPath}`);
updateDTSFile(outPath, tag);
console.log(`Done updating vscode.d.ts at ${outPath}`);
console.log(`Done updating azdata.d.ts at ${outPath}`); // {{SQL CARBON EDIT}} Use our typings
}
catch (err) {
console.error(err);
@@ -51,21 +51,25 @@ function getNewFileContent(content, tag) {
function getNewFileHeader(tag) {
const [major, minor] = tag.split('.');
const shorttag = `${major}.${minor}`;
// {{SQL CARBON EDIT}} Use our own header
const header = [
`// Type definitions for Visual Studio Code ${shorttag}`,
`// Project: https://github.com/microsoft/vscode`,
`// Definitions by: Visual Studio Code Team, Microsoft <https://github.com/Microsoft>`,
`// Type definitions for Azure Data Studio ${shorttag}`,
`// Project: https://github.com/microsoft/azuredatastudio`,
`// Definitions by: Charles Gagnon <https://github.com/Charles-Gagnon>`,
`// Alan Ren: <https://github.com/alanrenmsft>`,
`// Karl Burtram: <https://github.com/kburtram>`,
`// Ken Van Hyning: <https://github.com/kenvanhyning>`,
`// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`,
``,
`/*---------------------------------------------------------------------------------------------`,
` * Copyright (c) Microsoft Corporation. All rights reserved.`,
` * Licensed under the Source EULA.`,
` * See https://github.com/Microsoft/vscode/blob/main/LICENSE.txt for license information.`,
` * Licensed under the MIT License.`,
` * See https://github.com/Microsoft/azuredatastudio/blob/main/LICENSE.txt for license information.`,
` *--------------------------------------------------------------------------------------------*/`,
``,
`/**`,
` * Type Definition for Visual Studio Code ${shorttag} Extension API`,
` * See https://code.visualstudio.com/api for more information`,
` * Type Definition for Azure Data Studio ${shorttag} Extension API`,
` * See https://docs.microsoft.com/sql/azure-data-studio/extensibility-apis for more information`,
` */`
].join('\n');
return header;

View File

@@ -16,13 +16,13 @@ try {
.toString()
.trim();
const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vs/vscode.d.ts`;
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts');
const dtsUri = `https://raw.githubusercontent.com/microsoft/azuredatastudio/${tag}/src/sql/azdata.d.ts`; // {{SQL CARBON EDIT}} Use our typings
const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/azdata/index.d.ts'); // {{SQL CARBON EDIT}} Use our typings
cp.execSync(`curl ${dtsUri} --output ${outPath}`);
updateDTSFile(outPath, tag);
console.log(`Done updating vscode.d.ts at ${outPath}`);
console.log(`Done updating azdata.d.ts at ${outPath}`); // {{SQL CARBON EDIT}} Use our typings
} catch (err) {
console.error(err);
console.error('Failed to update types');
@@ -63,21 +63,25 @@ function getNewFileHeader(tag: string) {
const [major, minor] = tag.split('.');
const shorttag = `${major}.${minor}`;
// {{SQL CARBON EDIT}} Use our own header
const header = [
`// Type definitions for Visual Studio Code ${shorttag}`,
`// Project: https://github.com/microsoft/vscode`,
`// Definitions by: Visual Studio Code Team, Microsoft <https://github.com/Microsoft>`,
`// Type definitions for Azure Data Studio ${shorttag}`,
`// Project: https://github.com/microsoft/azuredatastudio`,
`// Definitions by: Charles Gagnon <https://github.com/Charles-Gagnon>`,
`// Alan Ren: <https://github.com/alanrenmsft>`,
`// Karl Burtram: <https://github.com/kburtram>`,
`// Ken Van Hyning: <https://github.com/kenvanhyning>`,
`// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`,
``,
`/*---------------------------------------------------------------------------------------------`,
` * Copyright (c) Microsoft Corporation. All rights reserved.`,
` * Licensed under the Source EULA.`,
` * See https://github.com/Microsoft/vscode/blob/main/LICENSE.txt for license information.`,
` * Licensed under the MIT License.`,
` * See https://github.com/Microsoft/azuredatastudio/blob/main/LICENSE.txt for license information.`,
` *--------------------------------------------------------------------------------------------*/`,
``,
`/**`,
` * Type Definition for Visual Studio Code ${shorttag} Extension API`,
` * See https://code.visualstudio.com/api for more information`,
` * Type Definition for Azure Data Studio ${shorttag} Extension API`,
` * See https://docs.microsoft.com/sql/azure-data-studio/extensibility-apis for more information`,
` */`
].join('\n');

View File

@@ -79,19 +79,8 @@ steps:
- script: |
set -e
yarn sqllint
yarn gulp hygiene
yarn strict-vscode
yarn valid-layers-check
displayName: Run hygiene, eslint
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
yarn gulp compile-build
yarn gulp compile-extensions-build
yarn gulp minify-vscode
displayName: Compile
yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check sqllint strict-vscode
displayName: Compile & Hygiene
- script: |
set -e

View File

@@ -15,6 +15,8 @@ const task = require('./lib/task');
const glob = require('glob');
const vsce = require('vsce');
const mkdirp = require('mkdirp');
const rename = require('gulp-rename');
const fs = require('fs');
gulp.task('fmt', () => formatStagedFiles());
const formatFiles = (some) => {
@@ -94,12 +96,14 @@ const root = path.dirname(__dirname);
gulp.task('package-external-extensions', task.series(
task.define('bundle-external-extensions-build', () => ext.packageExternalExtensionsStream().pipe(gulp.dest('.build/external'))),
task.define('create-external-extension-vsix-build', () => {
task.define('create-external-extension-vsix-build', async () => {
const vsixes = glob.sync('.build/external/extensions/*/package.json').map(manifestPath => {
const extensionPath = path.dirname(path.join(root, manifestPath));
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
}).map(element => {
})
.filter(element => ext.vscodeExternalExtensions.indexOf(element.name) === -1) // VS Code external extensions are bundled into ADS so no need to create a normal VSIX for them
.map(element => {
const pkgJson = require(path.join(element.path, 'package.json'));
const vsixDirectory = path.join(root, '.build', 'extensions');
mkdirp.sync(vsixDirectory);
@@ -111,8 +115,46 @@ gulp.task('package-external-extensions', task.series(
useYarn: true
});
});
// Wait for all the initial VSIXes to be completed before making the VS Code ones since we'll be overwriting
// values in the package.json for those.
await Promise.all(vsixes);
return Promise.all(vsixes);
// Go through and find the extensions which build separate versions of themselves for VS Code.
// This is currently a pretty simplistic process, essentially just replacing certain values in
// the package.json. It doesn't handle more complex tasks such as replacing localized strings.
const vscodeVsixes = glob.sync('.build/external/extensions/*/package.vscode.json')
.map(async vscodeManifestRelativePath => {
const vscodeManifestFullPath = path.join(root, vscodeManifestRelativePath);
const packageDir = path.dirname(vscodeManifestFullPath);
const packageManifestPath = path.join(packageDir, 'package.json');
const json = require('gulp-json-editor');
const packageJsonStream = gulp.src(packageManifestPath) // Create stream for the original package.json
.pipe(json(data => { // And now use gulp-json-editor to modify the contents
const updateData = JSON.parse(fs.readFileSync(vscodeManifestFullPath)); // Read in the set of values to replace from package.vscode.json
Object.keys(updateData).forEach(key => {
data[key] = updateData[key];
});
// Remove ADS-only menus. This is a subset of the menus listed in https://github.com/microsoft/azuredatastudio/blob/main/src/vs/workbench/api/common/menusExtensionPoint.ts
// More can be added to the list as needed.
['objectExplorer/item/context', 'dataExplorer/context', 'dashboard/toolbar'].forEach(menu => {
delete data.contributes.menus[menu];
});
return data;
}, { beautify: false }))
.pipe(gulp.dest(packageDir));
await new Promise(resolve => packageJsonStream.on('finish', resolve)); // Wait for the files to finish being updated before packaging
const pkgJson = JSON.parse(fs.readFileSync(packageManifestPath));
const vsixDirectory = path.join(root, '.build', 'extensions');
const packagePath = path.join(vsixDirectory, `${pkgJson.name}-${pkgJson.version}.vsix`);
console.info('Creating vsix for ' + packageDir + ' result:' + packagePath);
return vsce.createVSIX({
cwd: packageDir,
packagePath: packagePath,
useYarn: true
});
});
return Promise.all(vscodeVsixes);
})
));

View File

@@ -112,33 +112,33 @@ gulp.task(optimizeVSCodeTask);
// List of ADS extension XLF files that we want to put into the English resource folder.
const extensionsFilter = filter([
"**/admin-tool-ext-win.xlf",
"**/agent.xlf",
"**/arc.xlf",
"**/asde-deployment.xlf",
"**/azdata.xlf",
"**/azurecore.xlf",
"**/azurehybridtoolkit.xlf",
"**/big-data-cluster.xlf",
"**/cms.xlf",
"**/dacpac.xlf",
"**/data-workspace.xlf",
"**/import.xlf",
"**/kusto.xlf",
"**/machine-learning.xlf",
"**/Microsoft.sqlservernotebook.xlf",
"**/mssql.xlf",
"**/notebook.xlf",
"**/profiler.xlf",
"**/query-history.xlf",
"**/resource-deployment.xlf",
"**/schema-compare.xlf",
"**/server-report.xlf",
"**/sql-assessment.xlf",
"**/sql-database-projects.xlf",
"**/sql-migration.xlf",
"**/xml-language-features.xlf"
])
'**/admin-tool-ext-win.xlf',
'**/agent.xlf',
'**/arc.xlf',
'**/asde-deployment.xlf',
'**/azdata.xlf',
'**/azurecore.xlf',
'**/azurehybridtoolkit.xlf',
'**/big-data-cluster.xlf',
'**/cms.xlf',
'**/dacpac.xlf',
'**/data-workspace.xlf',
'**/import.xlf',
'**/kusto.xlf',
'**/machine-learning.xlf',
'**/Microsoft.sqlservernotebook.xlf',
'**/mssql.xlf',
'**/notebook.xlf',
'**/profiler.xlf',
'**/query-history.xlf',
'**/resource-deployment.xlf',
'**/schema-compare.xlf',
'**/server-report.xlf',
'**/sql-assessment.xlf',
'**/sql-database-projects.xlf',
'**/sql-migration.xlf',
'**/xml-language-features.xlf'
]);
// Copy ADS extension XLFs into English resource folder.
const importExtensionsTask = task.define('import-extensions-xlfs', function () {
@@ -149,7 +149,7 @@ const importExtensionsTask = task.define('import-extensions-xlfs', function () {
)
.pipe(vfs.dest(`./resources/xlf/en`));
});
gulp.task(importExtensionsTask)
gulp.task(importExtensionsTask);
// {{SQL CARBON EDIT}} end
const sourceMappingURLBase = `https://sqlopsbuilds.blob.core.windows.net/sourcemaps/${commit}`;
@@ -486,7 +486,7 @@ const vscodeTranslationsExport = task.define(
'vscode-translations-export',
task.series(
compileBuildTask,
compileLocalizationExtensionsBuildTask, // {{SQL CARBON EDIT}} now include all extensions in ADS, not just a subset. (replaces "compileExtensionsBuildTask" here).
compileLocalizationExtensionsBuildTask, // {{SQL CARBON EDIT}} now include all extensions in ADS, not just a subset. (replaces 'compileExtensionsBuildTask' here).
optimizeVSCodeTask,
function () {
const pathToMetadata = './out-vscode/nls.metadata.json';
@@ -501,7 +501,7 @@ const vscodeTranslationsExport = task.define(
}
)
);
gulp.task(vscodeTranslationsExport)
gulp.task(vscodeTranslationsExport);
// {{SQL CARBON EDIT}} Localization gulp task, runs vscodeTranslationsExport and imports a subset of the generated XLFs into the folder.
gulp.task(task.define(

View File

@@ -4,7 +4,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.translatePackageJSON = exports.packageRebuildExtensionsStream = exports.cleanRebuildExtensions = exports.packageExternalExtensionsStream = exports.scanBuiltinExtensions = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = exports.fromLocalNormal = exports.fromLocal = void 0;
exports.translatePackageJSON = exports.packageRebuildExtensionsStream = exports.cleanRebuildExtensions = exports.packageExternalExtensionsStream = exports.scanBuiltinExtensions = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.vscodeExternalExtensions = exports.fromMarketplace = exports.fromLocalNormal = exports.fromLocal = void 0;
const es = require("event-stream");
const fs = require("fs");
const glob = require("glob");
@@ -232,6 +232,12 @@ const externalExtensions = [
'sql-database-projects',
'sql-migration'
];
/**
* Extensions that are built into ADS but should be packaged externally as well for VS Code.
*/
exports.vscodeExternalExtensions = [
'data-workspace'
];
// extensions that require a rebuild since they have native parts
const rebuildExtensions = [
'big-data-cluster',
@@ -348,7 +354,7 @@ function packageExternalExtensionsStream() {
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
})
.filter(({ name }) => externalExtensions.indexOf(name) >= 0);
.filter(({ name }) => externalExtensions.indexOf(name) >= 0 || exports.vscodeExternalExtensions.indexOf(name) >= 0);
const builtExtensions = extenalExtensionDescriptions.map(extension => {
return fromLocal(extension.path, false)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));

View File

@@ -268,6 +268,13 @@ const externalExtensions = [
'sql-migration'
];
/**
* Extensions that are built into ADS but should be packaged externally as well for VS Code.
*/
export const vscodeExternalExtensions = [
'data-workspace'
];
// extensions that require a rebuild since they have native parts
const rebuildExtensions = [
'big-data-cluster',
@@ -425,7 +432,7 @@ export function packageExternalExtensionsStream(): NodeJS.ReadWriteStream {
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
})
.filter(({ name }) => externalExtensions.indexOf(name) >= 0);
.filter(({ name }) => externalExtensions.indexOf(name) >= 0 || vscodeExternalExtensions.indexOf(name) >= 0);
const builtExtensions = extenalExtensionDescriptions.map(extension => {
return fromLocal(extension.path, false)

View File

@@ -527,7 +527,11 @@ function createXlfFilesForCoreBundle() {
if (file.isBuffer()) {
const xlfs = Object.create(null);
const json = JSON.parse(file.contents.toString('utf8'));
for (let coreModule in json.keys) {
// {{SQL CARBON EDIT}} - Must sort the keys for easier translation.
let sortedKeys = Object.keys(json.keys).sort();
for (let i = 0; i < sortedKeys.length; i++) {
let coreModule = sortedKeys[i];
// {{SQL CARBON EDIT}} - End
const projectResource = getResource(coreModule);
const resource = projectResource.name;
const project = projectResource.project;

View File

@@ -652,7 +652,11 @@ export function createXlfFilesForCoreBundle(): ThroughStream {
if (file.isBuffer()) {
const xlfs: Map<XLF> = Object.create(null);
const json: BundledFormat = JSON.parse((file.contents as Buffer).toString('utf8'));
for (let coreModule in json.keys) {
// {{SQL CARBON EDIT}} - Must sort the keys for easier translation.
let sortedKeys = Object.keys(json.keys).sort();
for (let i = 0; i < sortedKeys.length; i++) {
let coreModule = sortedKeys[i];
// {{SQL CARBON EDIT}} - End
const projectResource = getResource(coreModule);
const resource = projectResource.name;
const project = projectResource.project;

View File

@@ -238,7 +238,7 @@ function refreshLangpacks() {
}
let packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString());
//processing extension fields, version and folder name must be changed manually.
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText);
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText).toLowerCase();
packageJSON['displayName'] = packageJSON['displayName'].replace('Visual Studio Code', textFields.displayNameText);
packageJSON['publisher'] = textFields.publisherText;
packageJSON['license'] = textFields.licenseText;
@@ -266,18 +266,6 @@ function refreshLangpacks() {
if (languageId === "zh-tw") {
languageId = "zh-hant";
}
//remove extensions not part of ADS.
if (fs.existsSync(translationDataFolder)) {
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
for (let extensionTag in totalExtensions) {
let extensionFileName = totalExtensions[extensionTag];
let xlfPath = path.join(location, `${languageId}`, extensionFileName.replace('.i18n.json', '.xlf'));
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
rimraf.sync(filePath);
}
}
}
console.log(`Importing translations for ${languageId} from '${location}' to '${translationDataFolder}' ...`);
let translationPaths = [];
gulp.src(path.join(location, languageId, '**', '*.xlf'))
@@ -356,6 +344,8 @@ function renameVscodeLangpacks() {
}
let locADSFolder = path.join('.', 'i18n', `ads-language-pack-${langId}`);
let locVSCODEFolder = path.join('.', 'i18n', `vscode-language-pack-${langId}`);
let translationDataFolder = path.join(locVSCODEFolder, 'translations');
let xlfFolder = path.join('.', 'resources', 'xlf');
try {
fs.statSync(locVSCODEFolder);
}
@@ -363,12 +353,31 @@ function renameVscodeLangpacks() {
console.log('vscode pack is not in ADS yet: ' + langId);
continue;
}
gulp.src(`i18n/ads-language-pack-${langId}/*.md`)
.pipe(vfs.dest(locVSCODEFolder, { overwrite: true }))
.end(function () {
rimraf.sync(locADSFolder);
fs.renameSync(locVSCODEFolder, locADSFolder);
//Delete any erroneous zip files found in vscode folder.
let globZipArray = glob.sync(path.join(locVSCODEFolder, '*.zip'));
globZipArray.forEach(element => {
fs.unlinkSync(element);
});
// Delete extension files in vscode language pack that are not in ADS.
if (fs.existsSync(translationDataFolder)) {
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
for (let extensionTag in totalExtensions) {
let extensionFileName = totalExtensions[extensionTag];
let xlfPath = path.join(xlfFolder, `${langId}`, extensionFileName.replace('.i18n.json', '.xlf'));
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
rimraf.sync(filePath);
}
}
}
//Get list of md files in ADS langpack, to copy to vscode langpack prior to renaming.
let globMDArray = glob.sync(path.join(locADSFolder, '*.md'));
//Copy files to vscode langpack, then remove the ADS langpack, and finally rename the vscode langpack to match the ADS one.
globMDArray.forEach(element => {
fs.copyFileSync(element, path.join(locVSCODEFolder, path.parse(element).base));
});
rimraf.sync(locADSFolder);
fs.renameSync(locVSCODEFolder, locADSFolder);
}
console.log("Langpack Rename Completed.");
return Promise.resolve();

View File

@@ -256,7 +256,7 @@ export function refreshLangpacks(): Promise<void> {
}
let packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString());
//processing extension fields, version and folder name must be changed manually.
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText);
packageJSON['name'] = packageJSON['name'].replace('vscode', textFields.nameText).toLowerCase();
packageJSON['displayName'] = packageJSON['displayName'].replace('Visual Studio Code', textFields.displayNameText);
packageJSON['publisher'] = textFields.publisherText;
packageJSON['license'] = textFields.licenseText;
@@ -287,20 +287,6 @@ export function refreshLangpacks(): Promise<void> {
languageId = "zh-hant";
}
//remove extensions not part of ADS.
if (fs.existsSync(translationDataFolder)) {
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
for (let extensionTag in totalExtensions) {
let extensionFileName = totalExtensions[extensionTag];
let xlfPath = path.join(location, `${languageId}`, extensionFileName.replace('.i18n.json', '.xlf'))
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
rimraf.sync(filePath);
}
}
}
console.log(`Importing translations for ${languageId} from '${location}' to '${translationDataFolder}' ...`);
let translationPaths: any = [];
gulp.src(path.join(location, languageId, '**', '*.xlf'))
@@ -380,6 +366,8 @@ export function renameVscodeLangpacks(): Promise<void> {
}
let locADSFolder = path.join('.', 'i18n', `ads-language-pack-${langId}`);
let locVSCODEFolder = path.join('.', 'i18n', `vscode-language-pack-${langId}`);
let translationDataFolder = path.join(locVSCODEFolder, 'translations');
let xlfFolder = path.join('.', 'resources', 'xlf');
try {
fs.statSync(locVSCODEFolder);
}
@@ -387,12 +375,35 @@ export function renameVscodeLangpacks(): Promise<void> {
console.log('vscode pack is not in ADS yet: ' + langId);
continue;
}
gulp.src(`i18n/ads-language-pack-${langId}/*.md`)
.pipe(vfs.dest(locVSCODEFolder, {overwrite: true}))
.end(function () {
rimraf.sync(locADSFolder);
fs.renameSync(locVSCODEFolder, locADSFolder);
});
//Delete any erroneous zip files found in vscode folder.
let globZipArray = glob.sync(path.join(locVSCODEFolder, '*.zip'));
globZipArray.forEach(element => {
fs.unlinkSync(element);
});
// Delete extension files in vscode language pack that are not in ADS.
if (fs.existsSync(translationDataFolder)) {
let totalExtensions = fs.readdirSync(path.join(translationDataFolder, 'extensions'));
for (let extensionTag in totalExtensions) {
let extensionFileName = totalExtensions[extensionTag];
let xlfPath = path.join(xlfFolder, `${langId}`, extensionFileName.replace('.i18n.json', '.xlf'))
if (!(fs.existsSync(xlfPath) || VSCODEExtensions.indexOf(extensionFileName.replace('.i18n.json', '')) !== -1)) {
let filePath = path.join(translationDataFolder, 'extensions', extensionFileName);
rimraf.sync(filePath);
}
}
}
//Get list of md files in ADS langpack, to copy to vscode langpack prior to renaming.
let globMDArray = glob.sync(path.join(locADSFolder, '*.md'));
//Copy files to vscode langpack, then remove the ADS langpack, and finally rename the vscode langpack to match the ADS one.
globMDArray.forEach(element => {
fs.copyFileSync(element, path.join(locVSCODEFolder,path.parse(element).base));
});
rimraf.sync(locADSFolder);
fs.renameSync(locVSCODEFolder, locADSFolder);
}
console.log("Langpack Rename Completed.");

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
(async () => {
const serviceDownloader = require('service-downloader').ServiceDownloadProvider;
const serviceDownloader = require('@microsoft/ads-service-downloader').ServiceDownloadProvider;
const path = require('path');
const fs = require('fs').promises;
const rimraf = require('rimraf');

View File

@@ -92,7 +92,7 @@
},
"dependencies": {
"@microsoft/ads-extension-telemetry": "^1.1.3",
"service-downloader": "0.2.1",
"@microsoft/ads-service-downloader": "0.2.2",
"vscode-nls": "^4.1.2"
},
"devDependencies": {

View File

@@ -189,6 +189,20 @@
dependencies:
vscode-extension-telemetry "^0.1.6"
"@microsoft/ads-service-downloader@0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@microsoft/ads-service-downloader/-/ads-service-downloader-0.2.2.tgz#1a32c62eadb77fdab23eed257aaa5b590ccf2ce4"
integrity sha512-sEvp1dCQu8ZnUyrjX9qUo/S9IIvXkPCkCSuJ4IhuGEmGm2/++evixOtUgRKWYSRRvL1QskIUjZ0I59m6bKJ5yQ==
dependencies:
async-retry "^1.2.3"
eventemitter2 "^5.0.1"
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.3"
mkdirp "^0.5.1"
tar "^6.1.6"
tmp "^0.0.33"
yauzl "^2.10.0"
"@types/mocha@^5.2.5":
version "5.2.7"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
@@ -299,10 +313,10 @@ charenc@~0.0.1:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
chownr@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
cls-hooked@^4.2.2:
version "4.2.2"
@@ -695,10 +709,10 @@ minipass@^3.0.0:
dependencies:
yallist "^4.0.0"
minizlib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
@@ -839,20 +853,6 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
service-downloader@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/service-downloader/-/service-downloader-0.2.1.tgz#8bd756bc4bc0cbfdf04fe71d4337f19ce6196203"
integrity sha512-5IEy2nyMJj/f41pI65b8RMeJyCecGNrMmNCpUW8hckZ9cBMyX+VCp8GjYoM6Mz/X0XSaGVz7V5gtCWjfeJI7gA==
dependencies:
async-retry "^1.2.3"
eventemitter2 "^5.0.1"
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.3"
mkdirp "^0.5.1"
tar "^6.0.1"
tmp "^0.0.33"
yauzl "^2.10.0"
shimmer@^1.1.0, shimmer@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
@@ -950,15 +950,15 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
tar@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa"
integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==
tar@^6.1.6:
version "6.1.6"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.6.tgz#c23d797b0a1efe5d479b1490805c5443f3560c5d"
integrity sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==
dependencies:
chownr "^1.1.3"
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"

View File

@@ -2,8 +2,6 @@
Welcome to Microsoft Azure Arc Extension for Azure Data Studio!
**This extension is only applicable to customers in the Azure Arc data services public preview.**
## Overview
This extension adds the following features to Azure Data Studio.

View File

@@ -47,7 +47,8 @@
"|Tools|Description|Installation|\n",
"|---|---|---|\n",
"|kubectl | Command-line tool for monitoring the underlying Kubernetes cluster | [Installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) |\n",
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
],
"metadata": {
"azdata_cell_guid": "714582b9-10ee-409e-ab12-15a4825c9471"
@@ -64,8 +65,9 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"import sys,os,json,html,getpass,time, tempfile\n",
"import sys,os,getpass\n",
"def run_command(command):\n",
" print(\"Executing: \" + command)\n",
" !{command}\n",
@@ -73,12 +75,11 @@
" sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n\\t{command}\\n')\n",
" print(f'Successfully executed: {command}')"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "d973d5b4-7f0a-4a9d-b204-a16480f3940d",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -101,15 +102,15 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"run_command('azdata --version')"
"run_command('az --version')"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "691671d7-3f05-406c-a183-4cff7d17f83d",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -122,6 +123,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"if \"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\" in os.environ:\n",
" arc_admin_password = os.environ[\"AZDATA_NB_VAR_ARC_ADMIN_PASSWORD\"]\n",
@@ -134,12 +136,11 @@
" if arc_admin_password != confirm_password:\n",
" sys.exit(f'Passwords do not match.')"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "e7e10828-6cae-45af-8c2f-1484b6d4f9ac",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -152,17 +153,17 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"os.environ[\"KUBECONFIG\"] = arc_config_file\n",
"run_command(f'kubectl config use-context {arc_cluster_context}')\n",
"run_command('kubectl config current-context')"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "7d1a03d4-1df8-48eb-bff0-0042603b95b1",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -175,22 +176,22 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print (f'Creating Azure Arc Data Controller: {arc_data_controller_name} using configuration {arc_cluster_context}')\n",
"os.environ[\"ACCEPT_EULA\"] = 'yes'\n",
"os.environ[\"AZDATA_USERNAME\"] = arc_admin_username\n",
"os.environ[\"AZDATA_PASSWORD\"] = arc_admin_password\n",
"\n",
"if os.name == 'nt':\n",
" print(f'If you don\\'t see output produced by azdata, you can run the following command in a terminal window to check the deployment status:\\n\\t {os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {arc_data_controller_namespace}')\n",
"run_command(f'azdata arc dc create --connectivity-mode Indirect -n {arc_data_controller_name} -ns {arc_data_controller_namespace} -s {arc_subscription} -g {arc_resource_group} -l {arc_data_controller_location} -sc {arc_data_controller_storage_class} --profile-name {arc_profile}')\n",
" print(f'If you don\\'t see output produced by az, you can run the following command in a terminal window to check the deployment status:\\n\\t {os.environ[\"AZDATA_NB_VAR_KUBECTL\"]} get pods -n {arc_data_controller_namespace}')\n",
"run_command(f'az arcdata dc create --connectivity-mode indirect --name {arc_data_controller_name} --k8s-namespace {arc_data_controller_namespace} --subscription {arc_subscription} --resource-group {arc_resource_group} --location {arc_data_controller_location} --storage-class {arc_data_controller_storage_class} --profile-name {arc_profile} --infrastructure {arc_infrastructure} --use-k8s')\n",
"print(f'Azure Arc Data Controller: {arc_data_controller_name} created.') "
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "373947a1-90b9-49ee-86f4-17a4c7d4ca76",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -203,38 +204,16 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Setting context to Data Controller.\n",
"#\n",
"run_command(f'kubectl config set-context --current --namespace {arc_data_controller_namespace}')"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "c974561f-13d0-4e7a-b74b-d781c2e06d68"
},
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"source": [
"### **Login to the Data Controller.**\n"
],
"metadata": {
"azdata_cell_guid": "9376b2ab-0edf-478f-9e3c-5ff46ae3501a"
}
},
{
"cell_type": "code",
"source": [
"# Login to the Data Controller.\n",
"#\n",
"run_command(f'azdata login --namespace {arc_data_controller_namespace}')"
],
"metadata": {
"azdata_cell_guid": "9aed0c5a-2c8a-4ad7-becb-60281923a196"
},
"outputs": [],
"execution_count": null
}
]
}
}

View File

@@ -47,7 +47,8 @@
" \n",
"|Tools|Description|Installation|\n",
"|---|---|---|\n",
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
],
"metadata": {
"azdata_cell_guid": "20fe3985-a01e-461c-bce0-235f7606cc3c"
@@ -64,6 +65,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"import sys,os,json,subprocess\n",
"def run_command():\n",
@@ -76,16 +78,14 @@
" print(f'Successfully executed: {cmd}')\n",
" print(f'\\t>>>Output: {output.stdout.decode(\"utf-8\")}\\n')\n",
" return output.stdout.decode(\"utf-8\")\n",
"cmd = 'azdata --version'\n",
"out = run_command()\n",
""
"cmd = 'az --version'\n",
"out = run_command()\n"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "749d8dba-3da8-46e9-ae48-2b38056ab7a2",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -111,48 +111,50 @@
},
{
"cell_type": "code",
"source": [
"# Login to the data controller.\n",
"#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
"endpoint_option = f' -e {controller_endpoint}' if controller_endpoint else \"\"\n",
"cmd = f'azdata login --namespace {arc_data_controller_namespace} -u {controller_username}{endpoint_option}'\n",
"out=run_command()"
],
"metadata": {
"azdata_cell_guid": "71366399-5963-4e24-b2f2-6bb5bffba4ec"
},
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print (f'Creating the PostgreSQL Hyperscale - Azure Arc instance')\n",
"\n",
"workers_option = f' -w {postgres_server_group_workers}' if postgres_server_group_workers else \"\"\n",
"port_option = f' --port \"{postgres_server_group_port}\"' if postgres_server_group_port else \"\"\n",
"engine_version_option = f' -ev {postgres_server_group_engine_version}' if postgres_server_group_engine_version else \"\"\n",
"extensions_option = f' --extensions \"{postgres_server_group_extensions}\"' if postgres_server_group_extensions else \"\"\n",
"volume_size_data_option = f' -vsd {postgres_server_group_volume_size_data}Gi' if postgres_server_group_volume_size_data else \"\"\n",
"volume_size_logs_option = f' -vsl {postgres_server_group_volume_size_logs}Gi' if postgres_server_group_volume_size_logs else \"\"\n",
"volume_size_backups_option = f' -vsb {postgres_server_group_volume_size_backups}Gi' if postgres_server_group_volume_size_backups else \"\"\n",
"cores_request_option = f' -cr \"c={postgres_server_group_coordinator_cores_request},w={postgres_server_group_workers_cores_request}\"' if postgres_server_group_coordinator_cores_request and postgres_server_group_workers_cores_request else f' -cr \"c={postgres_server_group_coordinator_cores_request}\"' if postgres_server_group_coordinator_cores_request else f' -cr \"w={postgres_server_group_workers_cores_request}\"' if postgres_server_group_workers_cores_request else \"\"\n",
"cores_limit_option = f' -cl \"c={postgres_server_group_coordinator_cores_limit},w={postgres_server_group_workers_cores_limit}\"' if postgres_server_group_coordinator_cores_limit and postgres_server_group_workers_cores_limit else f' -cl \"c={postgres_server_group_coordinator_cores_limit}\"' if postgres_server_group_coordinator_cores_limit else f' -cl \"w={postgres_server_group_workers_cores_limit}\"' if postgres_server_group_workers_cores_limit else \"\"\n",
"memory_request_option = f' -mr \"c={postgres_server_group_coordinator_memory_request}Gi,w={postgres_server_group_workers_memory_request}Gi\"' if postgres_server_group_coordinator_memory_request and postgres_server_group_workers_memory_request else f' -mr \"c={postgres_server_group_coordinator_memory_request}Gi\"' if postgres_server_group_coordinator_memory_request else f' -mr \"w={postgres_server_group_workers_memory_request}Gi\"' if postgres_server_group_workers_memory_request else \"\"\n",
"memory_limit_option = f' -ml \"c={postgres_server_group_coordinator_memory_limit}Gi,w={postgres_server_group_workers_memory_limit}Gi\"' if postgres_server_group_coordinator_memory_limit and postgres_server_group_workers_memory_limit else f' -ml \"c={postgres_server_group_coordinator_memory_limit}Gi\"' if postgres_server_group_coordinator_memory_limit else f' -ml \"w={postgres_server_group_workers_memory_limit}Gi\"' if postgres_server_group_workers_memory_limit else \"\"\n",
"workers = f' --workers {postgres_server_group_workers}' if postgres_server_group_workers else \"\"\n",
"port = f' --port \"{postgres_server_group_port}\"' if postgres_server_group_port else \"\"\n",
"engine_version = f' --engine-version {postgres_server_group_engine_version}' if postgres_server_group_engine_version else \"\"\n",
"extensions = f' --extensions \"{postgres_server_group_extensions}\"' if postgres_server_group_extensions else \"\"\n",
"volume_size_data = f' --volume-size-data {postgres_server_group_volume_size_data}Gi' if postgres_server_group_volume_size_data else \"\"\n",
"volume_size_logs = f' --volume-size-logs {postgres_server_group_volume_size_logs}Gi' if postgres_server_group_volume_size_logs else \"\"\n",
"volume_size_backups = f' --volume-size-backups {postgres_server_group_volume_size_backups}Gi' if postgres_server_group_volume_size_backups else \"\"\n",
"\n",
"def get_per_role_argument(argument, roles, unit=''):\n",
" value = ','.join(f'{role}={value}{unit}' for role,value in roles.items() if value not in (None, ''))\n",
" return f' {argument} {value}' if value else ''\n",
"\n",
"cores_request = get_per_role_argument('--cores-request', {\n",
" 'c': postgres_server_group_coordinator_cores_request,\n",
" 'w': postgres_server_group_workers_cores_request\n",
"})\n",
"\n",
"cores_limit = get_per_role_argument('--cores-limit', {\n",
" 'c': postgres_server_group_coordinator_cores_limit,\n",
" 'w': postgres_server_group_workers_cores_limit\n",
"})\n",
"\n",
"memory_request = get_per_role_argument('--memory-request', {\n",
" 'c': postgres_server_group_coordinator_memory_request,\n",
" 'w': postgres_server_group_workers_memory_request\n",
"}, 'Gi')\n",
"\n",
"memory_limit = get_per_role_argument('--memory-limit', {\n",
" 'c': postgres_server_group_coordinator_memory_limit,\n",
" 'w': postgres_server_group_workers_memory_limit\n",
"}, 'Gi')\n",
"\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
"cmd = f'azdata arc postgres server create -n {postgres_server_group_name} -scd {postgres_storage_class_data} -scl {postgres_storage_class_logs} -scb {postgres_storage_class_backups}{workers_option}{port_option}{engine_version_option}{extensions_option}{volume_size_data_option}{volume_size_logs_option}{volume_size_backups_option}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
"cmd = f'az postgres arc-server create --name {postgres_server_group_name} --k8s-namespace {arc_data_controller_namespace} --use-k8s --storage-class-data {postgres_storage_class_data} --storage-class-logs {postgres_storage_class_logs} --storage-class-backups {postgres_storage_class_backups}{workers}{port}{engine_version}{extensions}{volume_size_data}{volume_size_logs}{volume_size_backups}{cores_request}{cores_limit}{memory_request}{memory_limit}'\n",
"out=run_command()"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "4fbaf071-55a1-40bc-be7e-7b9b5547b886"
},
"outputs": [],
"execution_count": null
}
}
]
}

View File

@@ -47,7 +47,8 @@
" \n",
"|Tools|Description|Installation|\n",
"|---|---|---|\n",
"|Azure Data CLI (azdata) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) |"
"|Azure CLI (az) | Command-line tool for installing and managing resources in an Azure Arc cluster |[Installation](https://docs.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli) |\n",
"|Azure CLI arcdata extension | Commands for using Azure Arc for Azure data services. | [Installation](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension)"
],
"metadata": {
"azdata_cell_guid": "d1c8258e-9efd-4380-a48c-cd675423ed2f"
@@ -64,6 +65,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"import sys,os,json,subprocess\n",
"def run_command():\n",
@@ -76,16 +78,14 @@
" print(f'Successfully executed: {cmd}')\n",
" print(f'\\t>>>Output: {output.stdout.decode(\"utf-8\")}\\n')\n",
" return output.stdout.decode(\"utf-8\")\n",
"cmd = 'azdata --version'\n",
"out = run_command()\n",
""
"cmd = 'az --version'\n",
"out = run_command()\n"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "749d8dba-3da8-46e9-ae48-2b38056ab7a2",
"tags": []
},
"outputs": [],
"execution_count": null
}
},
{
"cell_type": "markdown",
@@ -111,41 +111,24 @@
},
{
"cell_type": "code",
"source": [
"# Login to the data controller.\n",
"#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
"out=run_command()"
],
"metadata": {
"azdata_cell_guid": "1437c536-17e8-4a7f-80c1-aa43ad02686c"
},
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"print (f'Creating the SQL managed instance - Azure Arc instance')\n",
"\n",
"cores_request_option = f' -cr \"{sql_cores_request}\"' if sql_cores_request else \"\"\n",
"cores_limit_option = f' -cl \"{sql_cores_limit}\"' if sql_cores_limit else \"\"\n",
"memory_request_option = f' -mr \"{sql_memory_request}Gi\"' if sql_memory_request else \"\"\n",
"memory_limit_option = f' -ml \"{sql_memory_limit}Gi\"' if sql_memory_limit else \"\"\n",
"cores_request_option = f' --cores-request \"{sql_cores_request}\"' if sql_cores_request else \"\"\n",
"cores_limit_option = f' --cores-limit \"{sql_cores_limit}\"' if sql_cores_limit else \"\"\n",
"memory_request_option = f' --memory-request \"{sql_memory_request}Gi\"' if sql_memory_request else \"\"\n",
"memory_limit_option = f' --memory-limit \"{sql_memory_limit}Gi\"' if sql_memory_limit else \"\"\n",
"\n",
"os.environ[\"AZDATA_USERNAME\"] = sql_username\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
"cmd = f'azdata arc sql mi create -n {sql_instance_name} -scd {sql_storage_class_data} -scl {sql_storage_class_logs} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option}'\n",
"cmd = f'az sql mi-arc create --name {sql_instance_name} --k8s-namespace {arc_data_controller_namespace} --replicas {sql_replicas}{cores_request_option}{cores_limit_option}{memory_request_option}{memory_limit_option} --use-k8s'\n",
"out=run_command()"
],
"outputs": [],
"metadata": {
"azdata_cell_guid": "4fbaf071-55a1-40bc-be7e-7b9b5547b886"
},
"outputs": [],
"execution_count": null
}
}
]
}
}

View File

@@ -9,7 +9,7 @@
"icon": "images/extension.png",
"engines": {
"vscode": "*",
"azdata": ">=1.28.0"
"azdata": ">=1.32.0"
},
"activationEvents": [
"onCommand:arc.connectToController",
@@ -18,7 +18,7 @@
"onView:azureArc"
],
"extensionDependencies": [
"Microsoft.azdata",
"Microsoft.azcli",
"Microsoft.resource-deployment"
],
"repository": {
@@ -190,7 +190,7 @@
"editable": false,
"options": {
"source": {
"providerId": "arc.controller.config.profiles",
"providerId": "azcli.arc.controller.config.profiles",
"loadingText": "%arc.data.controller.cluster.config.profile.loading%",
"loadingCompletedText": "%arc.data.controller.cluster.config.profile.loadingcompleted%"
},
@@ -289,6 +289,21 @@
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_STORAGE_CLASS",
"type": "kube_storage_class",
"required": true
},
{
"type": "options",
"label": "%arc.data.controller.infrastructure%",
"defaultValue": "azure",
"required": true,
"variableName": "AZDATA_NB_VAR_ARC_INFRASTRUCTURE",
"options": [
"azure",
"gcp",
"aws",
"alibaba",
"onpremises",
"other"
]
}
]
},
@@ -507,6 +522,12 @@
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
},
{
"label": "%arc.data.controller.summary.data.controller.infrastructure%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_INFRASTRUCTURE)"
}
]
}
@@ -519,8 +540,7 @@
"name": "kubectl"
},
{
"name": "azdata",
"version": "20.3.4"
"name": "azure-cli"
}
],
"when": true
@@ -573,11 +593,8 @@
"providerId": "arc.controllers",
"variableNames": {
"namespace": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE",
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
"kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG",
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT",
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT"
}
},
"optionsType": "dropdown"
@@ -838,8 +855,7 @@
"name": "kubectl"
},
{
"name": "azdata",
"version": "20.3.4"
"name": "azure-cli"
}
],
"when": "true"
@@ -915,11 +931,9 @@
"source": {
"providerId": "arc.controllers",
"variableNames": {
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
"namespace": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE",
"kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG",
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT",
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
"clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT"
}
},
"optionsType": "dropdown"
@@ -1067,8 +1081,7 @@
"name": "kubectl"
},
{
"name": "azdata",
"version": "20.3.4"
"name": "azure-cli"
}
],
"when": "mi-type=arc-mi"
@@ -1092,6 +1105,9 @@
"links": [
{
"text": "%arc.agreement.sql.help.text.learn.more%",
"accessibilityInformation": {
"label": "%arc.agreement.sql.help.text.learn.more.ariaLabel%"
},
"url": "https://go.microsoft.com/fwlink/?linkid=2141849"
}
],

View File

@@ -33,6 +33,7 @@
"arc.data.controller.name": "Data controller name",
"arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
"arc.data.controller.location": "Location",
"arc.data.controller.infrastructure": "Infrastructure",
"arc.data.controller.admin.account.title": "Administrator account",
"arc.data.controller.admin.account.name": "Data controller login",
"arc.data.controller.admin.account.password": "Password",
@@ -58,6 +59,7 @@
"arc.data.controller.summary.resource.group": "Resource group",
"arc.data.controller.summary.data.controller.name": "Data controller name",
"arc.data.controller.summary.data.controller.namespace": "Data controller namespace",
"arc.data.controller.summary.data.controller.infrastructure": "Data controller infrastructure",
"arc.data.controller.summary.controller": "Controller",
"arc.data.controller.summary.location": "Location",
"arc.data.controller.agreement": "I accept {0} and {1}.",
@@ -153,6 +155,7 @@
"cores.limit.greater.than.or.equal.to.requested.cores": "Cores limit must be greater than or equal to requested cores",
"requested.memory.less.than.or.equal.to.memory.limit": "Requested memory must be less than or equal to memory limit",
"memory.limit.greater.than.or.equal.to.requested.memory": "Memory limit must be greater than or equal to requested memory",
"arc.agreement.sql.help.text": "Azure Arc enabled Managed Instance provides SQL Server access and feature compatibility that can be deployed on the infrastructure of your choice. While this service is in preview, it has some feature limitations compared to SQL Managed Instance on Azure. {0}",
"arc.agreement.sql.help.text.learn.more": "Learn more"
"arc.agreement.sql.help.text": "Azure Arc enabled Managed Instance provides SQL Server access and feature compatibility that can be deployed on the infrastructure of your choice. {0}",
"arc.agreement.sql.help.text.learn.more": "Learn more",
"arc.agreement.sql.help.text.learn.more.ariaLabel": "Learn more about Azure Arc enabled Managed Instance"
}

View File

@@ -5,8 +5,6 @@
import * as arc from 'arc';
import * as rd from 'resource-deployment';
import * as loc from '../localizedConstants';
import { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../ui/tree/controllerTreeNode';
@@ -17,26 +15,10 @@ export class UserCancelledError extends Error implements rd.ErrorWithType {
}
export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension {
return {
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider),
getControllerPassword: (controllerInfo: arc.ControllerInfo) => getControllerPassword(treeDataProvider, controllerInfo),
reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo)
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider)
};
}
export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
const dialog = new PasswordToControllerDialog(treeDataProvider);
dialog.showDialog(controllerInfo);
const model = await dialog.waitForClose();
if (!model) {
throw new UserCancelledError(loc.userCancelledError);
}
return model.password;
}
export async function getControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
return await treeDataProvider.getPassword(controllerInfo);
}
export async function getRegisteredDataControllers(treeDataProvider: AzureArcTreeDataProvider): Promise<arc.DataController[]> {
return (await treeDataProvider.getChildren())
.filter(node => node instanceof ControllerTreeNode)

View File

@@ -32,7 +32,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
dialog.showDialog();
const model = await dialog.waitForClose();
if (model) {
await treeDataProvider.addOrUpdateController(model.controllerModel, model.password);
await treeDataProvider.addOrUpdateController(model.controllerModel);
}
});
@@ -50,10 +50,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
vscode.commands.registerCommand('arc.editConnection', async (treeNode: ControllerTreeNode) => {
const dialog = new ConnectToControllerDialog(treeDataProvider);
dialog.showDialog(treeNode.model.info, await treeDataProvider.getPassword(treeNode.model.info));
dialog.showDialog(treeNode.model.info);
const model = await dialog.waitForClose();
if (model) {
await treeDataProvider.addOrUpdateController(model.controllerModel, model.password, true);
await treeDataProvider.addOrUpdateController(model.controllerModel, true);
}
});

View File

@@ -42,8 +42,9 @@ export const dropText = localize('arc.drop', "Drop");
export const saveText = localize('arc.save', "Save");
export const discardText = localize('arc.discard', "Discard");
export const resetPassword = localize('arc.resetPassword', "Reset Password");
export const addExtensions = localize('arc.addExtensions', "Add extensions");
export const dropExtension = localize('arc.dropExtension', "Drop extension");
export const loadExtensions = localize('arc.loadExtensions', "Load extensions");
export const unloadExtensions = localize('arc.unloadExtensions', "Unload extensions");
export const noExtensions = localize('arc.noExtensions', "No extensions listed in configuration.");
export const openInAzurePortal = localize('arc.openInAzurePortal', "Open in Azure Portal");
export const resourceGroup = localize('arc.resourceGroup', "Resource Group");
export const region = localize('arc.region', "Region");
@@ -60,6 +61,8 @@ export const controllerEndpoint = localize('arc.controllerEndpoint', "Controller
export const extensionName = localize('arc.extensionName', "Extension name");
export const extensionsDescription = localize('arc.extensionsDescription', "PostgreSQL provides the ability to extend the functionality of your database by using extensions. Extensions allow for bundling multiple related SQL objects together in a single package that can be loaded or removed from your database with a single command. After being loaded in the database, extensions can function like built-in features.");
export const extensionsFunction = localize('arc.extensionsFunction', "Some extensions must be loaded into PostgreSQL at startup time before they can be used. These preloaded extensions can be viewed and edited below.");
export function extensionsAddFunction(extensions: string): string { return localize('arc.extensionsAddFunction', "Some extensions must be loaded into PostgreSQL at startup time before they can be used. To edit, type in comma separated list of valid extensions: ({0}).", extensions); }
export function extensionsAddErrorrMessage(extensions: string): string { return localize('arc.extensionsAddErrorrMessage', "Value should be either of the following: ({0}).", extensions); }
export const extensionsLearnMore = localize('arc.extensionsLearnMore', "Learn more about PostgreSQL extensions.");
export const extensionsTableLoading = localize('arc.extensionsTableLoading', "Table of preloaded extensions are loading.");
export const extensionsTableLabel = localize('arc.extensionsTableLabel', "Table of preloaded extensions.");
@@ -72,6 +75,7 @@ export const grafanaDashboard = localize('arc.grafanaDashboard', "Grafana Dashbo
export const kibanaDashboardDescription = localize('arc.kibanaDashboardDescription', "Dashboard for viewing logs");
export const grafanaDashboardDescription = localize('arc.grafanaDashboardDescription', "Dashboard for viewing metrics");
export const serviceEndpoints = localize('arc.serviceEndpoints', "Service endpoints");
export const serviceEndpointsTable = localize('arc.serviceEndpointsTable', "Service endpoints table");
export const databases = localize('arc.databases', "Databases");
export const endpoint = localize('arc.endpoint', "Endpoint");
export const description = localize('arc.description', "Description");
@@ -206,6 +210,8 @@ export const noWorkerPods = localize('arc.noWorkerPods', "No worker pods in this
export const podsReady = localize('arc.podsReady', "pods ready");
export const podsPresent = localize('arc.podsPresent', "Pods Present");
export const podsUsedDescription = localize('arc.podsUsedDescription', "Select a pod in the dropdown below for detailed health information.");
export const podsUsedDescriptionAria = localize('arc.podsUsedDescriptionAria', "Select a pod in the dropdown below for detailed health information");
export const podConditionsTable = localize('arc.podConditionsTable', "Pod conditions table");
export const connectToPostgresDescription = localize('arc.connectToPostgresDescription', "A connection to the server is required to show and set database engine settings, which will require the PostgreSQL Extension to be installed.");
export const postgresExtension = localize('arc.postgresExtension', "microsoft.azuredatastudio-postgresql");
export const podInitialized = localize('arc.podInitialized', "Pod is initialized.");
@@ -223,7 +229,7 @@ export function extensionInstalled(name: string): string { return localize('arc.
export function updatingInstance(name: string): string { return localize('arc.updatingInstance', "Updating instance '{0}'...", name); }
export function instanceDeleted(name: string): string { return localize('arc.instanceDeleted', "Instance '{0}' deleted", name); }
export function instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
export function extensionDropped(name: string): string { return localize('arc.extensionDropped', "Extension '{0}' deleted", name); }
export function extensionsDropped(name: string): string { return localize('arc.extensionsDropped', "Extensions '{0}' dropped", name); }
export function extensionsAdded(name: string): string { return localize('arc.extensionsAdded', "Extensions '{0}' added", name); }
export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); }
export function clickTheTroubleshootButton(resourceType: string): string { return localize('arc.clickTheTroubleshootButton', "Click the troubleshoot button to open the Azure Arc {0} troubleshooting notebook.", resourceType); }
@@ -242,6 +248,7 @@ export function numVCores(vCores: string | undefined): string {
}
}
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
export function connectionString(type: string): string { return localize({ key: 'arc.connectionString', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Connection string for {0}", type); }
export function copyConnectionStringToClipboard(type: string): string { return localize({ key: 'arc.copyConnectionStringToClipboard', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Copy {0} Connection String to clipboard", type); }
export function copyValueToClipboard(valueName: string): string { return localize({ key: 'arc.copyValueToClipboard', comment: ['{0} is the name of the type of value being copied (e.g. Coordinator endpoint)'] }, "Copy {0} to clipboard", valueName); }

View File

@@ -4,12 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { ControllerInfo, ResourceType } from 'arc';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as vscode from 'vscode';
import { UserCancelledError } from '../common/api';
import { getCurrentClusterContext, getKubeConfigClusterContexts } from '../common/kubeUtils';
import * as loc from '../localizedConstants';
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
export type Registration = {
@@ -19,13 +16,13 @@ export type Registration = {
};
export class ControllerModel {
private readonly _azdataApi: azdataExt.IExtension;
private _endpoints: azdataExt.DcEndpointListResult[] = [];
private readonly _azApi: azExt.IExtension;
private _endpoints: azExt.DcEndpointListResult[] = [];
private _registrations: Registration[] = [];
private _controllerConfig: azdataExt.DcConfigShowResult | undefined = undefined;
private _controllerConfig: azExt.DcConfigShowResult | undefined = undefined;
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.DcConfigShowResult | undefined>();
private readonly _onEndpointsUpdated = new vscode.EventEmitter<azdataExt.DcEndpointListResult[]>();
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.DcConfigShowResult | undefined>();
private readonly _onEndpointsUpdated = new vscode.EventEmitter<azExt.DcEndpointListResult[]>();
private readonly _onRegistrationsUpdated = new vscode.EventEmitter<Registration[]>();
private readonly _onInfoUpdated = new vscode.EventEmitter<ControllerInfo>();
@@ -38,85 +35,26 @@ export class ControllerModel {
public endpointsLastUpdated?: Date;
public registrationsLastUpdated?: Date;
constructor(public treeDataProvider: AzureArcTreeDataProvider, private _info: ControllerInfo, private _password?: string) {
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
constructor(public treeDataProvider: AzureArcTreeDataProvider, private _info: ControllerInfo) {
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
}
public get info(): ControllerInfo {
return this._info;
}
/**
* Gets the controller context to use when executing azdata commands. This is in one of two forms :
*
* If no URL is specified for this controller then just the namespace is used (e.g. test-namespace)
* If a URL is specified then a 3-part name is used, combining the namespace, username and URL separated by
* / (e.g. test-namespace/admin/https://10.91.86.13:30080)
*/
public get controllerContext(): string {
if (this._info.endpoint) {
return `${this._info.namespace}/${this._info.username}/${this._info.endpoint}`;
}
return this._info.namespace;
}
public set info(value: ControllerInfo) {
this._info = value;
this._onInfoUpdated.fire(this._info);
}
public get azdataAdditionalEnvVars(): azdataExt.AdditionalEnvVars {
public get azAdditionalEnvVars(): azExt.AdditionalEnvVars {
return {
'KUBECONFIG': this.info.kubeConfigFilePath,
'KUBECTL_CONTEXT': this.info.kubeClusterContext
};
}
/**
* Calls azdata login to set the context to this controller and acquires a login session to prevent other
* calls from changing the context while commands for this session are being executed.
* @param promptReconnect
*/
public async login(promptReconnect: boolean = false): Promise<void> {
let promptForValidClusterContext: boolean = false;
try {
const contexts = getKubeConfigClusterContexts(this.info.kubeConfigFilePath);
getCurrentClusterContext(contexts, this.info.kubeClusterContext, true); // this throws if this.info.kubeClusterContext is not found in 'contexts'
} catch (error) {
const response = await vscode.window.showErrorMessage(loc.clusterContextConfigNoLongerValid(this.info.kubeConfigFilePath, this.info.kubeClusterContext, error), loc.yes, loc.no);
if (response === loc.yes) {
promptForValidClusterContext = true;
} else {
if (!promptReconnect) { //throw unless we are required to prompt for reconnect anyways
throw error;
}
}
}
// We haven't gotten our password yet or we want to prompt for a reconnect or we want to prompt to reacquire valid cluster context or any and all of these.
if (!this._password || promptReconnect || promptForValidClusterContext) {
this._password = '';
if (this.info.rememberPassword) {
// It should be in the credentials store, get it from there
this._password = await this.treeDataProvider.getPassword(this.info);
}
if (promptReconnect || !this._password || promptForValidClusterContext) {
// No password yet or we want to re-prompt for credentials so prompt for it from the user
const dialog = new ConnectToControllerDialog(this.treeDataProvider);
dialog.showDialog(this.info, this._password);
const model = await dialog.waitForClose();
if (model) {
await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false);
this._password = model.password;
this._info = model.controllerModel.info;
} else {
throw new UserCancelledError(loc.userCancelledError);
}
}
}
await this._azdataApi.azdata.login({ endpoint: this.info.endpoint, namespace: this.info.namespace }, this.info.username, this._password, this.azdataAdditionalEnvVars);
}
/**
* Refreshes the Tree Node for this model. This will also result in the model being refreshed.
*/
@@ -125,16 +63,14 @@ export class ControllerModel {
if (node) {
this.treeDataProvider.refreshNode(node);
} else {
await this.refresh(false);
await this.refresh(false, this.info.namespace);
}
}
public async refresh(showErrors: boolean = true): Promise<void> {
// First need to log in to ensure that we're able to authenticate with the controller
await this.login(false);
public async refresh(showErrors: boolean = true, namespace: string): Promise<void> {
const newRegistrations: Registration[] = [];
await Promise.all([
this._azdataApi.azdata.arc.dc.config.show(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
this._controllerConfig = result.result;
this._azApi.az.arcdata.dc.config.show(namespace, this.azAdditionalEnvVars).then(result => {
this._controllerConfig = result.stdout;
this.configLastUpdated = new Date();
this._onConfigUpdated.fire(this._controllerConfig);
}).catch(err => {
@@ -147,8 +83,8 @@ export class ControllerModel {
this._onConfigUpdated.fire(this._controllerConfig);
throw err;
}),
this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
this._endpoints = result.result;
this._azApi.az.arcdata.dc.endpoint.list(namespace, this.azAdditionalEnvVars).then(result => {
this._endpoints = result.stdout;
this.endpointsLastUpdated = new Date();
this._onEndpointsUpdated.fire(this._endpoints);
}).catch(err => {
@@ -162,8 +98,8 @@ export class ControllerModel {
throw err;
}),
Promise.all([
this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
newRegistrations.push(...result.result.map(r => {
this._azApi.az.postgres.arcserver.list(namespace, this.azAdditionalEnvVars).then(result => {
newRegistrations.push(...result.stdout.map(r => {
return {
instanceName: r.name,
state: r.state,
@@ -171,14 +107,15 @@ export class ControllerModel {
};
}));
}),
this._azdataApi.azdata.arc.sql.mi.list(this.azdataAdditionalEnvVars, this.controllerContext).then(result => {
newRegistrations.push(...result.result.map(r => {
this._azApi.az.sql.miarc.list(namespace, this.azAdditionalEnvVars).then(result => {
newRegistrations.push(...result.stdout.map(r => {
return {
instanceName: r.name,
state: r.state,
instanceType: ResourceType.sqlManagedInstances
};
}));
})
]).then(() => {
this._registrations = newRegistrations;
@@ -188,11 +125,11 @@ export class ControllerModel {
]);
}
public get endpoints(): azdataExt.DcEndpointListResult[] {
public get endpoints(): azExt.DcEndpointListResult[] {
return this._endpoints;
}
public getEndpoint(name: string): azdataExt.DcEndpointListResult | undefined {
public getEndpoint(name: string): azExt.DcEndpointListResult | undefined {
return this._endpoints.find(e => e.name === name);
}
@@ -200,7 +137,7 @@ export class ControllerModel {
return this._registrations;
}
public get controllerConfig(): azdataExt.DcConfigShowResult | undefined {
public get controllerConfig(): azExt.DcConfigShowResult | undefined {
return this._controllerConfig;
}
@@ -214,6 +151,6 @@ export class ControllerModel {
* property to for use a display label for this controller
*/
public get label(): string {
return `${this.info.name} (${this.controllerContext})`;
return `${this.info.name}`;
}
}

View File

@@ -5,7 +5,7 @@
import { MiaaResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as vscode from 'vscode';
import { UserCancelledError } from '../common/api';
import { Deferred } from '../common/promise';
@@ -20,12 +20,12 @@ export type DatabaseModel = { name: string, status: string };
export class MiaaModel extends ResourceModel {
private _config: azdataExt.SqlMiShowResult | undefined;
private _config: azExt.SqlMiShowResult | undefined;
private _databases: DatabaseModel[] = [];
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.SqlMiShowResult | undefined>();
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.SqlMiShowResult | undefined>();
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
public onConfigUpdated = this._onConfigUpdated.event;
public onDatabasesUpdated = this._onDatabasesUpdated.event;
public configLastUpdated: Date | undefined;
@@ -35,7 +35,7 @@ export class MiaaModel extends ResourceModel {
constructor(_controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
super(_controllerModel, _miaaInfo, registration);
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
}
/**
@@ -48,7 +48,7 @@ export class MiaaModel extends ResourceModel {
/**
* The status of this instance
*/
public get config(): azdataExt.SqlMiShowResult | undefined {
public get config(): azExt.SqlMiShowResult | undefined {
return this._config;
}
@@ -73,8 +73,8 @@ export class MiaaModel extends ResourceModel {
this._refreshPromise = new Deferred();
try {
try {
const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext);
this._config = result.result;
const result = await this._azApi.az.sql.miarc.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars);
this._config = result.stdout;
this.configLastUpdated = new Date();
this._onConfigUpdated.fire(this._config);
} catch (err) {

View File

@@ -5,7 +5,7 @@
import { PGResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as vscode from 'vscode';
import * as loc from '../localizedConstants';
import { ConnectToPGSqlDialog } from '../ui/dialogs/connectPGDialog';
@@ -27,12 +27,12 @@ export type EngineSettingsModel = {
};
export class PostgresModel extends ResourceModel {
private _config?: azdataExt.PostgresServerShowResult;
private _config?: azExt.PostgresServerShowResult;
public workerNodesEngineSettings: EngineSettingsModel[] = [];
public coordinatorNodeEngineSettings: EngineSettingsModel[] = [];
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.PostgresServerShowResult>();
public onConfigUpdated = this._onConfigUpdated.event;
public configLastUpdated?: Date;
public engineSettingsLastUpdated?: Date;
@@ -42,11 +42,11 @@ export class PostgresModel extends ResourceModel {
constructor(_controllerModel: ControllerModel, private _pgInfo: PGResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
super(_controllerModel, _pgInfo, registration);
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = <azExt.IExtension>vscode.extensions.getExtension(azExt.extension.name)?.exports;
}
/** Returns the configuration of Postgres */
public get config(): azdataExt.PostgresServerShowResult | undefined {
public get config(): azExt.PostgresServerShowResult | undefined {
return this._config;
}
@@ -118,7 +118,7 @@ export class PostgresModel extends ResourceModel {
}
this._refreshPromise = new Deferred();
try {
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, this.controllerModel.controllerContext)).result;
this._config = (await this._azApi.az.postgres.arcserver.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars)).stdout;
this.configLastUpdated = new Date();
this._onConfigUpdated.fire(this._config);
this._refreshPromise.resolve();

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arc from 'arc';
import * as azdata from 'azdata';
import * as rd from 'resource-deployment';
import { getControllerPassword, getRegisteredDataControllers, reacquireControllerPassword } from '../common/api';
import { getRegisteredDataControllers } from '../common/api';
import { throwUnless } from '../common/utils';
import * as loc from '../localizedConstants';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
@@ -31,32 +30,17 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
switch (variableName) {
case 'namespace': return controller.info.namespace || '';
case 'endpoint': return controller.info.endpoint || '';
case 'username': return controller.info.username;
case 'kubeConfig': return controller.info.kubeConfigFilePath;
case 'clusterContext': return controller.info.kubeClusterContext;
case 'password': return this.getPassword(controller);
default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
}
}
private async getPassword(controller: arc.DataController): Promise<string> {
let password = await getControllerPassword(this._treeProvider, controller.info);
if (!password) {
password = await reacquireControllerPassword(this._treeProvider, controller.info);
}
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
return password;
}
public getIsPassword(variableName: string): boolean {
switch (variableName) {
case 'namespace': return false;
case 'endpoint': return false;
case 'username': return false;
case 'kubeConfig': return false;
case 'clusterContext': return false;
case 'password': return true;
default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
}
}

View File

@@ -1,97 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
/**
* Simple fake Azdata Api used to mock the API during tests
*/
export class FakeAzdataApi implements azdataExt.IAzdataApi {
private _arcApi = {
dc: {
create(_namespace: string, _name: string, _connectivityMode: string, _resourceGroup: string, _location: string, _subscription: string, _profileName?: string, _storageClass?: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
endpoint: {
async list(): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> { return <any>{ result: [] }; }
},
config: {
list(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> { throw new Error('Method not implemented.'); },
async show(): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> { return <any>{ result: undefined! }; }
}
},
postgres: {
server: {
postgresInstances: <azdataExt.PostgresServerListResult[]>[],
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> { return { result: this.postgresInstances, logs: [], stdout: [], stderr: [] }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> { throw new Error('Method not implemented.'); },
edit(
_name: string,
_args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
coordinatorEngineSettings?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workerEngineSettings?: string,
workers?: number
},
_additionalEnvVars?: azdataExt.AdditionalEnvVars
): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
}
},
sql: {
mi: {
miaaInstances: <azdataExt.SqlMiListResult[]>[],
delete(_name: string): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); },
async list(): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> { return { logs: [], stdout: [], stderr: [], result: this.miaaInstances }; },
show(_name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> { throw new Error('Method not implemented.'); },
edit(
_name: string,
_args: {
coresLimit?: string,
coresRequest?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean
}): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
}
}
};
public set postgresInstances(instances: azdataExt.PostgresServerListResult[]) {
this._arcApi.postgres.server.postgresInstances = instances;
}
public set miaaInstances(instances: azdataExt.SqlMiListResult[]) {
this._arcApi.sql.mi.miaaInstances = instances;
}
//
// API Implementation
//
public get arc() {
return this._arcApi;
}
getPath(): Promise<string> {
throw new Error('Method not implemented.');
}
login(_endpointOrNamespace: azdataExt.EndpointOrNamespace, _username: string, _password: string, _additionalEnvVars: azdataExt.AdditionalEnvVars = {}, _azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
return <any>undefined;
}
version(): Promise<azdataExt.AzdataOutput<string>> {
throw new Error('Method not implemented.');
}
getSemVersion(): any {
throw new Error('Method not implemented.');
}
}

View File

@@ -10,9 +10,9 @@ import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider
export class FakeControllerModel extends ControllerModel {
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>) {
const _info: ControllerInfo = Object.assign({ id: uuid(), endpoint: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', namespace: '', username: '', rememberPassword: false, resources: [] }, info);
super(treeDataProvider!, _info, password);
super(treeDataProvider!, _info);
}
}

View File

@@ -1,221 +1,221 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import { ControllerInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as should from 'should';
import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode';
import * as loc from '../../localizedConstants';
import * as kubeUtils from '../../common/kubeUtils';
import { UserCancelledError } from '../../common/api';
import { ControllerModel } from '../../models/controllerModel';
import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
// import { ControllerInfo } from 'arc';
// import * as azdata from 'azdata';
// import * as azExt from 'azdata-ext';
// import * as should from 'should';
// import * as sinon from 'sinon';
// import * as TypeMoq from 'typemoq';
// import { v4 as uuid } from 'uuid';
// import * as vscode from 'vscode';
// import * as loc from '../../localizedConstants';
// import * as kubeUtils from '../../common/kubeUtils';
// import { UserCancelledError } from '../../common/api';
// import { ControllerModel } from '../../models/controllerModel';
// import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
// import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
interface ExtensionGlobalMemento extends vscode.Memento {
setKeysForSync(keys: string[]): void;
}
// interface ExtensionGlobalMemento extends vscode.Memento {
// setKeysForSync(keys: string[]): void;
// }
function getDefaultControllerInfo(): ControllerInfo {
return {
id: uuid(),
endpoint: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'admin',
name: 'arc',
namespace: 'arc-ns',
rememberPassword: true,
resources: []
};
}
// function getDefaultControllerInfo(): ControllerInfo {
// return {
// id: uuid(),
// endpoint: '127.0.0.1',
// kubeConfigFilePath: '/path/to/.kube/config',
// kubeClusterContext: 'currentCluster',
// username: 'admin',
// name: 'arc',
// namespace: 'arc-ns',
// rememberPassword: true,
// resources: []
// };
// }
describe('ControllerModel', function (): void {
afterEach(function (): void {
sinon.restore();
});
// describe('ControllerModel', function (): void {
// afterEach(function (): void {
// sinon.restore();
// });
describe('azdataLogin', function (): void {
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockGlobalState: TypeMoq.IMock<ExtensionGlobalMemento>;
// describe('azdataLogin', function (): void {
// let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
// let mockGlobalState: TypeMoq.IMock<ExtensionGlobalMemento>;
before(function (): void {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
});
// before(function (): void {
// mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
// mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
// mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
// });
beforeEach(function (): void {
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
});
// beforeEach(function (): void {
// sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
// sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
// sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
// });
it('Rejected with expected error when user cancels', async function (): Promise<void> {
// Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
});
// it('Rejected with expected error when user cancels', async function (): Promise<void> {
// // Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
// sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
// await should(model.login()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
// });
it('Reads password from cred store', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Test password, not actually used")]
// it('Reads password from cred store', async function (): Promise<void> {
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Test password, not actually used")]
// Set up cred store to return our password
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: password }));
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
// // Set up cred store to return our password
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: password }));
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.login();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
// await model.login();
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
// });
it('Prompt for password when not in cred store', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// it('Prompt for password when not in cred store', async function (): Promise<void> {
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return empty password
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: '' }));
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
// // Set up cred store to return empty password
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: '' }));
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
// Set up dialog to return new model with our password
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// // Set up dialog to return new model with our password
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
// sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.login();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
// await model.login();
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
// });
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
// it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// // Set up cred store to return a password to start with
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// // Set up dialog to return new model with our new password from the reprompt
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo());
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
// await model.login(true);
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
// });
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
// it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
// const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// // Set up cred store to return a password to start with
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// // Set up dialog to return new model with our new password from the reprompt
// const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), password);
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// Set up original model with a password
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
// // Set up original model with a password
// const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), getDefaultControllerInfo(), 'originalPassword');
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
// await model.login(true);
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
// azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
// });
it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
const treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
// it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
// const treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
// Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
credProviderMock.setup((x: any) => x.then).returns(() => undefined);
sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
// // Set up cred store to return a password to start with
// const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
// credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
// // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
// credProviderMock.setup((x: any) => x.then).returns(() => undefined);
// sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object));
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// const azExtApiMock = TypeMoq.Mock.ofType<azExt.IExtension>();
// const azdataMock = TypeMoq.Mock.ofType<azExt.IAzdataApi>();
// azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
// azExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExtApiMock.object });
// Add existing model to provider
const originalPassword = 'originalPassword';
const model = new ControllerModel(
treeDataProvider,
getDefaultControllerInfo(),
originalPassword
);
await treeDataProvider.addOrUpdateController(model, originalPassword);
// // Add existing model to provider
// const originalPassword = 'originalPassword';
// const model = new ControllerModel(
// treeDataProvider,
// getDefaultControllerInfo(),
// originalPassword
// );
// await treeDataProvider.addOrUpdateController(model, originalPassword);
const newInfo: ControllerInfo = {
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
endpoint: 'newUrl',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'newUser',
name: 'newName',
namespace: 'newNamespace',
rememberPassword: true,
resources: []
};
const newPassword = 'newPassword';
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(
treeDataProvider,
newInfo,
newPassword);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
{ controllerModel: newModel, password: newPassword }));
// const newInfo: ControllerInfo = {
// id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
// endpoint: 'newUrl',
// kubeConfigFilePath: '/path/to/.kube/config',
// kubeClusterContext: 'currentCluster',
// username: 'newUser',
// name: 'newName',
// namespace: 'newNamespace',
// rememberPassword: true,
// resources: []
// };
// const newPassword = 'newPassword';
// // Set up dialog to return new model with our new password from the reprompt
// const newModel = new ControllerModel(
// treeDataProvider,
// newInfo,
// newPassword);
// const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
// { controllerModel: newModel, password: newPassword }));
await model.login(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
should(model.info).deepEqual(newInfo, 'Model info should have been updated');
// await model.login(true);
// should(waitForCloseStub.called).be.true('waitForClose should have been called');
// should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
// should(model.info).deepEqual(newInfo, 'Model info should have been updated');
});
// });
});
// });
});
// });

File diff suppressed because it is too large Load Diff

View File

@@ -1,80 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import { PGResourceInfo, ResourceType } from 'arc';
import * as azdataExt from 'azdata-ext';
import * as should from 'should';
import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode';
import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
import { ControllerModel, Registration } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel';
import { PostgresConnectionStringsPage } from '../../../ui/dashboards/postgres/postgresConnectionStringsPage';
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
import { FakePostgresServerShowOutput } from '../../models/postgresModel.test';
// import { PGResourceInfo, ResourceType } from 'arc';
// import * as azExt from 'azdata-ext';
// import * as should from 'should';
// import * as sinon from 'sinon';
// import * as TypeMoq from 'typemoq';
// import * as vscode from 'vscode';
// import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
// import { ControllerModel, Registration } from '../../../models/controllerModel';
// import { PostgresModel } from '../../../models/postgresModel';
// import { PostgresConnectionStringsPage } from '../../../ui/dashboards/postgres/postgresConnectionStringsPage';
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
// import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
// import { FakePostgresServerShowOutput } from '../../models/postgresModel.test';
describe('postgresConnectionStringsPage', function (): void {
let controllerModel: ControllerModel;
let postgresModel: PostgresModel;
let azdataApi: azdataExt.IAzdataApi;
let postgresConnectionStrings: PostgresConnectionStringsPage;
// describe('postgresConnectionStringsPage', function (): void {
// let controllerModel: ControllerModel;
// let postgresModel: PostgresModel;
// let azdataApi: azExt.IAzdataApi;
// let postgresConnectionStrings: PostgresConnectionStringsPage;
afterEach(function (): void {
sinon.restore();
});
// afterEach(function (): void {
// sinon.restore();
// });
beforeEach(async () => {
// Stub the azdata CLI API
azdataApi = new FakeAzdataApi();
const azdataExt = TypeMoq.Mock.ofType<azdataExt.IExtension>();
azdataExt.setup(x => x.azdata).returns(() => azdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExt.object });
// beforeEach(async () => {
// // Stub the azdata CLI API
// azdataApi = new FakeAzdataApi();
// const azExt = TypeMoq.Mock.ofType<azExt.IExtension>();
// azExt.setup(x => x.azdata).returns(() => azdataApi);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExt.object });
// Setup Controller Model
controllerModel = new FakeControllerModel();
// // Setup Controller Model
// controllerModel = new FakeControllerModel();
//Stub calling azdata login and acquiring session
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
// // Setup PostgresModel
// const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };
// const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
// postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
// Setup PostgresModel
const postgresResource: PGResourceInfo = { name: 'pgt', resourceType: '' };
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object));
// // Setup stub of show call
// const postgresShow = sinon.stub().returns(FakePostgresServerShowOutput);
// sinon.stub(azdataApi, 'arc').get(() => {
// return { postgres: { server: { show(name: string) { return postgresShow(name); } } } };
// });
// Setup stub of show call
const postgresShow = sinon.stub().returns(FakePostgresServerShowOutput);
sinon.stub(azdataApi, 'arc').get(() => {
return { postgres: { server: { show(name: string) { return postgresShow(name); } } } };
});
// // Setup the PostgresConnectionsStringsPage
// let { modelViewMock } = createModelViewMock();
// postgresConnectionStrings = new PostgresConnectionStringsPage(modelViewMock.object, undefined!, postgresModel);
// });
// Setup the PostgresConnectionsStringsPage
let { modelViewMock } = createModelViewMock();
postgresConnectionStrings = new PostgresConnectionStringsPage(modelViewMock.object, undefined!, postgresModel);
});
// describe('getConnectionStrings', function (): void {
describe('getConnectionStrings', function (): void {
// it('Strings container should be empty since postgres model has not been refreshed', async function (): Promise<void> {
// should(postgresConnectionStrings['getConnectionStrings']()).be.empty();
// });
it('Strings container should be empty since postgres model has not been refreshed', async function (): Promise<void> {
should(postgresConnectionStrings['getConnectionStrings']()).be.empty();
});
// it('String contain correct ip and port', async function (): Promise<void> {
// // Call to provide external endpoint
// await postgresModel.refresh();
it('String contain correct ip and port', async function (): Promise<void> {
// Call to provide external endpoint
await postgresModel.refresh();
// let endpoint = FakePostgresServerShowOutput.result.status.primaryEndpoint.split(':');
let endpoint = FakePostgresServerShowOutput.result.status.primaryEndpoint.split(':');
// postgresConnectionStrings['getConnectionStrings']().forEach(k => {
// should(k.value.includes(endpoint[0])).be.True();
// should(k.value.includes(endpoint[1])).be.True();
// });
// });
postgresConnectionStrings['getConnectionStrings']().forEach(k => {
should(k.value.includes(endpoint[0])).be.True();
should(k.value.includes(endpoint[1])).be.True();
});
});
// });
});
});
// });

View File

@@ -1,110 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import * as azdataExt from 'azdata-ext';
import * as utils from '../../../common/utils';
import * as loc from '../../../localizedConstants';
import { Deferred } from '../../../common/promise';
import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
import { StubButton } from '@microsoft/azdata-test/out/stubs/modelView/stubButton';
import { PGResourceInfo, ResourceType } from 'arc';
import { PostgresOverviewPage } from '../../../ui/dashboards/postgres/postgresOverviewPage';
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
import { PostgresModel } from '../../../models/postgresModel';
import { ControllerModel, Registration } from '../../../models/controllerModel';
// import * as vscode from 'vscode';
// import * as sinon from 'sinon';
// import * as TypeMoq from 'typemoq';
// import * as azExt from 'azdata-ext';
// import * as utils from '../../../common/utils';
// import * as loc from '../../../localizedConstants';
// import { Deferred } from '../../../common/promise';
// import { createModelViewMock } from '@microsoft/azdata-test/out/mocks/modelView/modelViewMock';
// import { StubButton } from '@microsoft/azdata-test/out/stubs/modelView/stubButton';
// import { PGResourceInfo, ResourceType } from 'arc';
// import { PostgresOverviewPage } from '../../../ui/dashboards/postgres/postgresOverviewPage';
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
// import { FakeAzApi } from '../../mocks/fakeAzdataApi';
// import { PostgresModel } from '../../../models/postgresModel';
// import { ControllerModel, Registration } from '../../../models/controllerModel';
describe('postgresOverviewPage', () => {
let postgresOverview: PostgresOverviewPage;
let azdataApi: azdataExt.IAzdataApi;
let controllerModel: ControllerModel;
let postgresModel: PostgresModel;
// describe('postgresOverviewPage', () => {
// let postgresOverview: PostgresOverviewPage;
// let azdataApi: azExt.IAzdataApi;
// let controllerModel: ControllerModel;
// let postgresModel: PostgresModel;
let showInformationMessage: sinon.SinonStub;
let showErrorMessage: sinon.SinonStub;
// let showInformationMessage: sinon.SinonStub;
// let showErrorMessage: sinon.SinonStub;
let informationMessageShown: Deferred;
let errorMessageShown: Deferred;
// let informationMessageShown: Deferred;
// let errorMessageShown: Deferred;
beforeEach(async () => {
// Stub the azdata CLI API
azdataApi = new FakeAzdataApi();
const azdataExt = TypeMoq.Mock.ofType<azdataExt.IExtension>();
azdataExt.setup(x => x.azdata).returns(() => azdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExt.object });
// beforeEach(async () => {
// // Stub the azdata CLI API
// azdataApi = new FakeAzdataApi();
// const azExt = TypeMoq.Mock.ofType<azExt.IExtension>();
// azExt.setup(x => x.azdata).returns(() => azdataApi);
// sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azExt.object });
// Stub the window UI
informationMessageShown = new Deferred();
showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').callsFake(
(_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
informationMessageShown.resolve();
return Promise.resolve(undefined);
});
// // Stub the window UI
// informationMessageShown = new Deferred();
// showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').callsFake(
// (_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
// informationMessageShown.resolve();
// return Promise.resolve(undefined);
// });
errorMessageShown = new Deferred();
showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').callsFake(
(_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
errorMessageShown.resolve();
return Promise.resolve(undefined);
});
// errorMessageShown = new Deferred();
// showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').callsFake(
// (_: string, __: vscode.MessageOptions, ...___: vscode.MessageItem[]) => {
// errorMessageShown.resolve();
// return Promise.resolve(undefined);
// });
// Setup the PostgresModel
controllerModel = new FakeControllerModel();
const postgresResource: PGResourceInfo = { name: 'my-pg', resourceType: '' };
const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
const treeDataProvider = new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object);
postgresModel = new PostgresModel(controllerModel, postgresResource, registration, treeDataProvider);
// // Setup the PostgresModel
// controllerModel = new FakeControllerModel();
// const postgresResource: PGResourceInfo = { name: 'my-pg', resourceType: '' };
// const registration: Registration = { instanceName: '', state: '', instanceType: ResourceType.postgresInstances };
// const treeDataProvider = new AzureArcTreeDataProvider(TypeMoq.Mock.ofType<vscode.ExtensionContext>().object);
// postgresModel = new PostgresModel(controllerModel, postgresResource, registration, treeDataProvider);
// Setup the PostgresOverviewPage
const { modelViewMock } = createModelViewMock();
postgresOverview = new PostgresOverviewPage(modelViewMock.object, undefined!, controllerModel, postgresModel);
// Call the getter to initialize toolbar, but we don't need to use it for anything
// eslint-disable-next-line code-no-unused-expressions
postgresOverview['toolbarContainer'];
});
// // Setup the PostgresOverviewPage
// const { modelViewMock } = createModelViewMock();
// postgresOverview = new PostgresOverviewPage(modelViewMock.object, undefined!, controllerModel, postgresModel);
// // Call the getter to initialize toolbar, but we don't need to use it for anything
// // eslint-disable-next-line code-no-unused-expressions
// postgresOverview['toolbarContainer'];
// });
afterEach(() => {
sinon.restore();
});
// afterEach(() => {
// sinon.restore();
// });
describe('delete button', () => {
let refreshTreeNode: sinon.SinonStub;
// describe('delete button', () => {
// let refreshTreeNode: sinon.SinonStub;
beforeEach(() => {
sinon.stub(utils, 'promptForInstanceDeletion').returns(Promise.resolve(true));
sinon.stub(controllerModel, 'login').returns(Promise.resolve());
refreshTreeNode = sinon.stub(controllerModel, 'refreshTreeNode');
});
// beforeEach(() => {
// sinon.stub(utils, 'promptForInstanceDeletion').returns(Promise.resolve(true));
// refreshTreeNode = sinon.stub(controllerModel, 'refreshTreeNode');
// });
it('deletes Postgres on success', async () => {
// Stub 'azdata arc postgres server delete' to return success
const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete');
// it('deletes Postgres on success', async () => {
// // Stub 'azdata arc postgres server delete' to return success
// const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete');
(postgresOverview['deleteButton'] as StubButton).click();
await informationMessageShown;
sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
sinon.assert.calledOnceWithExactly(showInformationMessage, loc.instanceDeleted(postgresModel.info.name));
sinon.assert.notCalled(showErrorMessage);
sinon.assert.calledOnce(refreshTreeNode);
});
// (postgresOverview['deleteButton'] as StubButton).click();
// await informationMessageShown;
// sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
// sinon.assert.calledOnceWithExactly(showInformationMessage, loc.instanceDeleted(postgresModel.info.name));
// sinon.assert.notCalled(showErrorMessage);
// sinon.assert.calledOnce(refreshTreeNode);
// });
it('shows an error message on failure', async () => {
// Stub 'azdata arc postgres server delete' to throw an exception
const error = new Error('something bad happened');
const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete').throws(error);
// it('shows an error message on failure', async () => {
// // Stub 'azdata arc postgres server delete' to throw an exception
// const error = new Error('something bad happened');
// const postgresDeleteStub = sinon.stub(azdataApi.arc.postgres.server, 'delete').throws(error);
(postgresOverview['deleteButton'] as StubButton).click();
await errorMessageShown;
sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
sinon.assert.notCalled(showInformationMessage);
sinon.assert.calledOnceWithExactly(showErrorMessage, loc.instanceDeletionFailed(postgresModel.info.name, error.message));
sinon.assert.notCalled(refreshTreeNode);
});
});
});
// (postgresOverview['deleteButton'] as StubButton).click();
// await errorMessageShown;
// sinon.assert.calledOnceWithExactly(postgresDeleteStub, postgresModel.info.name, sinon.match.any, sinon.match.any);
// sinon.assert.notCalled(showInformationMessage);
// sinon.assert.calledOnceWithExactly(showErrorMessage, loc.instanceDeletionFailed(postgresModel.info.name, error.message));
// sinon.assert.notCalled(refreshTreeNode);
// });
// });
// });

View File

@@ -1,97 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import { ControllerInfo } from 'arc';
import * as should from 'should';
import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid';
import * as loc from '../../../localizedConstants';
import { ControllerModel } from '../../../models/controllerModel';
import { ConnectToControllerDialog } from '../../../ui/dialogs/connectControllerDialog';
// import { ControllerInfo } from 'arc';
// import * as should from 'should';
// import * as sinon from 'sinon';
// import { v4 as uuid } from 'uuid';
// import * as loc from '../../../localizedConstants';
// import { ControllerModel } from '../../../models/controllerModel';
// import { ConnectToControllerDialog } from '../../../ui/dialogs/connectControllerDialog';
describe('ConnectControllerDialog', function (): void {
afterEach(function (): void {
sinon.restore();
});
// describe('ConnectControllerDialog', function (): void {
// afterEach(function (): void {
// sinon.restore();
// });
(<{ info: ControllerInfo | undefined, description: string }[]>[
{ info: undefined, description: 'all input' },
{ info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
{ info: { endpoint: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
connectControllerDialog.showDialog(test.info, undefined);
await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate();
should(validateResult).be.false();
});
});
// (<{ info: ControllerInfo | undefined, description: string }[]>[
// { info: undefined, description: 'all input' },
// { info: { endpoint: '127.0.0.1' }, description: 'all but URL' },
// { info: { endpoint: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => {
// it(`Validate returns false when ${test.description} is empty`, async function (): Promise<void> {
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
// connectControllerDialog.showDialog(test.info, undefined);
// await connectControllerDialog.isInitialized;
// const validateResult = await connectControllerDialog.validate();
// should(validateResult).be.false();
// });
// });
it('validate returns false if controller refresh fails', async function (): Promise<void> {
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
const info: ControllerInfo = { id: uuid(), endpoint: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] };
connectControllerDialog.showDialog(info, 'pwd');
await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate();
should(validateResult).be.false('Validation should have returned false');
});
// it('validate returns false if controller refresh fails', async function (): Promise<void> {
// sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
// const info: ControllerInfo = { id: uuid(), endpoint: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] };
// connectControllerDialog.showDialog(info, 'pwd');
// await connectControllerDialog.isInitialized;
// const validateResult = await connectControllerDialog.validate();
// should(validateResult).be.false('Validation should have returned false');
// });
it('validate replaces http with https', async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
// it('validate replaces http with https', async function (): Promise<void> {
// await validateConnectControllerDialog(
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30081');
// });
it('validate appends https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
// it('validate appends https if missing', async function (): Promise<void> {
// await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30080');
// });
it('validate appends default port if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), endpoint: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
// it('validate appends default port if missing', async function (): Promise<void> {
// await validateConnectControllerDialog({ id: uuid(), endpoint: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30080');
// });
it('validate appends both port and https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
// it('validate appends both port and https if missing', async function (): Promise<void> {
// await validateConnectControllerDialog({ id: uuid(), endpoint: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30080');
// });
for (const name of ['', undefined]) {
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
}
// for (const name of ['', undefined]) {
// it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
// await validateConnectControllerDialog(
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30081');
// });
// }
it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081',
undefined);
});
});
// it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
// await validateConnectControllerDialog(
// { id: uuid(), endpoint: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', namespace: 'arc-ns', username: 'sa', rememberPassword: true, resources: [] },
// 'https://127.0.0.1:30081',
// undefined);
// });
// });
async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl: string, arcInstanceName: string = 'arc-instance'): Promise<void> {
const expectedControllerInfoName = info.name || arcInstanceName || loc.defaultControllerName;
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
// Stub out refresh calls to controllerModel - we'll test those separately
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.resolve());
// stub out controller registration response to return a known instanceName for the dc.
/*
sinon.stub(ControllerModel.prototype, 'controllerRegistration').get(() => {
return <Registration>{ instanceName: arcInstanceName };
});
*/
connectControllerDialog.showDialog(info, 'pwd');
await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate();
should(validateResult).be.true('Validation should have returned true');
const model = await connectControllerDialog.waitForClose();
should(model?.controllerModel.info.endpoint).equal(expectedUrl);
should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
}
// async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl: string, arcInstanceName: string = 'arc-instance'): Promise<void> {
// const expectedControllerInfoName = info.name || arcInstanceName || loc.defaultControllerName;
// const connectControllerDialog = new ConnectToControllerDialog(undefined!);
// // Stub out refresh calls to controllerModel - we'll test those separately
// sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.resolve());
// // stub out controller registration response to return a known instanceName for the dc.
// /*
// sinon.stub(ControllerModel.prototype, 'controllerRegistration').get(() => {
// return <Registration>{ instanceName: arcInstanceName };
// });
// */
// connectControllerDialog.showDialog(info, 'pwd');
// await connectControllerDialog.isInitialized;
// const validateResult = await connectControllerDialog.validate();
// should(validateResult).be.true('Validation should have returned true');
// const model = await connectControllerDialog.waitForClose();
// should(model?.controllerModel.info.endpoint).equal(expectedUrl);
// should(model?.controllerModel.info.name).equal(expectedControllerInfoName);
// }

View File

@@ -1,184 +1,184 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import { ControllerInfo, ResourceType } from 'arc';
import 'mocha';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode';
import * as azdataExt from 'azdata-ext';
import * as kubeUtils from '../../../common/kubeUtils';
import { ControllerModel } from '../../../models/controllerModel';
import { MiaaModel } from '../../../models/miaaModel';
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
import { FakeControllerModel } from '../../mocks/fakeControllerModel';
import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
// import { ControllerInfo, ResourceType } from 'arc';
// import 'mocha';
// import * as should from 'should';
// import * as TypeMoq from 'typemoq';
// import * as sinon from 'sinon';
// import { v4 as uuid } from 'uuid';
// import * as vscode from 'vscode';
// import * as azdataExt from 'azdata-ext';
// import * as kubeUtils from '../../../common/kubeUtils';
// import { ControllerModel } from '../../../models/controllerModel';
// import { MiaaModel } from '../../../models/miaaModel';
// import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
// import { ControllerTreeNode } from '../../../ui/tree/controllerTreeNode';
// import { MiaaTreeNode } from '../../../ui/tree/miaaTreeNode';
// import { FakeControllerModel } from '../../mocks/fakeControllerModel';
// import { FakeAzdataApi } from '../../mocks/fakeAzdataApi';
interface ExtensionGlobalMemento extends vscode.Memento {
setKeysForSync(keys: string[]): void;
}
// interface ExtensionGlobalMemento extends vscode.Memento {
// setKeysForSync(keys: string[]): void;
// }
function getDefaultControllerInfo(): ControllerInfo {
return {
id: uuid(),
endpoint: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'sa',
name: 'my-arc',
namespace: 'arc-ns',
rememberPassword: true,
resources: []
};
}
// function getDefaultControllerInfo(): ControllerInfo {
// return {
// id: uuid(),
// endpoint: '127.0.0.1',
// kubeConfigFilePath: '/path/to/.kube/config',
// kubeClusterContext: 'currentCluster',
// username: 'sa',
// name: 'my-arc',
// namespace: 'arc-ns',
// rememberPassword: true,
// resources: []
// };
// }
describe('AzureArcTreeDataProvider tests', function (): void {
let treeDataProvider: AzureArcTreeDataProvider;
beforeEach(function (): void {
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
//treeDataProviderMock = TypeMoq.Mock.ofType<AzureArcTreeDataProvider>();
treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
});
// describe('AzureArcTreeDataProvider tests', function (): void {
// let treeDataProvider: AzureArcTreeDataProvider;
// beforeEach(function (): void {
// const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
// const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
// mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
// mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
// //treeDataProviderMock = TypeMoq.Mock.ofType<AzureArcTreeDataProvider>();
// treeDataProvider = new AzureArcTreeDataProvider(mockExtensionContext.object);
// });
describe('addOrUpdateController', function (): void {
it('Multiple Controllers are added correctly', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const controllerModel = new FakeControllerModel();
await treeDataProvider.addOrUpdateController(controllerModel, '');
children = await treeDataProvider.getChildren();
should(children.length).equal(1, 'Controller node should be added correctly');
// describe('addOrUpdateController', function (): void {
// it('Multiple Controllers are added correctly', async function (): Promise<void> {
// treeDataProvider['_loading'] = false;
// let children = await treeDataProvider.getChildren();
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
// const controllerModel = new FakeControllerModel();
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// children = await treeDataProvider.getChildren();
// should(children.length).equal(1, 'Controller node should be added correctly');
// Add a couple more
const controllerModel2 = new FakeControllerModel();
const controllerModel3 = new FakeControllerModel();
await treeDataProvider.addOrUpdateController(controllerModel2, '');
await treeDataProvider.addOrUpdateController(controllerModel3, '');
children = await treeDataProvider.getChildren();
should(children.length).equal(3, 'Additional Controller nodes should be added correctly');
});
// // Add a couple more
// const controllerModel2 = new FakeControllerModel();
// const controllerModel3 = new FakeControllerModel();
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
// await treeDataProvider.addOrUpdateController(controllerModel3, '');
// children = await treeDataProvider.getChildren();
// should(children.length).equal(3, 'Additional Controller nodes should be added correctly');
// });
it('Adding a Controller more than once doesn\'t create duplicates', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
});
// it('Adding a Controller more than once doesn\'t create duplicates', async function (): Promise<void> {
// treeDataProvider['_loading'] = false;
// let children = await treeDataProvider.getChildren();
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// should(children.length).equal(1, 'Controller node should be added correctly');
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
// });
it('Updating an existing controller works as expected', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const originalInfo: ControllerInfo = getDefaultControllerInfo();
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
const newInfo: ControllerInfo = { id: originalInfo.id, endpoint: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', namespace: 'new-namespace', username: 'admin', rememberPassword: false, resources: [] };
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
await treeDataProvider.addOrUpdateController(controllerModel2, '');
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
should((<ControllerTreeNode>children[0]).model.info).deepEqual(newInfo);
});
});
// it('Updating an existing controller works as expected', async function (): Promise<void> {
// treeDataProvider['_loading'] = false;
// let children = await treeDataProvider.getChildren();
// should(children.length).equal(0, 'There initially shouldn\'t be any children');
// const originalInfo: ControllerInfo = getDefaultControllerInfo();
// const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// should(children.length).equal(1, 'Controller node should be added correctly');
// should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
// const newInfo: ControllerInfo = { id: originalInfo.id, endpoint: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', namespace: 'new-namespace', username: 'admin', rememberPassword: false, resources: [] };
// const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
// should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
// should((<ControllerTreeNode>children[0]).model.info).deepEqual(newInfo);
// });
// });
describe('getChildren', function (): void {
it('should return an empty array before loading stored controllers is completed', async function (): Promise<void> {
treeDataProvider['_loading'] = true;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'While loading we should return an empty array');
});
// describe('getChildren', function (): void {
// it('should return an empty array before loading stored controllers is completed', async function (): Promise<void> {
// treeDataProvider['_loading'] = true;
// let children = await treeDataProvider.getChildren();
// should(children.length).equal(0, 'While loading we should return an empty array');
// });
it('should return no children after loading', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'After loading we should have 0 children');
});
// it('should return no children after loading', async function (): Promise<void> {
// treeDataProvider['_loading'] = false;
// let children = await treeDataProvider.getChildren();
// should(children.length).equal(0, 'After loading we should have 0 children');
// });
it('should return all children of controller after loading', async function (): Promise<void> {
const mockArcExtension = TypeMoq.Mock.ofType<vscode.Extension<any>>();
const mockArcApi = TypeMoq.Mock.ofType<azdataExt.IExtension>();
mockArcExtension.setup(x => x.exports).returns(() => {
return mockArcApi.object;
});
const fakeAzdataApi = new FakeAzdataApi();
const pgInstances = [{ name: 'pg1', state: '', workers: 0 }];
const miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
fakeAzdataApi.postgresInstances = pgInstances;
fakeAzdataApi.miaaInstances = miaaInstances;
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
// it('should return all children of controller after loading', async function (): Promise<void> {
// const mockArcExtension = TypeMoq.Mock.ofType<vscode.Extension<any>>();
// const mockArcApi = TypeMoq.Mock.ofType<azdataExt.IExtension>();
// mockArcExtension.setup(x => x.exports).returns(() => {
// return mockArcApi.object;
// });
// const fakeAzdataApi = new FakeAzdataApi();
// const pgInstances = [{ name: 'pg1', state: '', workers: 0 }];
// const miaaInstances = [{ name: 'miaa1', state: '', replicas: '', serverEndpoint: '' }];
// fakeAzdataApi.postgresInstances = pgInstances;
// fakeAzdataApi.miaaInstances = miaaInstances;
// mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
const children = await treeDataProvider.getChildren(controllerNode);
should(children.filter(c => c.label === pgInstances[0].name).length).equal(1, 'Should have a Postgres child');
should(children.filter(c => c.label === miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
should(children.length).equal(2, 'Should have exactly 2 children');
});
});
// sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
// sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').returns([{ name: 'currentCluster', isCurrentContext: true }]);
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo(), 'mypassword');
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// const controllerNode = treeDataProvider.getControllerNode(controllerModel);
// const children = await treeDataProvider.getChildren(controllerNode);
// should(children.filter(c => c.label === pgInstances[0].name).length).equal(1, 'Should have a Postgres child');
// should(children.filter(c => c.label === miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
// should(children.length).equal(2, 'Should have exactly 2 children');
// });
// });
describe('removeController', function (): void {
it('removing a controller should work as expected', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
const info2 = getDefaultControllerInfo();
info2.username = 'cloudsa';
const controllerModel2 = new ControllerModel(treeDataProvider, info2);
await treeDataProvider.addOrUpdateController(controllerModel, '');
await treeDataProvider.addOrUpdateController(controllerModel2, '');
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
await treeDataProvider.removeController(children[0]);
should((await treeDataProvider.getChildren()).length).equal(1, 'Node should have been removed');
await treeDataProvider.removeController(children[0]);
should((await treeDataProvider.getChildren()).length).equal(1, 'Removing same node again should do nothing');
await treeDataProvider.removeController(children[1]);
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node should work');
await treeDataProvider.removeController(children[1]);
should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
});
});
// describe('removeController', function (): void {
// it('removing a controller should work as expected', async function (): Promise<void> {
// treeDataProvider['_loading'] = false;
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
// const info2 = getDefaultControllerInfo();
// info2.username = 'cloudsa';
// const controllerModel2 = new ControllerModel(treeDataProvider, info2);
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// await treeDataProvider.addOrUpdateController(controllerModel2, '');
// const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
// await treeDataProvider.removeController(children[0]);
// should((await treeDataProvider.getChildren()).length).equal(1, 'Node should have been removed');
// await treeDataProvider.removeController(children[0]);
// should((await treeDataProvider.getChildren()).length).equal(1, 'Removing same node again should do nothing');
// await treeDataProvider.removeController(children[1]);
// should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node should work');
// await treeDataProvider.removeController(children[1]);
// should((await treeDataProvider.getChildren()).length).equal(0, 'Removing other node again should do nothing');
// });
// });
describe('openResourceDashboard', function (): void {
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
// describe('openResourceDashboard', function (): void {
// it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
// const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
// await should(openDashboardPromise).be.rejected();
// });
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
await treeDataProvider.addOrUpdateController(controllerModel, '');
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
// it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
// await should(openDashboardPromise).be.rejected();
// });
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
const resourceNode = new MiaaTreeNode(miaaModel, controllerModel);
sinon.stub(controllerNode, 'getResourceNode').returns(resourceNode);
const showDashboardStub = sinon.stub(resourceNode, 'openDashboard');
await treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
should(showDashboardStub.calledOnce).be.true('showDashboard should have been called exactly once');
});
});
});
// it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
// const controllerModel = new ControllerModel(treeDataProvider, getDefaultControllerInfo());
// const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
// await treeDataProvider.addOrUpdateController(controllerModel, '');
// const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;
// const resourceNode = new MiaaTreeNode(miaaModel, controllerModel);
// sinon.stub(controllerNode, 'getResourceNode').returns(resourceNode);
// const showDashboardStub = sinon.stub(resourceNode, 'openDashboard');
// await treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
// should(showDashboardStub.calledOnce).be.true('showDashboard should have been called exactly once');
// });
// });
// });

View File

@@ -36,12 +36,9 @@ declare module 'arc' {
export type ControllerInfo = {
id: string,
kubeConfigFilePath: string,
kubeClusterContext: string
endpoint: string | undefined,
kubeClusterContext: string,
namespace: string,
name: string,
username: string,
rememberPassword: boolean,
resources: ResourceInfo[]
};
@@ -51,7 +48,5 @@ declare module 'arc' {
}
export interface IExtension {
getRegisteredDataControllers(): Promise<DataController[]>;
getControllerPassword(controllerInfo: ControllerInfo): Promise<string>;
reacquireControllerPassword(controllerInfo: ControllerInfo, password: string, retryCount?: number): Promise<string>;
}
}

View File

@@ -7,5 +7,5 @@
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../azdata/src/typings/azdata-ext.d.ts'/>
/// <reference path='../../../azcli/src/typings/az-ext.d.ts'/>
/// <reference path='../../../resource-deployment/src/typings/resource-deployment.d.ts'/>

View File

@@ -26,7 +26,7 @@ export class FilePicker {
) {
const buttonWidth = 80;
this.filePathInputBox = modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
.withProps({
value: initialPath,
ariaLabel: ariaLabel,
validationErrorMessage: validationErrorMessage,
@@ -43,7 +43,7 @@ export class FilePicker {
}).component();
this.filePickerButton = modelBuilder.button()
.withProperties<azdata.ButtonProperties>({
.withProps({
label: loc.browse,
width: buttonWidth,
secondary: true
@@ -89,5 +89,5 @@ function createFlexContainer(modelBuilder: azdata.ModelBuilder, items: azdata.Co
alignItems = alignItems || (rowLayout ? 'center' : undefined);
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px', } } : {};
const flexLayout: azdata.FlexLayout = { flexFlow: flexFlow, height: height, width: width, alignItems: alignItems };
return modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProperties<azdata.ComponentProperties>({ CSSStyles: cssStyles || {} }).component();
return modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProps({ CSSStyles: cssStyles || {} }).component();
}

View File

@@ -56,7 +56,7 @@ export abstract class KeyValue extends vscode.Disposable {
alignItems: 'center'
}).component();
const keyComponent = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const keyComponent = modelBuilder.text().withProps({
value: key,
CSSStyles: { ...cssStyles.text, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
@@ -80,7 +80,7 @@ export class TextKeyValue extends KeyValue {
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string) {
super(modelBuilder, key, value);
this.text = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
this.text = modelBuilder.text().withProps({
value: value,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
@@ -101,16 +101,17 @@ export abstract class BaseInputKeyValue extends KeyValue {
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, multiline: boolean) {
super(modelBuilder, key, value);
this.input = modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
this.input = modelBuilder.inputBox().withProps({
value: value,
readOnly: true,
multiline: multiline
multiline: multiline,
ariaLabel: loc.connectionString(key)
}).component();
const inputContainer = modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
inputContainer.addItem(this.input);
const copy = modelBuilder.button().withProperties<azdata.ButtonProperties>({
const copy = modelBuilder.button().withProps({
iconPath: IconPathHelper.copy,
width: '17px',
height: '17px',
@@ -153,7 +154,7 @@ export class LinkKeyValue extends KeyValue {
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, onClick: (e: any) => any) {
super(modelBuilder, key, value);
this.link = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
this.link = modelBuilder.hyperlink().withProps({
label: value,
url: ''
}).component();

View File

@@ -26,7 +26,7 @@ export class RadioOptionsGroup {
private _loadingCompleteMessage: string,
private _loadingCompleteErrorMessage: (error: any) => string
) {
this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
this._divContainer = this._modelBuilder.divContainer().withProps({ clickable: false }).component();
this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
}
@@ -42,7 +42,7 @@ export class RadioOptionsGroup {
const options = optionsInfo.values!;
let defaultValue: string = optionsInfo.defaultValue!;
options.forEach((option: string) => {
const radioOption = this._modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
const radioOption = this._modelBuilder.radioButton().withProps({
label: option,
checked: option === defaultValue,
name: this._groupName,

View File

@@ -18,7 +18,7 @@ export class ControllerDashboard extends Dashboard {
public override async showDashboard(): Promise<void> {
await super.showDashboard();
// Kick off the model refresh but don't wait on it since that's all handled with callbacks anyways
this._controllerModel.refresh(false).catch(err => console.log(`Error refreshing Controller dashboard ${err}`));
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing Controller dashboard ${err}`));
}
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {

View File

@@ -58,7 +58,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}
protected async refresh(): Promise<void> {
await this._controllerModel.refresh();
await this._controllerModel.refresh(false, this._controllerModel.info.namespace);
}
public get container(): azdata.Component {
@@ -67,8 +67,8 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
this._propertiesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
this._arcResourcesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
data: [],
this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProps({
dataValues: [],
columns: [
{
displayName: '',
@@ -126,7 +126,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
// Resources
const arcResourcesTitle = this.modelView.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: loc.arcResources })
.withProps({ value: loc.arcResources })
.component();
contentContainer.addItem(arcResourcesTitle, {
@@ -140,7 +140,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
public get toolbarContainer(): azdata.ToolbarContainer {
const newInstance = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const newInstance = this.modelView.modelBuilder.button().withProps({
label: loc.newInstance,
iconPath: IconPathHelper.add
}).component();
@@ -156,7 +156,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}));
// Refresh
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const refreshButton = this.modelView.modelBuilder.button().withProps({
label: loc.refresh,
iconPath: IconPathHelper.refresh
}).component();
@@ -173,7 +173,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}
}));
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
label: loc.openInAzurePortal,
iconPath: IconPathHelper.openInTab,
enabled: !!this._controllerModel.controllerConfig
@@ -190,7 +190,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}
}));
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
label: loc.troubleshoot,
iconPath: IconPathHelper.wrench
}).component();
@@ -224,12 +224,12 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
this.controllerProperties.instanceNamespace = config?.metadata.namespace || this.controllerProperties.instanceNamespace;
this.refreshDisplayedProperties();
this._arcResourcesTable.data = this._controllerModel.registrations
let registrations: (string | azdata.ImageComponent | azdata.HyperlinkComponent)[][] = this._controllerModel.registrations
.filter(r => r.instanceType !== ResourceType.dataControllers)
.map(r => {
const iconPath = getResourceTypeIcon(r.instanceType ?? '');
const imageComponent = this.modelView.modelBuilder.image()
.withProperties<azdata.ImageComponentProperties>({
.withProps({
width: iconSize,
height: iconSize,
iconPath: iconPath,
@@ -238,7 +238,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}).component();
const nameComponent = this.modelView.modelBuilder.hyperlink()
.withProperties<azdata.HyperlinkComponentProperties>({
.withProps({
label: r.instanceName || '',
url: ''
}).component();
@@ -249,6 +249,13 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
return [imageComponent, nameComponent, resourceTypeToDisplayName(r.instanceType), r.state];
});
let registrationsData = registrations.map(r => {
return r.map((value): azdata.DeclarativeTableCellValue => {
return { value: value };
});
});
this._arcResourcesTable.setDataValues(registrationsData);
this._arcResourcesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated;
}

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
@@ -30,11 +30,11 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
memoryRequest?: string
} = {};
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _miaaModel: MiaaModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this.initializeConfigurationBoxes();
@@ -59,33 +59,33 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.computeAndStorage,
CSSStyles: { ...cssStyles.title }
}).component());
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const infoComputeStorage_p1 = this.modelView.modelBuilder.text().withProps({
value: loc.miaaComputeAndStorageDescriptionPartOne,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'max-width': 'auto' }
}).component();
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProps({
label: loc.scalingCompute,
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-managed-instance',
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const infoComputeStorage_p4 = this.modelView.modelBuilder.text().withProps({
value: loc.computeAndStorageDescriptionPartFour,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const infoComputeStorage_p5 = this.modelView.modelBuilder.text().withProps({
value: loc.computeAndStorageDescriptionPartFive,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const infoComputeStorage_p6 = this.modelView.modelBuilder.text().withProps({
value: loc.computeAndStorageDescriptionPartSix,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
@@ -112,7 +112,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
protected get toolbarContainer(): azdata.ToolbarContainer {
// Save Edits
this.saveButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
this.saveButton = this.modelView.modelBuilder.button().withProps({
label: loc.saveText,
iconPath: IconPathHelper.save,
enabled: false
@@ -130,8 +130,8 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
},
async (_progress, _token): Promise<void> => {
try {
await this._azdataApi.azdata.arc.sql.mi.edit(
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.azdataAdditionalEnvVars, this._miaaModel.controllerModel.controllerContext);
await this._azApi.az.sql.miarc.edit(
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.info.namespace, this._miaaModel.controllerModel.azAdditionalEnvVars);
} catch (err) {
this.saveButton!.enabled = true;
throw err;
@@ -153,7 +153,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
}));
// Discard
this.discardButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
this.discardButton = this.modelView.modelBuilder.button().withProps({
label: loc.discardText,
iconPath: IconPathHelper.discard,
enabled: false
@@ -179,7 +179,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
}
private initializeConfigurationBoxes() {
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
min: 1,
inputType: 'number',
@@ -197,7 +197,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
})
);
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
min: 1,
inputType: 'number',
@@ -215,7 +215,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
})
);
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
this.memoryLimitBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
min: 2,
inputType: 'number',
@@ -233,7 +233,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
})
);
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
this.memoryRequestBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
min: 2,
inputType: 'number',
@@ -277,7 +277,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
alignItems: 'center'
}).component();
const keyComponent = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const keyComponent = this.modelView.modelBuilder.text().withProps({
value: `${key} :`,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();

View File

@@ -41,12 +41,12 @@ export class MiaaConnectionStringsPage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.connectionStrings,
CSSStyles: { ...cssStyles.title }
}).component());
const info = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const info = this.modelView.modelBuilder.text().withProps({
value: `${loc.selectConnectionString}`,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
@@ -60,7 +60,7 @@ export class MiaaConnectionStringsPage extends DashboardPage {
content.addItem(this._keyValueContainer.container);
this._connectionStringsMessage = this.modelView.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ CSSStyles: { 'text-align': 'center' } })
.withProps({ CSSStyles: { 'text-align': 'center' } })
.component();
content.addItem(this._connectionStringsMessage);

View File

@@ -21,7 +21,7 @@ export class MiaaDashboard extends Dashboard {
public override async showDashboard(): Promise<void> {
await super.showDashboard();
// Kick off the model refreshes but don't wait on it since that's all handled with callbacks anyways
this._controllerModel.refresh().catch(err => console.log(`Error refreshing controller model for MIAA dashboard ${err}`));
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing controller model for MIAA dashboard ${err}`));
this._miaaModel.refresh().catch(err => console.log(`Error refreshing MIAA model for MIAA dashboard ${err}`));
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as azurecore from 'azurecore';
import * as vscode from 'vscode';
import { getDatabaseStateDisplayText, promptForInstanceDeletion } from '../../../common/utils';
@@ -34,7 +34,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
private _connectToServerButton!: azdata.ButtonComponent;
private _databasesTableLoading!: azdata.LoadingComponent;
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
private readonly _azurecoreApi: azurecore.IExtension;
private _instanceProperties = {
@@ -50,7 +50,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
@@ -75,7 +75,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
}
protected async refresh(): Promise<void> {
await Promise.all([this._controllerModel.refresh(), this._miaaModel.refresh()]);
await Promise.all([this._controllerModel.refresh(false, this._controllerModel.info.namespace), this._miaaModel.refresh()]);
}
public get container(): azdata.Component {
@@ -112,7 +112,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
this._databasesContainer.addItem(this._connectToServerLoading, { CSSStyles: { 'margin-top': '20px' } });
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component();
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
columns: [
{
@@ -132,11 +132,11 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: []
dataValues: []
}).component();
this._databasesMessage = this.modelView.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ CSSStyles: { 'text-align': 'center' } })
.withProps({ CSSStyles: { 'text-align': 'center' } })
.component();
// Update loaded components with data
@@ -162,9 +162,9 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
// Service endpoints
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
columns: [
{
@@ -198,15 +198,15 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: [
[loc.kibanaDashboard, this._kibanaLoading, loc.kibanaDashboardDescription],
[loc.grafanaDashboard, this._grafanaLoading, loc.grafanaDashboardDescription]]
dataValues: [
[{ value: loc.kibanaDashboard }, { value: this._kibanaLoading }, { value: loc.kibanaDashboardDescription }],
[{ value: loc.grafanaDashboard }, { value: this._grafanaLoading }, { value: loc.grafanaDashboardDescription }]]
}).component();
rootContainer.addItem(endpointsTable);
// Databases
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component());
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.databases, CSSStyles: titleCSS }).component());
this.disposables.push(
this._connectToServerButton!.onDidClick(async () => {
this._connectToServerButton!.enabled = false;
@@ -227,7 +227,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
public get toolbarContainer(): azdata.ToolbarContainer {
const deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const deleteButton = this.modelView.modelBuilder.button().withProps({
label: loc.deleteText,
iconPath: IconPathHelper.delete
}).component();
@@ -244,7 +244,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
cancellable: false
},
async (_progress, _token) => {
return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
return await this._azApi.az.sql.miarc.delete(this._miaaModel.info.name, this._controllerModel.info.namespace, this._controllerModel.azAdditionalEnvVars);
}
);
await this._controllerModel.refreshTreeNode();
@@ -265,7 +265,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
}));
// Refresh
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const refreshButton = this.modelView.modelBuilder.button().withProps({
label: loc.refresh,
iconPath: IconPathHelper.refresh
}).component();
@@ -285,7 +285,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
}
}));
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
label: loc.openInAzurePortal,
iconPath: IconPathHelper.openInTab,
enabled: !!this._controllerModel.controllerConfig
@@ -302,7 +302,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
}
}));
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
label: loc.troubleshoot,
iconPath: IconPathHelper.wrench
}).component();
@@ -366,7 +366,13 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
// If we were able to get the databases it means we have a good connection so update the username too
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
this.refreshDisplayedProperties();
this._databasesTable.data = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
let databaseDisplayText = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
let databasesTextValues = databaseDisplayText.map(d => {
return d.map((value): azdata.DeclarativeTableCellValue => {
return { value: value };
});
});
this._databasesTable.setDataValues(databasesTextValues);
this._databasesTableLoading.loading = false;
if (this._miaaModel.databasesLastUpdated) {

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
@@ -45,11 +45,11 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
private discardButton!: azdata.ButtonComponent;
private saveButton!: azdata.ButtonComponent;
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this.initializeConfigurationBoxes();
@@ -88,7 +88,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProps({
label: loc.addingWorkerNodes,
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli',
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
@@ -99,7 +99,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
const memoryVCoreslink = this.modelView.modelBuilder.hyperlink().withProps({
label: loc.scalingCompute,
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli',
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
@@ -165,7 +165,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
},
async (_progress, _token): Promise<void> => {
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{
workers: this.saveArgs.workers,
@@ -174,7 +174,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
memoryRequest: this.schedulingParamsToEdit(this.saveArgs.memoryRequest!),
memoryLimit: this.schedulingParamsToEdit(this.saveArgs.memoryLimit!)
},
this._postgresModel.controllerModel.azdataAdditionalEnvVars);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
} catch (err) {
// If an error occurs while editing the instance then re-enable the save button since
// the edit wasn't successfully applied
@@ -479,21 +480,14 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const keyComponent = this.modelView.modelBuilder.text().withProps({
value: loc.workerNodeCount,
requiredIndicator: true,
description: loc.workerNodesInformation,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const keyContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
keyContainer.addItem(keyComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
const information = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.information,
title: loc.workerNodesInformation,
width: '15px',
height: '15px',
enabled: false
}).component();
keyContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
flexContainer.addItem(keyContainer, keyFlex);
const inputContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
@@ -547,6 +541,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
if (component.value === originalValue) {
return false;
} else if ((!component.valid)) {
this.discardButton.enabled = true;
return false;
} else {
this.saveButton.enabled = true;
@@ -575,21 +570,13 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const titleComponent = this.modelView.modelBuilder.text().withProps({
value: title,
description: description,
CSSStyles: { ...cssStyles.title, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const titleContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
titleContainer.addItem(titleComponent, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
const information = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.information,
title: description,
width: '15px',
height: '15px',
enabled: false
}).component();
titleContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
flexContainer.addItem(titleContainer, titleFlex);
let configurationSection = this.modelView.modelBuilder.divContainer().component();

View File

@@ -38,17 +38,17 @@ export class PostgresConnectionStringsPage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.connectionStrings,
CSSStyles: { ...cssStyles.title }
}).component());
const info = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
const info = this.modelView.modelBuilder.text().withProps({
value: loc.selectConnectionString,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const link = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
const link = this.modelView.modelBuilder.hyperlink().withProps({
label: loc.learnAboutPostgresClients,
url: 'https://docs.microsoft.com/azure/azure-arc/data/get-connection-endpoints-and-connection-strings-postgres-hyperscale',
}).component();
@@ -63,7 +63,7 @@ export class PostgresConnectionStringsPage extends DashboardPage {
this.connectionStringsLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.keyValueContainer.container)
.withProperties<azdata.LoadingComponentProperties>({
.withProps({
loading: !this._postgresModel.configLastUpdated
}).component();

View File

@@ -36,27 +36,27 @@ export class PostgresCoordinatorNodeParametersPage extends PostgresParametersPag
}
protected async saveParameterEdits(engineSettings: string): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ coordinatorEngineSettings: engineSettings },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
protected async resetAllParameters(): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ coordinatorEngineSettings: `''`, replaceEngineSettings: true },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
protected async resetParameter(parameterName: string): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ coordinatorEngineSettings: parameterName + '=' },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
}

View File

@@ -29,7 +29,7 @@ export class PostgresDashboard extends Dashboard {
await super.showDashboard();
// Kick off the model refresh but don't wait on it since that's all handled with callbacks anyways
this._controllerModel.refresh().catch(err => console.log(`Error refreshing controller model for Postgres dashboard ${err}`));
this._controllerModel.refresh(false, this._controllerModel.info.namespace).catch(err => console.log(`Error refreshing controller model for Postgres dashboard ${err}`));
this._postgresModel.refresh().catch(err => console.log(`Error refreshing Postgres model for Postgres dashboard ${err}`));
}

View File

@@ -33,17 +33,17 @@ export class PostgresDiagnoseAndSolveProblemsPage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.diagnoseAndSolveProblems,
CSSStyles: { ...cssStyles.title, 'margin-bottom': '20px' }
}).component());
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.clickTheTroubleshootButton('Postgres'),
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
}).component());
const troubleshootButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.wrench,
label: loc.troubleshoot,
width: '160px'

View File

@@ -5,28 +5,28 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
import { PostgresModel } from '../../../models/postgresModel';
import { AddPGExtensionsDialog } from '../../dialogs/addPGExtensionsDialog';
import { Deferred } from '../../../common/promise';
export class PostgresExtensionsPage extends DashboardPage {
private extensionNames: string[] = [];
private droppedExtensions: string[] = [];
private extensionsTable!: azdata.DeclarativeTableComponent;
private extensionsLoading!: azdata.LoadingComponent;
private addExtensionsButton!: azdata.ButtonComponent;
private _dropExtPromise?: Deferred<void>;
private dropExtensionsButton!: azdata.ButtonComponent;
private extensionsLink!: azdata.HyperlinkComponent;
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this.disposables.push(
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated())));
@@ -79,23 +79,23 @@ export class PostgresExtensionsPage extends DashboardPage {
width: '100%',
columns: [
{
displayName: loc.extensionName,
valueType: azdata.DeclarativeDataType.string,
displayName: '',
valueType: azdata.DeclarativeDataType.component,
width: '20px',
isReadOnly: true,
width: '95%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.dropText,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: false,
width: '10%',
displayName: loc.extensionName,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '100%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: []
dataValues: []
}).component();
this.extensionsLoading = this.modelView.modelBuilder.loadingComponent()
@@ -114,21 +114,22 @@ export class PostgresExtensionsPage extends DashboardPage {
protected get toolbarContainer(): azdata.ToolbarContainer {
// Add extensions
this.addExtensionsButton = this.modelView.modelBuilder.button().withProps({
label: loc.addExtensions,
ariaLabel: loc.addExtensions,
label: loc.loadExtensions,
ariaLabel: loc.loadExtensions,
iconPath: IconPathHelper.add
}).component();
this.disposables.push(
this.addExtensionsButton.onDidClick(async () => {
const addExtDialog = new AddPGExtensionsDialog(this._postgresModel);
addExtDialog.showDialog(loc.addExtensions);
addExtDialog.showDialog(loc.loadExtensions);
let extArg = await addExtDialog.waitForClose();
if (extArg) {
try {
this.addExtensionsButton.enabled = false;
let extensionList = this.extensionNames.join() + ',' + extArg;
this.dropExtensionsButton.enabled = false;
let extensionList = this.extensionNames.length ? this.extensionNames.join() + ',' + extArg : extArg;
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
@@ -137,12 +138,13 @@ export class PostgresExtensionsPage extends DashboardPage {
},
async (_progress, _token): Promise<void> => {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{
extensions: extensionList
},
this._postgresModel.controllerModel.azdataAdditionalEnvVars);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
try {
await this._postgresModel.refresh();
@@ -152,7 +154,7 @@ export class PostgresExtensionsPage extends DashboardPage {
}
);
vscode.window.showInformationMessage(loc.extensionsAdded(extensionList));
vscode.window.showInformationMessage(loc.extensionsAdded(extArg));
} catch (error) {
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
@@ -162,43 +164,20 @@ export class PostgresExtensionsPage extends DashboardPage {
}
}));
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
{ component: this.addExtensionsButton }
]).component();
}
private refreshExtensionsTable(): void {
let extensions = this._postgresModel.config!.spec.engine.extensions;
this.extensionsTable.data = extensions.map(e => {
this.extensionNames.push(e.name);
return [e.name, this.createDropButton(e.name)];
});
}
/**
* Creates drop button to add to each row of extensions table.
* Allows user to drop individual extension.
* @param name name of postgres extension the drop button will be tied to.
*/
public createDropButton(name: string): azdata.ButtonComponent {
// Can drop individual extensions
let button = this.modelView.modelBuilder.button().withProps({
// Drop extensions
this.dropExtensionsButton = this.modelView.modelBuilder.button().withProps({
label: loc.unloadExtensions,
ariaLabel: loc.unloadExtensions,
iconPath: IconPathHelper.delete,
ariaLabel: loc.dropExtension,
title: loc.dropExtension,
width: '20px',
height: '20px',
enabled: true
enabled: false
}).component();
this.disposables.push(
button.onDidClick(async () => {
this.dropExtensionsButton.onDidClick(async () => {
try {
this.addExtensionsButton.enabled = false;
button.enabled = false;
await this.dropExtension(name);
this.dropExtensionsButton.enabled = false;
await this.dropExtension();
try {
await this._postgresModel.refresh();
@@ -206,63 +185,103 @@ export class PostgresExtensionsPage extends DashboardPage {
vscode.window.showErrorMessage(loc.refreshFailed(error));
}
vscode.window.showInformationMessage(loc.extensionDropped(name));
vscode.window.showInformationMessage(loc.extensionsDropped(this.droppedExtensions.join()));
this.droppedExtensions = [];
} catch (error) {
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
} finally {
this.addExtensionsButton.enabled = true;
}
}));
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
{ component: this.addExtensionsButton },
{ component: this.dropExtensionsButton }
]).component();
}
private refreshExtensionsTable(): void {
let extensions = this._postgresModel.config!.spec.engine.extensions;
let extenesionFinalData: azdata.DeclarativeTableCellValue[][] = [];
let extensionBasicData: (string | azdata.CheckBoxComponent | azdata.ImageComponent)[][] = [];
if (extensions) {
extensionBasicData = extensions.map(e => {
this.extensionNames.push(e.name);
return [this.createDropCheckBox(e.name), e.name];
});
} else {
extensionBasicData = [[this.modelView.modelBuilder.image().component(), loc.noExtensions]];
}
extenesionFinalData = extensionBasicData.map(e => {
return e.map((value): azdata.DeclarativeTableCellValue => {
return { value: value };
});
});
this.extensionsTable.setDataValues(extenesionFinalData);
}
/**
* Creates checkboxes to select which extensions to drop.
* Allows user to drop multiple extension.
* @param name name of postgres extension the checkbox will be tied to.
*/
public createDropCheckBox(name: string): azdata.CheckBoxComponent {
// Can select extensions to drop
let checkBox = this.modelView.modelBuilder.checkBox().withProps({
ariaLabel: loc.unloadExtensions,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
if (name === 'citus') {
checkBox.enabled = false;
}
this.disposables.push(
checkBox.onChanged(() => {
if (checkBox.checked) {
this.droppedExtensions.push(name);
this.dropExtensionsButton.focus();
} else {
let index = this.droppedExtensions.indexOf(name, 0);
this.droppedExtensions.splice(index, 1);
}
this.dropExtensionsButton.enabled = this.droppedExtensions.length ? true : false;
})
);
// Dropping the citus extension is not supported.
if (name === 'citus') {
button.enabled = false;
}
return button;
return checkBox;
}
/**
* Calls edit on postgres extensions with an updated extensions list.
* @param name name of postgres extension to not inlcude when editing list of extensions
*/
public async dropExtension(name: string): Promise<void> {
// Only allow one drop to be happening at a time
if (this._dropExtPromise) {
vscode.window.showErrorMessage(loc.dropMultipleExtensions);
return this._dropExtPromise.promise;
}
public async dropExtension(): Promise<void> {
this.droppedExtensions.forEach(d => {
let index = this.extensionNames.indexOf(d, 0);
this.extensionNames.splice(index, 1);
});
this._dropExtPromise = new Deferred();
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
let index = this.extensionNames.indexOf(name, 0);
this.extensionNames.splice(index, 1);
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
{
extensions: this.extensionNames.join()
},
this._postgresModel.controllerModel.azdataAdditionalEnvVars
);
}
);
this._dropExtPromise.resolve();
} catch (err) {
this._dropExtPromise.reject(err);
throw err;
} finally {
this._dropExtPromise = undefined;
}
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{
extensions: this.extensionNames.join()
},
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars
);
}
);
}
private handleConfigUpdated(): void {
@@ -271,6 +290,7 @@ export class PostgresExtensionsPage extends DashboardPage {
this.extensionsLink.url = `https://www.postgresql.org/docs/${this._postgresModel.engineVersion}/external-extensions.html`;
this.extensionNames = [];
this.refreshExtensionsTable();
this.addExtensionsButton.focus();
}
}
}

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles, iconSize } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
@@ -35,11 +35,11 @@ export class PostgresOverviewPage extends DashboardPage {
private podStatusTable!: azdata.DeclarativeTableComponent;
private podStatusData: PodStatusModel[] = [];
private readonly _azdataApi: azdataExt.IExtension;
private readonly _azApi: azExt.IExtension;
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this.disposables.push(
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
@@ -65,13 +65,13 @@ export class PostgresOverviewPage extends DashboardPage {
// Properties
this.properties = this.modelView.modelBuilder.propertiesContainer()
.withProperties<azdata.PropertiesContainerComponentProperties>({
.withProps({
propertyItems: this.getProperties()
}).component();
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.properties)
.withProperties<azdata.LoadingComponentProperties>({
.withProps({
loading: !this._controllerModel.registrationsLastUpdated && !this._postgresModel.configLastUpdated
}).component();
@@ -79,9 +79,10 @@ export class PostgresOverviewPage extends DashboardPage {
// Service endpoints
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.serviceEndpoints,
CSSStyles: titleCSS
CSSStyles: titleCSS,
headingLevel: 1
}).component());
this.kibanaLink = this.modelView.modelBuilder.hyperlink().component();
@@ -89,13 +90,13 @@ export class PostgresOverviewPage extends DashboardPage {
this.grafanaLink = this.modelView.modelBuilder.hyperlink().component();
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent()
.withProperties<azdata.LoadingComponentProperties>(
.withProps(
{ loading: !this._postgresModel?.configLastUpdated }
)
.component();
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent()
.withProperties<azdata.LoadingComponentProperties>(
.withProps(
{ loading: !this._postgresModel?.configLastUpdated }
)
.component();
@@ -105,8 +106,9 @@ export class PostgresOverviewPage extends DashboardPage {
this.kibanaLoading.component = this.kibanaLink;
this.grafanaLoading.component = this.grafanaLink;
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
ariaLabel: loc.serviceEndpoints,
columns: [
{
displayName: loc.name,
@@ -139,20 +141,24 @@ export class PostgresOverviewPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: [
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
dataValues: [
[{ value: loc.kibanaDashboard }, { value: this.kibanaLoading }, { value: loc.kibanaDashboardDescription }],
[{ value: loc.grafanaDashboard }, { value: this.grafanaLoading }, { value: loc.grafanaDashboardDescription }]]
}).component();
content.addItem(endpointsTable);
// Server Group Nodes
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.serverGroupNodes,
CSSStyles: titleCSS
CSSStyles: titleCSS,
headingLevel: 1
}).component());
this.podStatusTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
ariaLabel: loc.serverGroupNodes,
columns: [
{
displayName: loc.name,
@@ -185,14 +191,14 @@ export class PostgresOverviewPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: [this.podStatusData.map(p => [p.podName, p.type, p.status])]
dataValues: this.createPodStatusDataValues()
}).component();
this.serverGroupNodesLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.podStatusTable)
.withProperties<azdata.LoadingComponentProperties>({
.withProps({
loading: !this._postgresModel.configLastUpdated
}).component();
@@ -206,7 +212,7 @@ export class PostgresOverviewPage extends DashboardPage {
protected get toolbarContainer(): azdata.ToolbarContainer {
// Reset password
const resetPasswordButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const resetPasswordButton = this.modelView.modelBuilder.button().withProps({
label: loc.resetPassword,
iconPath: IconPathHelper.edit
}).component();
@@ -217,13 +223,14 @@ export class PostgresOverviewPage extends DashboardPage {
try {
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
if (password) {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{
adminPassword: true,
noWait: true
},
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azdataAdditionalEnvVars));
this._postgresModel.controllerModel.info.namespace,
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azAdditionalEnvVars));
vscode.window.showInformationMessage(loc.passwordReset);
}
} catch (error) {
@@ -234,7 +241,7 @@ export class PostgresOverviewPage extends DashboardPage {
}));
// Delete service
this.deleteButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
this.deleteButton = this.modelView.modelBuilder.button().withProps({
label: loc.deleteText,
iconPath: IconPathHelper.delete
}).component();
@@ -251,7 +258,7 @@ export class PostgresOverviewPage extends DashboardPage {
cancellable: false
},
async (_progress, _token) => {
return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name, this._controllerModel.azdataAdditionalEnvVars, this._controllerModel.controllerContext);
return await this._azApi.az.postgres.arcserver.delete(this._postgresModel.info.name, this._postgresModel.controllerModel.info.namespace, this._controllerModel.azAdditionalEnvVars);
}
);
await this._controllerModel.refreshTreeNode();
@@ -272,7 +279,7 @@ export class PostgresOverviewPage extends DashboardPage {
}));
// Refresh
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const refreshButton = this.modelView.modelBuilder.button().withProps({
label: loc.refresh,
iconPath: IconPathHelper.refresh
}).component();
@@ -288,7 +295,7 @@ export class PostgresOverviewPage extends DashboardPage {
await Promise.all([
this._postgresModel.refresh(),
this._controllerModel.refresh()
this._controllerModel.refresh(false, this._controllerModel.info.namespace)
]);
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));
@@ -299,7 +306,7 @@ export class PostgresOverviewPage extends DashboardPage {
}));
// Open in Azure portal
const openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
label: loc.openInAzurePortal,
iconPath: IconPathHelper.openInTab
}).component();
@@ -345,7 +352,7 @@ export class PostgresOverviewPage extends DashboardPage {
let podModels: PodStatusModel[] = [];
const podStatus = this._postgresModel.config?.status.podsStatus;
podStatus?.forEach(p => {
podStatus?.forEach((p: { conditions: any[]; name: any; role: string; }) => {
// If a condition of the pod has a status of False, pod is not Ready
const status = p.conditions.find(c => c.status === 'False') ? loc.notReady : loc.ready;
@@ -389,6 +396,15 @@ export class PostgresOverviewPage extends DashboardPage {
return podModels;
}
private createPodStatusDataValues(): azdata.DeclarativeTableCellValue[][] {
let podDataValue: (string | azdata.Component)[][] = this.podStatusData.map(p => [p.podName, p.type, p.status]);
return podDataValue.map(p => {
return p.map((value): azdata.DeclarativeTableCellValue => {
return { value: value };
});
});
}
private refreshDashboardLinks(): void {
if (this._postgresModel.config) {
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
@@ -406,7 +422,7 @@ export class PostgresOverviewPage extends DashboardPage {
private refreshServerNodes(): void {
if (this._postgresModel.config) {
this.podStatusData = this.getPodStatus();
this.podStatusTable.data = this.podStatusData.map(p => [p.podName, p.type, p.status]);
this.podStatusTable.setDataValues(this.createPodStatusDataValues());
this.serverGroupNodesLoading.loading = false;
}
}

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as loc from '../../../localizedConstants';
import { UserCancelledError } from '../../../common/api';
import { IconPathHelper, cssStyles } from '../../../constants';
@@ -37,12 +37,12 @@ export abstract class PostgresParametersPage extends DashboardPage {
private changedComponentValues: Set<string> = new Set();
private parameterUpdates: Map<string, string> = new Map();
protected readonly _azdataApi: azdataExt.IExtension;
protected readonly _azApi: azExt.IExtension;
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, protected _postgresModel: PostgresModel) {
super(modelView, dashboard);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
this.initializeSearchBox();
@@ -551,8 +551,7 @@ export abstract class PostgresParametersPage extends DashboardPage {
iconPath: IconPathHelper.information,
width: '15px',
height: '15px',
enabled: false,
title: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
description: loc.rangeSetting(engineSetting.min!, engineSetting.max!)
}).component();
return {

View File

@@ -44,7 +44,7 @@ export class PostgresPropertiesPage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.properties,
CSSStyles: { ...cssStyles.title, 'margin-bottom': '25px' }
}).component());
@@ -55,7 +55,7 @@ export class PostgresPropertiesPage extends DashboardPage {
this.loading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.keyValueContainer.container)
.withProperties<azdata.LoadingComponentProperties>({
.withProps({
loading: !this._postgresModel.configLastUpdated && !this._controllerModel.registrationsLastUpdated
}).component();
@@ -65,7 +65,7 @@ export class PostgresPropertiesPage extends DashboardPage {
}
protected get toolbarContainer(): azdata.ToolbarContainer {
const refreshButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const refreshButton = this.modelView.modelBuilder.button().withProps({
label: loc.refresh,
iconPath: IconPathHelper.refresh
}).component();
@@ -77,7 +77,7 @@ export class PostgresPropertiesPage extends DashboardPage {
this.loading!.loading = true;
await Promise.all([
this._postgresModel.refresh(),
this._controllerModel.refresh()
this._controllerModel.refresh(false, this._controllerModel.info.namespace)
]);
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));

View File

@@ -12,7 +12,7 @@ import { PostgresModel } from '../../../models/postgresModel';
export type PodHealthModel = {
condition: string,
details?: azdata.Component,
details: azdata.Component,
lastUpdate: string
};
@@ -90,6 +90,7 @@ export class PostgresResourceHealthPage extends DashboardPage {
this.podConditionsContainer = this.modelView.modelBuilder.divContainer().component();
this.podConditionsTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
ariaLabel: loc.podConditionsTable,
columns: [
{
displayName: loc.condition,
@@ -119,10 +120,13 @@ export class PostgresResourceHealthPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: [this.coordinatorData.map(p => [p.condition, p.details, p.lastUpdate])]
dataValues: this.createPodConditionsDataValues(this.coordinatorData)
}).component();
this.podDropDown = this.modelView.modelBuilder.dropDown().withProps({ width: '150px' }).component();
this.podDropDown = this.modelView.modelBuilder.dropDown().withProps({
width: '150px',
ariaLabel: loc.podsUsedDescriptionAria
}).component();
this.disposables.push(
this.podDropDown.onValueChanged(() => {
this.podConditionsTable.setFilter(this.podConditionsTableIndexes.get(String(this.podDropDown.value)));
@@ -133,7 +137,7 @@ export class PostgresResourceHealthPage extends DashboardPage {
this.podConditionsContainer.addItem(this.podConditionsTable);
this.podConditionsLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.podConditionsContainer)
.withProperties<azdata.LoadingComponentProperties>({
.withProps({
loading: !this._postgresModel.configLastUpdated
}).component();
@@ -234,11 +238,20 @@ export class PostgresResourceHealthPage extends DashboardPage {
this.podConditionsTableIndexes.set(p.name, indexes);
});
this.podConditionsTable.data = this.podsData.map(p => [p.condition, p.details, p.lastUpdate]);
this.podConditionsTable.setDataValues(this.createPodConditionsDataValues(this.podsData));
return podNames;
}
private createPodConditionsDataValues(podInfo: PodHealthModel[]): azdata.DeclarativeTableCellValue[][] {
let podDataValues: (string | azdata.Component)[][] = podInfo.map(p => [p.condition, p.details, p.lastUpdate]);
return podDataValues.map(p => {
return p.map((value): azdata.DeclarativeTableCellValue => {
return { value: value };
});
});
}
private findPodIssues(): string[] {
const podStatus = this._postgresModel.config?.status.podsStatus;
let issueCount = 0;

View File

@@ -34,22 +34,22 @@ export class PostgresSupportRequestPage extends DashboardPage {
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.newSupportRequest,
CSSStyles: { ...cssStyles.title, 'margin-bottom': '20px' }
}).component());
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.clickTheNewSupportRequestButton,
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
}).component());
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.supportRequestNote,
CSSStyles: { ...cssStyles.text, 'margin-bottom': '20px' }
}).component());
const supportRequestButton = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
const supportRequestButton = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.support,
label: loc.newSupportRequest,
width: '205px'

View File

@@ -37,26 +37,26 @@ export class PostgresWorkerNodeParametersPage extends PostgresParametersPage {
}
protected async saveParameterEdits(engineSettings: string): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ workerEngineSettings: engineSettings },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
protected async resetAllParameters(): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ workerEngineSettings: `''`, replaceEngineSettings: true },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
protected async resetParameter(parameterName: string): Promise<void> {
await this._azdataApi.azdata.arc.postgres.server.edit(
await this._azApi.az.postgres.arcserver.edit(
this._postgresModel.info.name,
{ workerEngineSettings: parameterName + '=' },
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
this._postgresModel.controllerModel.controllerContext);
this._postgresModel.controllerModel.info.namespace,
this._postgresModel.controllerModel.azAdditionalEnvVars);
}
}

View File

@@ -10,6 +10,8 @@ import { cssStyles } from '../../constants';
import { InitializingComponent } from '../components/initializingComponent';
import { PostgresModel } from '../../models/postgresModel';
export const validExtensions = ['citus', 'pgaudit', 'pgautofailover', 'pg_cron', 'pg_partman', 'plv8', 'postgis', 'postgis_raster', 'postgis_sfcgal', 'postgis_tiger_geocoder', 'tdigest'];
export class AddPGExtensionsDialog extends InitializingComponent {
protected modelBuilder!: azdata.ModelBuilder;
@@ -27,12 +29,12 @@ export class AddPGExtensionsDialog extends InitializingComponent {
dialog.registerContent(async view => {
this.modelBuilder = view.modelBuilder;
const info = this.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: loc.extensionsFunction,
const info = this.modelBuilder.text().withProps({
value: loc.extensionsAddFunction(validExtensions.join(', ')),
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
const link = this.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
const link = this.modelBuilder.hyperlink().withProps({
label: loc.extensionsLearnMore,
url: 'https://docs.microsoft.com/azure/azure-arc/data/using-extensions-in-postgresql-hyperscale-server-group',
}).component();
@@ -42,10 +44,18 @@ export class AddPGExtensionsDialog extends InitializingComponent {
infoAndLink.addItem(link);
this.extensionsListInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
.withProps({
value: '',
ariaLabel: loc.extensionsAddList,
enabled: true
enabled: true,
validationErrorMessage: loc.extensionsAddErrorrMessage(validExtensions.join(','))
}).withValidation((component) => {
if (!component.value) {
return true;
}
let newExtensions = component.value.split(',');
return newExtensions.every(e => validExtensions.includes(e));
}).component();
let formModel = this.modelBuilder.formContainer()
@@ -56,7 +66,8 @@ export class AddPGExtensionsDialog extends InitializingComponent {
},
{
component: this.extensionsListInputBox,
title: loc.extensionsAddList
title: loc.extensionsAddList,
required: true
}
],
title: ''
@@ -67,7 +78,7 @@ export class AddPGExtensionsDialog extends InitializingComponent {
});
dialog.registerCloseValidator(async () => await this.validate());
dialog.okButton.label = loc.addExtensions;
dialog.okButton.label = loc.loadExtensions;
dialog.cancelButton.label = loc.cancel;
azdata.window.openDialog(dialog);
return dialog;

View File

@@ -5,7 +5,6 @@
import { ControllerInfo, ResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode';
import { Deferred } from '../../common/promise';
@@ -13,12 +12,11 @@ import * as loc from '../../localizedConstants';
import { ControllerModel } from '../../models/controllerModel';
import { InitializingComponent } from '../components/initializingComponent';
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
import { getErrorMessage } from '../../common/utils';
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts, KubeClusterContext } from '../../common/kubeUtils';
import { FilePicker } from '../components/filePicker';
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel };
abstract class ControllerDialogBase extends InitializingComponent {
protected _toDispose: vscode.Disposable[] = [];
@@ -29,9 +27,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
protected kubeConfigInputBox!: FilePicker;
protected clusterContextRadioGroup!: RadioOptionsGroup;
protected nameInputBox!: azdata.InputBoxComponent;
protected usernameInputBox!: azdata.InputBoxComponent;
protected passwordInputBox!: azdata.InputBoxComponent;
protected urlInputBox!: azdata.InputBoxComponent;
private _kubeClusters: KubeClusterContext[] = [];
@@ -46,13 +41,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
component: this.namespaceInputBox,
title: loc.namespace,
required: true
},
{
component: this.urlInputBox,
title: loc.controllerUrl,
layout: {
info: loc.controllerUrlDescription
}
}, {
component: this.kubeConfigInputBox.component(),
title: loc.controllerKubeConfig,
@@ -68,14 +56,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
layout: {
info: loc.controllerNameDescription
}
}, {
component: this.usernameInputBox,
title: loc.controllerUsername,
required: true
}, {
component: this.passwordInputBox,
title: loc.controllerPassword,
required: true
}
];
}
@@ -83,16 +63,11 @@ abstract class ControllerDialogBase extends InitializingComponent {
protected abstract fieldToFocusOn(): azdata.Component;
protected readonlyFields(): azdata.Component[] { return []; }
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
protected initializeFields(controllerInfo: ControllerInfo | undefined) {
this.namespaceInputBox = this.modelBuilder.inputBox()
.withProps({
value: controllerInfo?.namespace,
}).component();
this.urlInputBox = this.modelBuilder.inputBox()
.withProps({
value: controllerInfo?.endpoint,
placeHolder: loc.controllerUrlPlaceholder,
}).component();
this.kubeConfigInputBox = new FilePicker(
this.modelBuilder,
controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(),
@@ -113,15 +88,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
.withProps({
value: controllerInfo?.name
}).component();
this.usernameInputBox = this.modelBuilder.inputBox()
.withProps({
value: controllerInfo?.username
}).component();
this.passwordInputBox = this.modelBuilder.inputBox()
.withProps({
inputType: 'password',
value: password
}).component();
}
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
@@ -150,13 +116,13 @@ abstract class ControllerDialogBase extends InitializingComponent {
this.namespaceInputBox.value = currentContext?.namespace;
}
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
public showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
this.id = controllerInfo?.id ?? uuid();
this.resources = controllerInfo?.resources ?? [];
this._toDispose.push(this.dialog.cancelButton.onClick(() => this.handleCancel()));
this.dialog.registerContent(async (view) => {
this.modelBuilder = view.modelBuilder;
this.initializeFields(controllerInfo, password);
this.initializeFields(controllerInfo);
let formModel = this.modelBuilder.formContainer()
.withFormItems([{
@@ -192,75 +158,37 @@ abstract class ControllerDialogBase extends InitializingComponent {
return this.completionPromise.promise;
}
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
protected getControllerInfo(): ControllerInfo {
return {
id: this.id,
endpoint: url || undefined,
namespace: this.namespaceInputBox.value!.trim(),
kubeConfigFilePath: this.kubeConfigInputBox.value!,
kubeClusterContext: this.clusterContextRadioGroup.value!,
name: this.nameInputBox.value ?? '',
username: this.usernameInputBox.value!,
rememberPassword: rememberPassword,
resources: this.resources
};
}
}
export class ConnectToControllerDialog extends ControllerDialogBase {
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
protected fieldToFocusOn() {
return this.namespaceInputBox;
}
protected override getComponents() {
return [
...super.getComponents(),
{
component: this.rememberPwCheckBox,
title: ''
}];
}
protected override initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
super.initializeFields(controllerInfo, password);
this.rememberPwCheckBox = this.modelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
label: loc.rememberPassword,
checked: controllerInfo?.rememberPassword
}).component();
}
constructor(treeDataProvider: AzureArcTreeDataProvider) {
super(treeDataProvider, loc.connectToController);
}
public async validate(): Promise<boolean> {
if (!this.namespaceInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
if (!this.namespaceInputBox.value) {
return false;
}
let url = this.urlInputBox.value?.trim() || '';
if (url) {
// Only support https connections
if (url.toLowerCase().startsWith('http://')) {
url = url.replace('http', 'https');
}
// Append https if they didn't type it in
if (!url.toLowerCase().startsWith('https://')) {
url = `https://${url}`;
}
// Append default port if one wasn't specified
if (!/.*:\d*$/.test(url)) {
url = `${url}:30080`;
}
}
const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
const controllerInfo: ControllerInfo = this.getControllerInfo();
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo);
try {
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
await controllerModel.refresh(false);
await controllerModel.refresh(false, this.namespaceInputBox.value);
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
} catch (err) {
@@ -270,74 +198,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
};
return false;
}
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
this.completionPromise.resolve({ controllerModel: controllerModel });
return true;
}
}
export class PasswordToControllerDialog extends ControllerDialogBase {
constructor(treeDataProvider: AzureArcTreeDataProvider) {
super(treeDataProvider, loc.passwordToController);
}
protected fieldToFocusOn() {
return this.passwordInputBox;
}
protected override readonlyFields(): azdata.Component[] {
return [
this.urlInputBox,
...this.kubeConfigInputBox.items,
...this.clusterContextRadioGroup.items,
this.nameInputBox,
this.usernameInputBox
];
}
public async validate(): Promise<boolean> {
if (!this.passwordInputBox.value) {
return false;
}
const controllerInfo: ControllerInfo = this.getControllerInfo(this.urlInputBox.value!, false);
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
try {
await azdataApi.azdata.login(
{
endpoint: controllerInfo.endpoint,
namespace: controllerInfo.namespace
},
controllerInfo.username,
this.passwordInputBox.value,
{
'KUBECONFIG': this.kubeConfigInputBox.value!,
'KUBECTL_CONTEXT': this.clusterContextRadioGroup.value!
}
);
} catch (e) {
if (getErrorMessage(e).match(/Wrong username or password/i)) {
this.dialog.message = {
text: loc.loginFailed,
level: azdata.window.MessageLevel.Error
};
return false;
} else {
this.dialog.message = {
text: loc.errorVerifyingPassword(e),
level: azdata.window.MessageLevel.Error
};
return false;
}
}
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
return true;
}
public override showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
const dialog = super.showDialog(controllerInfo);
dialog.okButton.label = loc.ok;
return dialog;
}
}

View File

@@ -35,22 +35,22 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
this.modelBuilder = view.modelBuilder;
this.serverNameInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
.withProps({
value: connectionProfile?.serverName,
enabled: false
readOnly: true
}).component();
this.usernameInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
.withProps({
value: connectionProfile?.userName
}).component();
this.passwordInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
.withProps({
inputType: 'password',
value: connectionProfile?.password
})
.component();
this.rememberPwCheckBox = this.modelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
.withProps({
label: loc.rememberPassword,
checked: connectionProfile?.savePassword
}).component();
@@ -78,7 +78,7 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
title: ''
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
this.serverNameInputBox.focus();
this.usernameInputBox.focus();
this.initialized = true;
});

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { ControllerInfo } from 'arc';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { ControllerModel } from '../../models/controllerModel';
import { ControllerTreeNode } from './controllerTreeNode';
@@ -18,7 +17,6 @@ const mementoToken = 'arcDataControllers.v2';
*/
export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNode> {
private _credentialsProvider = azdata.credentials.getProvider('arcControllerPasswords');
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode | undefined> = new vscode.EventEmitter<TreeNode | undefined>();
readonly onDidChangeTreeData: vscode.Event<TreeNode | undefined> = this._onDidChangeTreeData.event;
@@ -51,14 +49,13 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
return element;
}
public async addOrUpdateController(model: ControllerModel, password: string, refreshTree = true): Promise<void> {
public async addOrUpdateController(model: ControllerModel, refreshTree = true): Promise<void> {
const controllerNode = this.getControllerNode(model);
if (controllerNode) {
controllerNode.model.info = model.info;
} else {
this._controllerNodes.push(new ControllerTreeNode(model, this._context, this));
}
await this.updatePassword(model, password);
if (refreshTree) {
this._onDidChangeTreeData.fire(undefined);
}
@@ -71,22 +68,10 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
public async removeController(controllerNode: ControllerTreeNode): Promise<void> {
this._controllerNodes = this._controllerNodes.filter(node => node !== controllerNode);
await this.deletePassword(controllerNode.model.info);
this._onDidChangeTreeData.fire(undefined);
await this.saveControllers();
}
public async getPassword(info: ControllerInfo): Promise<string> {
const provider = await this._credentialsProvider;
const credential = await provider.readCredential(info.id);
return credential.password;
}
private async deletePassword(info: ControllerInfo): Promise<void> {
const provider = await this._credentialsProvider;
await provider.deleteCredential(info.id);
}
/**
* Refreshes the specified node, or the entire tree if node is undefined
* @param node The node to refresh, or undefined for the whole tree
@@ -95,15 +80,6 @@ export class AzureArcTreeDataProvider implements vscode.TreeDataProvider<TreeNod
this._onDidChangeTreeData.fire(node);
}
private async updatePassword(model: ControllerModel, password: string): Promise<void> {
const provider = await this._credentialsProvider;
if (model.info.rememberPassword) {
await provider.saveCredential(model.info.id, password);
} else {
await provider.deleteCredential(model.info.id);
}
}
private async loadSavedControllers(): Promise<void> {
try {
const controllerMementos: ControllerInfo[] = this._context.globalState.get(mementoToken) || [];

View File

@@ -39,12 +39,12 @@ export class ControllerTreeNode extends TreeNode {
public override async getChildren(): Promise<TreeNode[]> {
try {
await this.model.refresh(false);
await this.model.refresh(false, this.model.info.namespace);
this.updateChildren(this.model.registrations);
} catch (err) {
vscode.window.showErrorMessage(loc.errorConnectingToController(err));
try {
await this.model.refresh(false);
await this.model.refresh(false, this.model.info.namespace);
this.updateChildren(this.model.registrations);
} catch (err) {
if (!(err instanceof UserCancelledError)) {

View File

@@ -1,12 +1,10 @@
# Microsoft Azure Data CLI Extension for Azure Data Studio
# Microsoft Azure CLI Extension for Azure Data Studio
Welcome to Microsoft Azure Data CLI Extension for Azure Data Studio!
**This extension is only applicable to customers in the Azure Arc data services public preview. Other usage is not supported at this time.**
Welcome to Microsoft Azure CLI Extension for Azure Data Studio!
## Overview
This extension adds support for the Azure Data CLI (azdata) within Azure Data Studio.
This extension adds support for the Azure CLI (az) within Azure Data Studio.
See https://docs.microsoft.com/cli/azure/?view=azure-cli-latest for more information on the tool.

View File

@@ -1,7 +1,7 @@
{
"name": "azcli",
"displayName": "%azdata.displayName%",
"description": "%azdata.description%",
"displayName": "%azcli.arc.displayName%",
"description": "%azcli.arc.description%",
"version": "0.1.0",
"publisher": "Microsoft",
"preview": true,
@@ -26,104 +26,19 @@
"configuration": [
{
"type": "object",
"title": "%azdata.config.title%",
"title": "%azcli.arc.config.title%",
"properties": {
"azcli.logDebugInfo": {
"type": "boolean",
"default": false,
"description": "%azdata.config.debug%"
},
"azcli.acceptEula": {
"type": "string",
"default": "prompt",
"enum": [
"dontPrompt",
"prompt"
],
"enumDescriptions": [
"%azdata.acceptEula.dontPrompt.description%",
"%azdata.acceptEula.prompt.description%"
],
"description": "%azdata.acceptEula.description%"
},
"azcli.install": {
"type": "string",
"default": "prompt",
"enum": [
"dontPrompt",
"prompt"
],
"enumDescriptions": [
"%azdata.install.dontPrompt.description%",
"%azdata.install.prompt.description%"
],
"description": "%azdata.install.description%"
},
"azcli.update": {
"type": "string",
"default": "prompt",
"enum": [
"dontPrompt",
"prompt"
],
"enumDescriptions": [
"%azdata.update.dontPrompt.description%",
"%azdata.update.prompt.description%"
],
"description": "%azdata.update.description%"
},
"azcli.requiredUpdate": {
"type": "string",
"default": "prompt",
"enum": [
"dontPrompt",
"prompt"
],
"enumDescriptions": [
"%azdata.update.dontPrompt.description%",
"%azdata.update.prompt.description%"
],
"description": "%azdata.requiredUpdate.description%"
"description": "%azcli.arc.config.debug%"
}
}
}
],
"commands": [
{
"command": "azcli.acceptEula",
"title": "%azdata.acceptEula.command.name%",
"category": "%command.category%"
},
{
"command": "azcli.install",
"title": "%azdata.install.command.name%",
"category": "%command.category%"
},
{
"command": "azcli.update",
"title": "%azdata.update.command.name%",
"category": "%command.category%"
}
],
"menus": {
"commandPalette": [
{
"command": "azcli.acceptEula",
"when": "!azcli.eulaAccepted"
},
{
"command": "azcli.install",
"when": "!azcli.found"
},
{
"command": "azcli.update",
"when": "azcli.found"
}
]
},
"resourceDeploymentOptionsSources": [
{
"id": "arc.controller.config.profiles"
"id": "azcli.arc.controller.config.profiles"
}
]
},

View File

@@ -1,25 +1,9 @@
{
"azdata.displayName": "Azure CLI",
"azdata.description": "Support for Azure CLI.",
"azdata.config.title": "Azure CLI Configuration",
"azdata.config.debug": "Log debug info to the output channel for all executed az commands",
"azcli.arc.displayName": "Azure CLI",
"azcli.arc.description": "Support for Azure CLI.",
"azcli.arc.config.title": "Azure CLI Configuration",
"azcli.arc.config.debug": "Log debug info to the output channel for all executed az commands",
"command.category": "Azure CLI",
"azdata.acceptEula.command.name": "Accept Eula",
"azdata.install.command.name": "Install",
"azdata.update.command.name": "Check for Update",
"azdata.category": "Azure CLI",
"azdata.acceptEula.description": "Choose how acceptance of EULA for the Azure CLI is done",
"azdata.acceptEula.prompt.description": "The user will be prompted for acceptance of EULA for the Azure CLI",
"azdata.acceptEula.dontPrompt.description": "The user will not be prompted for acceptance of EULA for the Azure CLI",
"azdata.install.description": "Choose how install of Azure CLI is done",
"azdata.install.prompt.description": "The user will be prompted for installation of the Azure CLI",
"azdata.install.dontPrompt.description": "The user will not be prompted for installation of the Azure CLI",
"azdata.update.description": "Choose whether you will be prompted when an update of the Azure CLI is available.",
"azdata.requiredUpdate.description": "Choose whether you will be prompted when a required update of the Azure CLI is available.",
"azdata.update.prompt.description": "The user will be prompted for update of the Azure CLI",
"azdata.update.dontPrompt.description": "The user will not be prompted for update of the Azure CLI"
"azcli.arc.category": "Azure CLI"
}

View File

@@ -3,200 +3,137 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode';
import { IAzdataTool, isEulaAccepted, MIN_AZDATA_VERSION, promptForEula } from './azdata';
import * as azExt from 'az-ext';
import { IAzTool } from './az';
import Logger from './common/logger';
import { NoAzdataError } from './common/utils';
import * as constants from './constants';
import { NoAzureCLIError } from './common/utils';
import * as loc from './localizedConstants';
import { AzdataToolService } from './services/azdataToolService';
import { AzToolService } from './services/azToolService';
/**
* Validates that :
* - Azdata is installed
* - The Azdata version is >= the minimum required version
* - The Azdata CLI has been accepted
* @param azdata The azdata tool to check
* @param eulaAccepted Whether the Azdata CLI EULA has been accepted
* - Az is installed
* @param az The az tool to check
*/
async function validateAzdata(azdata: IAzdataTool | undefined, eulaAccepted: boolean): Promise<void> {
throwIfNoAzdataOrEulaNotAccepted(azdata, eulaAccepted);
await throwIfRequiredVersionMissing(azdata);
export function validateAz(az: IAzTool | undefined) {
throwIfNoAz(az);
}
export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
throwIfNoAzdata(azdata);
if (!eulaAccepted) {
Logger.log(loc.eulaNotAccepted);
throw new Error(loc.eulaNotAccepted);
export function throwIfNoAz(localAz: IAzTool | undefined): asserts localAz {
if (!localAz) {
Logger.log(loc.noAzureCLI);
throw new NoAzureCLIError();
}
}
export async function throwIfRequiredVersionMissing(azdata: IAzdataTool): Promise<void> {
const currentVersion = await azdata.getSemVersion();
if (currentVersion.compare(MIN_AZDATA_VERSION) < 0) {
throw new Error(loc.missingRequiredVersion(MIN_AZDATA_VERSION.raw));
}
}
export function throwIfNoAzdata(localAzdata: IAzdataTool | undefined): asserts localAzdata {
if (!localAzdata) {
Logger.log(loc.noAzdata);
throw new NoAzdataError();
}
}
export function getExtensionApi(memento: vscode.Memento, azdataToolService: AzdataToolService, localAzdataDiscovered: Promise<IAzdataTool | undefined>): azdataExt.IExtension {
export function getExtensionApi(azToolService: AzToolService): azExt.IExtension {
return {
isEulaAccepted: async () => {
throwIfNoAzdata(await localAzdataDiscovered); // ensure that we have discovered Azdata
return !!memento.get<boolean>(constants.eulaAccepted);
},
promptForEula: async (requireUserAction: boolean = true): Promise<boolean> => {
await localAzdataDiscovered;
return promptForEula(memento, true /* userRequested */, requireUserAction);
},
azdata: getAzdataApi(localAzdataDiscovered, azdataToolService, memento)
az: getAzApi(azToolService)
};
}
export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefined>, azdataToolService: AzdataToolService, memento: vscode.Memento): azdataExt.IAzdataApi {
export function getAzApi(azToolService: AzToolService): azExt.IAzApi {
return {
arc: {
arcdata: {
dc: {
create: async (
namespace: string,
name: string,
connectivityMode: string,
resourceGroup: string,
location: string,
subscription: string,
profileName?: string,
storageClass?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars, azdataContext);
},
endpoint: {
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.dc.endpoint.list(additionalEnvVars, azdataContext);
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.arcdata.dc.endpoint.list(namespace, additionalEnvVars);
}
},
config: {
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.dc.config.list(additionalEnvVars, azdataContext);
list: async (additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.arcdata.dc.config.list(additionalEnvVars);
},
show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.dc.config.show(additionalEnvVars, azdataContext);
}
}
},
postgres: {
server: {
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.postgres.server.delete(name, additionalEnvVars, azdataContext);
},
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.postgres.server.list(additionalEnvVars, azdataContext);
},
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.postgres.server.show(name, additionalEnvVars, azdataContext);
},
edit: async (
name: string,
args: {
adminPassword?: boolean;
coresLimit?: string;
coresRequest?: string;
coordinatorEngineSettings?: string;
engineSettings?: string;
extensions?: string;
memoryLimit?: string;
memoryRequest?: string;
noWait?: boolean;
port?: number;
replaceEngineSettings?: boolean;
workerEngineSettings?: string;
workers?: number;
},
additionalEnvVars?: azdataExt.AdditionalEnvVars,
azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.postgres.server.edit(name, args, additionalEnvVars, azdataContext);
}
}
},
sql: {
mi: {
delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.sql.mi.delete(name, additionalEnvVars, azdataContext);
},
list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.sql.mi.list(additionalEnvVars, azdataContext);
},
show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.sql.mi.show(name, additionalEnvVars, azdataContext);
},
edit: async (
name: string,
args: {
coresLimit?: string;
coresRequest?: string;
memoryLimit?: string;
memoryRequest?: string;
noWait?: boolean;
},
additionalEnvVars?: azdataExt.AdditionalEnvVars,
azdataContext?: string
) => {
await localAzdataDiscovered;
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.arc.sql.mi.edit(name, args, additionalEnvVars, azdataContext);
show: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.arcdata.dc.config.show(namespace, additionalEnvVars);
}
}
}
},
getPath: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.getPath();
postgres: {
arcserver: {
delete: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.postgres.arcserver.delete(name, namespace, additionalEnvVars);
},
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.postgres.arcserver.list(namespace, additionalEnvVars);
},
show: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.postgres.arcserver.show(name, namespace, additionalEnvVars);
},
edit: async (
name: string,
args: {
adminPassword?: boolean;
coresLimit?: string;
coresRequest?: string;
coordinatorEngineSettings?: string;
engineSettings?: string;
extensions?: string;
memoryLimit?: string;
memoryRequest?: string;
noWait?: boolean;
port?: number;
replaceEngineSettings?: boolean;
workerEngineSettings?: string;
workers?: number;
},
namespace: string,
additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.postgres.arcserver.edit(name, args, namespace, additionalEnvVars);
}
}
},
login: async (endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string) => {
await validateAzdata(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata!.login(endpointOrNamespace, username, password, additionalEnvVars, azdataContext);
sql: {
miarc: {
delete: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.sql.miarc.delete(name, namespace, additionalEnvVars);
},
list: async (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.sql.miarc.list(namespace, additionalEnvVars);
},
show: async (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.sql.miarc.show(name, namespace, additionalEnvVars);
},
edit: async (
name: string,
args: {
coresLimit?: string;
coresRequest?: string;
memoryLimit?: string;
memoryRequest?: string;
noWait?: boolean;
},
namespace: string,
additionalEnvVars?: azExt.AdditionalEnvVars
) => {
validateAz(azToolService.localAz);
return azToolService.localAz!.sql.miarc.edit(name, args, namespace, additionalEnvVars);
}
}
},
getPath: async () => {
throwIfNoAz(azToolService.localAz);
return azToolService.localAz.getPath();
},
getSemVersion: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.getSemVersion();
throwIfNoAz(azToolService.localAz);
return azToolService.localAz.getSemVersion();
},
version: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.version();
throwIfNoAz(azToolService.localAz);
return azToolService.localAz.version();
}
};
}

320
extensions/azcli/src/az.ts Normal file
View File

@@ -0,0 +1,320 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azExt from 'az-ext';
import * as fs from 'fs';
import * as os from 'os';
import { SemVer } from 'semver';
import * as vscode from 'vscode';
import { executeCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
import Logger from './common/logger';
import { NoAzureCLIError, searchForCmd } from './common/utils';
import { azConfigSection, azFound, debugConfigKey, latestAzArcExtensionVersion } from './constants';
import * as loc from './localizedConstants';
/**
* The latest Azure CLI arcdata extension version for this extension to function properly
*/
export const LATEST_AZ_ARC_EXTENSION_VERSION = new SemVer(latestAzArcExtensionVersion);
export const enum AzDeployOption {
dontPrompt = 'dontPrompt',
prompt = 'prompt'
}
/**
* Interface for an object to interact with the az tool installed on the box.
*/
export interface IAzTool extends azExt.IAzApi {
/**
* Executes az with the specified arguments (e.g. --version) and returns the result
* @param args The args to pass to az
* @param parseResult A function used to parse out the raw result into the desired shape
*/
executeCommand<R>(args: string[], additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<R>>
}
/**
* An object to interact with the az tool installed on the box.
*/
export class AzTool implements azExt.IAzApi {
private _semVersion: SemVer;
constructor(private _path: string, version: string) {
this._semVersion = new SemVer(version);
}
/**
* The semVersion corresponding to this installation of az. version() method should have been run
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
* Az has gotten reinstalled in the background after this IAzApi object was constructed.
*/
public async getSemVersion(): Promise<SemVer> {
return this._semVersion;
}
/**
* gets the path where az tool is installed
*/
public async getPath(): Promise<string> {
return this._path;
}
public arcdata = {
dc: {
endpoint: {
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcEndpointListResult[]>> => {
return this.executeCommand<azExt.DcEndpointListResult[]>(['arcdata', 'dc', 'endpoint', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
}
},
config: {
list: (additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcConfigListResult[]>> => {
return this.executeCommand<azExt.DcConfigListResult[]>(['arcdata', 'dc', 'config', 'list'], additionalEnvVars);
},
show: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.DcConfigShowResult>> => {
return this.executeCommand<azExt.DcConfigShowResult>(['arcdata', 'dc', 'config', 'show', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
}
}
}
};
public postgres = {
arcserver: {
delete: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
return this.executeCommand<void>(['postgres', 'arc-server', 'delete', '-n', name, '--k8s-namespace', namespace, '--force', '--use-k8s'], additionalEnvVars);
},
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.PostgresServerListResult[]>> => {
return this.executeCommand<azExt.PostgresServerListResult[]>(['postgres', 'arc-server', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
},
show: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.PostgresServerShowResult>> => {
return this.executeCommand<azExt.PostgresServerShowResult>(['postgres', 'arc-server', 'show', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
},
edit: (
name: string,
args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
coordinatorEngineSettings?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workerEngineSettings?: string,
workers?: number
},
namespace: string,
additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
const argsArray = ['postgres', 'arc-server', 'edit', '-n', name, '--k8s-namespace', namespace, '--use-k8s'];
if (args.adminPassword) { argsArray.push('--admin-password'); }
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
if (args.coordinatorEngineSettings) { argsArray.push('--coordinator-settings', args.coordinatorEngineSettings); }
if (args.engineSettings) { argsArray.push('--engine-settings', args.engineSettings); }
if (args.extensions) { argsArray.push('--extensions', args.extensions); }
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
if (args.noWait) { argsArray.push('--no-wait'); }
if (args.port) { argsArray.push('--port', args.port.toString()); }
if (args.replaceEngineSettings) { argsArray.push('--replace-settings'); }
if (args.workerEngineSettings) { argsArray.push('--worker-settings', args.workerEngineSettings); }
if (args.workers !== undefined) { argsArray.push('--workers', args.workers.toString()); }
return this.executeCommand<void>(argsArray, additionalEnvVars);
}
}
};
public sql = {
miarc: {
delete: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<void>> => {
return this.executeCommand<void>(['sql', 'mi-arc', 'delete', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
},
list: (namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.SqlMiListResult[]>> => {
return this.executeCommand<azExt.SqlMiListResult[]>(['sql', 'mi-arc', 'list', '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
},
show: (name: string, namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<azExt.SqlMiShowResult>> => {
return this.executeCommand<azExt.SqlMiShowResult>(['sql', 'mi-arc', 'show', '-n', name, '--k8s-namespace', namespace, '--use-k8s'], additionalEnvVars);
},
edit: (
name: string,
args: {
coresLimit?: string,
coresRequest?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
},
namespace: string,
additionalEnvVars?: azExt.AdditionalEnvVars
): Promise<azExt.AzOutput<void>> => {
const argsArray = ['sql', 'mi-arc', 'edit', '-n', name, '--k8s-namespace', namespace, '--use-k8s'];
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
if (args.noWait) { argsArray.push('--no-wait'); }
return this.executeCommand<void>(argsArray, additionalEnvVars);
}
}
};
/**
* Gets the output of running '--version' command on the az tool.
* It also updates the cachedVersion property based on the return value from the tool.
*/
public async version(): Promise<azExt.AzOutput<string>> {
const output = await executeAzCommand(`"${this._path}"`, ['--version']);
this._semVersion = new SemVer(parseVersion(output.stdout));
return {
stdout: output.stdout,
stderr: output.stderr.split(os.EOL)
};
}
/**
* Executes the specified az command.
* @param args The args to pass to az
* @param additionalEnvVars Additional environment variables to set for this execution
*/
public async executeCommand<R>(args: string[], additionalEnvVars?: azExt.AdditionalEnvVars): Promise<azExt.AzOutput<R>> {
try {
const result = await executeAzCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars);
let stdout = <unknown>result.stdout;
let stderr = <unknown>result.stderr;
try {
// Automatically try parsing the JSON. This is expected to fail for some az commands such as resource delete.
stdout = JSON.parse(result.stdout);
} catch (err) {
// If the output was not pure JSON, catch the error and log it here.
Logger.log(loc.azOutputParseErrorCaught(args.concat(['--output', 'json']).toString()));
}
return {
stdout: <R>stdout,
stderr: <string[]>stderr
};
} catch (err) {
if (err instanceof ExitCodeError) {
try {
await fs.promises.access(this._path);
//this.path exists
} catch (e) {
// this.path does not exist
await vscode.commands.executeCommand('setContext', azFound, false);
throw new NoAzureCLIError();
}
}
throw err;
}
}
}
/**
* Finds and returns the existing installation of Azure CLI, or throws an error if it can't find it
* or encountered an unexpected error.
* The promise is rejected when Azure CLI is not found.
*/
export async function findAz(): Promise<IAzTool> {
Logger.log(loc.searchingForAz);
try {
const az = await findSpecificAz();
Logger.log(loc.foundExistingAz(await az.getPath(), (await az.getSemVersion()).raw));
return az;
} catch (err) {
Logger.log(loc.noAzureCLI);
throw err;
}
}
/**
* Parses out the Azure CLI version from the raw az version output
* @param raw The raw version output from az --version
*/
function parseVersion(raw: string): string {
// Currently the version is a multi-line string that contains other version information such
// as the Python installation, with the first line holding the version of az itself.
//
// The output of az --version looks like:
// azure-cli 2.26.1
// ...
const start = raw.search('azure-cli');
const end = raw.search('core');
raw = raw.slice(start, end).replace('azure-cli', '');
return raw.trim();
}
/**
* Parses out the arcdata extension version from the raw az version output
* @param raw The raw version output from az --version
*/
function parseArcExtensionVersion(raw: string): string {
// Currently the version is a multi-line string that contains other version information such
// as the Python installation and any extensions.
//
// The output of az --version looks like:
// azure-cli 2.26.1
// ...
// Extensions:
// arcdata 1.0.0
// connectedk8s 1.1.5
// ...
const start = raw.search('arcdata');
if (start === -1) {
// Commented the install/update prompts out until DoNotAskAgain is implemented
//throw new AzureCLIArcExtError();
} else {
raw = raw.slice(start + 7);
raw = raw.split(os.EOL)[0].trim();
}
return raw.trim();
}
async function executeAzCommand(command: string, args: string[], additionalEnvVars: azExt.AdditionalEnvVars = {}): Promise<ProcessOutput> {
const debug = vscode.workspace.getConfiguration(azConfigSection).get(debugConfigKey);
if (debug) {
args.push('--debug');
}
return executeCommand(command, args, additionalEnvVars);
}
// Commented the install/update prompts out until DoNotAskAgain is implemented
// async function setConfig(key: string, value: string): Promise<void> {
// const config = vscode.workspace.getConfiguration(azConfigSection);
// await config.update(key, value, vscode.ConfigurationTarget.Global);
// }
/**
* Find user's local Azure CLI. Execute az --version and parse out the version number.
* If an update is needed, prompt the user to update via link. Return the AzTool.
* Currently commented out because Don't Prompt Again is not properly implemented.
*/
async function findSpecificAz(): Promise<IAzTool> {
const path = await ((process.platform === 'win32') ? searchForCmd('az.cmd') : searchForCmd('az'));
const versionOutput = await executeAzCommand(`"${path}"`, ['--version']);
const version = parseArcExtensionVersion(versionOutput.stdout);
const semVersion = new SemVer(version);
//let response: string | undefined;
if (LATEST_AZ_ARC_EXTENSION_VERSION.compare(semVersion) === 1) {
// If there is a greater version of az arc extension available, prompt to update
// Commented the install/update prompts out until DoNotAskAgain is implemented
// const responses = [loc.askLater, loc.doNotAskAgain];
// response = await vscode.window.showInformationMessage(loc.requiredArcDataVersionNotAvailable(latestAzArcExtensionVersion, version), ...responses);
// if (response === loc.doNotAskAgain) {
// await setConfig(azRequiredUpdateKey, AzDeployOption.dontPrompt);
// }
} else if (LATEST_AZ_ARC_EXTENSION_VERSION.compare(semVersion) === -1) {
// Current version should not be greater than latest version
// Commented the install/update prompts out until DoNotAskAgain is implemented
// vscode.window.showErrorMessage(loc.unsupportedArcDataVersion(latestAzArcExtensionVersion, version));
}
return new AzTool(path, version);
}

View File

@@ -1,712 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { SemVer } from 'semver';
import * as vscode from 'vscode';
import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataReleaseInfo';
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
import { HttpClient } from './common/httpClient';
import Logger from './common/logger';
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, azdatarequiredUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
import * as loc from './localizedConstants';
/**
* The minimum required azdata CLI version for this extension to function properly
*/
export const MIN_AZDATA_VERSION = new SemVer('20.3.4');
export const enum AzdataDeployOption {
dontPrompt = 'dontPrompt',
prompt = 'prompt'
}
/**
* Interface for an object to interact with the azdata tool installed on the box.
*/
export interface IAzdataTool extends azdataExt.IAzdataApi {
/**
* Executes azdata with the specified arguments (e.g. --version) and returns the result
* @param args The args to pass to azdata
* @param parseResult A function used to parse out the raw result into the desired shape
*/
executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>>
}
/**
* An object to interact with the azdata tool installed on the box.
*/
export class AzdataTool implements azdataExt.IAzdataApi {
private _semVersion: SemVer;
constructor(private _path: string, version: string) {
this._semVersion = new SemVer(version);
}
/**
* The semVersion corresponding to this installation of azdata. version() method should have been run
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
*/
public async getSemVersion(): Promise<SemVer> {
return this._semVersion;
}
/**
* gets the path where azdata tool is installed
*/
public async getPath(): Promise<string> {
return this._path;
}
public arc = {
dc: {
create: (
namespace: string,
name: string,
connectivityMode: string,
resourceGroup: string,
location: string,
subscription: string,
profileName?: string,
storageClass?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
const args = ['arc', 'dc', 'create',
'--namespace', namespace,
'--name', name,
'--connectivity-mode', connectivityMode,
'--resource-group', resourceGroup,
'--location', location,
'--subscription', subscription];
if (profileName) {
args.push('--profile-name', profileName);
}
if (storageClass) {
args.push('--storage-class', storageClass);
}
return this.executeCommand<void>(args, additionalEnvVars, azdataContext);
},
endpoint: {
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, azdataContext);
}
},
config: {
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, azdataContext);
},
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, azdataContext);
}
}
},
postgres: {
server: {
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, azdataContext);
},
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, azdataContext);
},
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars, azdataContext);
},
edit: (
name: string,
args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
coordinatorEngineSettings?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workerEngineSettings?: string,
workers?: number
},
additionalEnvVars?: azdataExt.AdditionalEnvVars,
azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
if (args.adminPassword) { argsArray.push('--admin-password'); }
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
if (args.coordinatorEngineSettings) { argsArray.push('--coordinator-engine-settings', args.coordinatorEngineSettings); }
if (args.engineSettings) { argsArray.push('--engine-settings', args.engineSettings); }
if (args.extensions) { argsArray.push('--extensions', args.extensions); }
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
if (args.noWait) { argsArray.push('--no-wait'); }
if (args.port) { argsArray.push('--port', args.port.toString()); }
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
if (args.workerEngineSettings) { argsArray.push('--worker-engine-settings', args.workerEngineSettings); }
if (args.workers !== undefined) { argsArray.push('--workers', args.workers.toString()); }
return this.executeCommand<void>(argsArray, additionalEnvVars, azdataContext);
}
}
},
sql: {
mi: {
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> => {
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, azdataContext);
},
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, azdataContext);
},
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars, azdataContext);
},
edit: (
name: string,
args: {
coresLimit?: string,
coresRequest?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
},
additionalEnvVars?: azdataExt.AdditionalEnvVars
): Promise<azdataExt.AzdataOutput<void>> => {
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
if (args.noWait) { argsArray.push('--no-wait'); }
return this.executeCommand<void>(argsArray, additionalEnvVars);
}
}
}
};
public async login(endpointOrNamespace: azdataExt.EndpointOrNamespace, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}, azdataContext?: string): Promise<azdataExt.AzdataOutput<void>> {
const args = ['login', '-u', username];
if (endpointOrNamespace.endpoint) {
args.push('-e', endpointOrNamespace.endpoint);
} else if (endpointOrNamespace.namespace) {
args.push('--namespace', endpointOrNamespace.namespace);
} else {
throw new Error(loc.endpointOrNamespaceRequired);
}
return this.executeCommand<void>(args, Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }), azdataContext);
}
/**
* Gets the output of running '--version' command on the azdata tool.
* It also updates the cachedVersion property based on the return value from the tool.
*/
public async version(): Promise<azdataExt.AzdataOutput<string>> {
const output = await executeAzdataCommand(`"${this._path}"`, ['--version']);
this._semVersion = new SemVer(parseVersion(output.stdout));
return {
logs: [],
stdout: output.stdout.split(os.EOL),
stderr: output.stderr.split(os.EOL),
result: output.stdout
};
}
/**
* Executes the specified azdata command.
* @param args The args to pass to azdata
* @param additionalEnvVars Additional environment variables to set for this execution
*/
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, azdataContext?: string): Promise<azdataExt.AzdataOutput<R>> {
try {
if (azdataContext) {
args = args.concat('--controller-context', azdataContext);
}
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
return {
logs: <string[]>output.log,
stdout: <string[]>output.stdout,
stderr: <string[]>output.stderr,
result: <R>output.result
};
} catch (err) {
if (err instanceof ExitCodeError) {
try {
// For azdata internal errors the output is JSON and so we need to do some extra parsing here
// to get the correct stderr out. The actual value we get is something like
// ERROR: { stderr: '...' }
// so we also need to trim off the start that isn't a valid JSON blob
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'), err.stderr.indexOf('}') + 1)).stderr;
} catch {
// it means this was probably some other generic error (such as command not being found)
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
try {
await fs.promises.access(this._path);
//this.path exists
} catch (e) {
// this.path does not exist
await vscode.commands.executeCommand('setContext', azdataFound, false);
throw new NoAzdataError();
}
throw err; // rethrow the original error
}
}
throw err;
}
}
}
export type AzdataDarwinPackageVersionInfo = {
versions: {
stable: string,
devel: string,
head: string,
bottle: boolean
}
};
/**
* Finds the existing installation of azdata, or throws an error if it couldn't find it
* or encountered an unexpected error.
* The promise is rejected when Azdata is not found.
*/
export async function findAzdata(): Promise<IAzdataTool> {
Logger.log(loc.searchingForAzdata);
try {
const azdata = await findSpecificAzdata();
await vscode.commands.executeCommand('setContext', azdataFound, true); // save a context key that azdata was found so that command for installing azdata is no longer available in commandPalette and that for updating it is.
Logger.log(loc.foundExistingAzdata(await azdata.getPath(), (await azdata.getSemVersion()).raw));
return azdata;
} catch (err) {
Logger.log(loc.couldNotFindAzdata(err));
Logger.log(loc.noAzdata);
await vscode.commands.executeCommand('setContext', azdataFound, false);// save a context key that azdata was not found so that command for installing azdata is available in commandPalette and that for updating it is no longer available.
throw err;
}
}
/**
* runs the commands to install azdata, downloading the installation package if needed
*/
export async function installAzdata(): Promise<void> {
Logger.show();
Logger.log(loc.installingAzdata);
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.installingAzdata,
cancellable: false
},
async (_progress, _token): Promise<void> => {
switch (process.platform) {
case 'win32':
await downloadAndInstallAzdataWin32();
break;
case 'darwin':
await installAzdataDarwin();
break;
case 'linux':
await installAzdataLinux();
break;
default:
throw new Error(loc.platformUnsupported(process.platform));
}
}
);
}
/**
* Updates the azdata using os appropriate method
*/
async function updateAzdata(version: string): Promise<boolean> {
try {
Logger.show();
Logger.log(loc.updatingAzdata);
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingAzdata,
cancellable: false
},
async (_progress, _token): Promise<void> => {
switch (process.platform) {
case 'win32':
await downloadAndInstallAzdataWin32();
break;
case 'darwin':
await updateAzdataDarwin();
break;
case 'linux':
await installAzdataLinux();
break;
default:
throw new Error(loc.platformUnsupported(process.platform));
}
}
);
vscode.window.showInformationMessage(loc.azdataUpdated(version));
Logger.log(loc.azdataUpdated(version));
return true;
} catch (err) {
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
vscode.window.showWarningMessage(loc.updateError(err));
Logger.log(loc.updateError(err));
}
}
return false;
}
/**
* Checks whether azdata is installed - and if it is not then invokes the process of azdata installation.
* @param userRequested true means that this operation by was requested by a user by executing an ads command.
*/
export async function checkAndInstallAzdata(userRequested: boolean = false): Promise<IAzdataTool | undefined> {
try {
return await findAzdata(); // find currently installed Azdata
} catch (err) {
// Calls will be made to handle azdata not being installed if user declines to install on the prompt
if (await promptToInstallAzdata(userRequested)) {
return await findAzdata();
}
}
return undefined;
}
/**
* Checks whether a newer version of azdata is available - and if it is then invokes the process of azdata update.
* @param currentAzdata The current version of azdata to check against
* @param userRequested true means that this operation by was requested by a user by executing an ads command.
* returns true if update was done and false otherwise.
*/
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
if (currentAzdata !== undefined) {
const newSemVersion = await discoverLatestAvailableAzdataVersion();
const currentSemVersion = await currentAzdata.getSemVersion();
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, currentSemVersion.raw));
if (MIN_AZDATA_VERSION.compare(currentSemVersion) === 1) {
if (newSemVersion.compare(MIN_AZDATA_VERSION) >= 0) {
return await promptToUpdateAzdata(newSemVersion.raw, userRequested, true);
} else {
// This should never happen - it means that the currently available version to download
// is < the version we require. If this was to happen it'd imply something is wrong with
// the version JSON or the minimum required version.
// Regardless, there's nothing we can do and so we just bail out at this point and tell the user
// they have to install it manually (hopefully it's available and wasn't a publishing mistake)
vscode.window.showInformationMessage(loc.requiredVersionNotAvailable(MIN_AZDATA_VERSION.raw, newSemVersion.raw));
Logger.log(loc.requiredVersionNotAvailable(newSemVersion.raw, currentSemVersion.raw));
}
}
else if (newSemVersion.compare(currentSemVersion) === 1) {
return await promptToUpdateAzdata(newSemVersion.raw, userRequested);
} else {
Logger.log(loc.currentlyInstalledVersionIsLatest((await currentAzdata.getSemVersion()).raw));
}
} else {
Logger.log(loc.updateCheckSkipped);
Logger.log(loc.noAzdata);
await vscode.commands.executeCommand('setContext', azdataFound, false);
}
return false;
}
/**
* prompt user to install Azdata.
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
* returns true if installation was done and false otherwise.
*/
async function promptToInstallAzdata(userRequested: boolean = false): Promise<boolean> {
let response: string | undefined = loc.yes;
const config = <AzdataDeployOption>getConfig(azdataInstallKey);
if (userRequested) {
Logger.show();
Logger.log(loc.userRequestedInstall);
}
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
Logger.log(loc.skipInstall(config));
return false;
}
const responses = userRequested
? [loc.yes, loc.no]
: [loc.yes, loc.askLater, loc.doNotAskAgain];
if (config === AzdataDeployOption.prompt) {
Logger.log(loc.promptForAzdataInstallLog);
response = await vscode.window.showErrorMessage(loc.promptForAzdataInstall, ...responses);
Logger.log(loc.userResponseToInstallPrompt(response));
}
if (response === loc.doNotAskAgain) {
await setConfig(azdataInstallKey, AzdataDeployOption.dontPrompt);
} else if (response === loc.yes) {
try {
await installAzdata();
vscode.window.showInformationMessage(loc.azdataInstalled);
Logger.log(loc.azdataInstalled);
return true;
} catch (err) {
// Windows: 1602 is User cancelling installation/update - not unexpected so don't display
if (!(err instanceof ExitCodeError) || err.code !== 1602) {
vscode.window.showWarningMessage(loc.installError(err));
Logger.log(loc.installError(err));
}
}
}
return false;
}
/**
* prompt user to update Azdata.
* @param newVersion - provides the new version that the user will be prompted to update to
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
* returns true if update was done and false otherwise.
* @param required - Whether this update is required. If true then we will always show the prompt and warn the user if they decline it
*/
async function promptToUpdateAzdata(newVersion: string, userRequested: boolean = false, required = false): Promise<boolean> {
if (required) {
let response: string | undefined = loc.yes;
const config = <AzdataDeployOption>getConfig(azdatarequiredUpdateKey);
if (userRequested) {
Logger.show();
Logger.log(loc.userRequestedUpdate);
}
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
Logger.log(loc.skipRequiredUpdate(config));
return false;
}
const responses = userRequested
? [loc.yes, loc.no]
: [loc.yes, loc.askLater, loc.doNotAskAgain];
Logger.log(loc.promptForRequiredAzdataUpdateLog(MIN_AZDATA_VERSION.raw, newVersion));
response = await vscode.window.showInformationMessage(loc.promptForRequiredAzdataUpdate(MIN_AZDATA_VERSION.raw, newVersion), ...responses);
Logger.log(loc.userResponseToUpdatePrompt(response));
if (response === loc.doNotAskAgain) {
await setConfig(azdatarequiredUpdateKey, AzdataDeployOption.dontPrompt);
} else if (response === loc.yes) {
return updateAzdata(newVersion);
} else {
vscode.window.showWarningMessage(loc.missingRequiredVersion(MIN_AZDATA_VERSION.raw));
}
} else {
let response: string | undefined = loc.yes;
const config = <AzdataDeployOption>getConfig(azdataUpdateKey);
if (userRequested) {
Logger.show();
Logger.log(loc.userRequestedUpdate);
}
if (config === AzdataDeployOption.dontPrompt && !userRequested) {
Logger.log(loc.skipUpdate(config));
return false;
}
const responses = userRequested
? [loc.yes, loc.no]
: [loc.yes, loc.askLater, loc.doNotAskAgain];
if (config === AzdataDeployOption.prompt) {
Logger.log(loc.promptForAzdataUpdateLog(newVersion));
response = await vscode.window.showInformationMessage(loc.promptForAzdataUpdate(newVersion), ...responses);
Logger.log(loc.userResponseToUpdatePrompt(response));
}
if (response === loc.doNotAskAgain) {
await setConfig(azdataUpdateKey, AzdataDeployOption.dontPrompt);
} else if (response === loc.yes) {
return updateAzdata(newVersion);
}
}
return false;
}
/**
* Returns true if Eula has been accepted.
*
* @param memento The memento that stores the eulaAccepted state
*/
export function isEulaAccepted(memento: vscode.Memento): boolean {
return !!memento.get<boolean>(eulaAccepted);
}
/**
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
* @param memento - memento where the user response is stored.
* @param userRequested - if true this operation was requested in response to a user issued command, if false it was issued at startup by system
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
* pre-requisite, the calling code has to ensure that the eula has not yet been previously accepted by the user.
* returns true if the user accepted the EULA.
*/
export async function promptForEula(memento: vscode.Memento, userRequested: boolean = false, requireUserAction: boolean = false): Promise<boolean> {
let response: string | undefined = loc.no;
const config = <AzdataDeployOption>getConfig(azdataAcceptEulaKey);
if (userRequested) {
Logger.show();
Logger.log(loc.userRequestedAcceptEula);
}
const responses = userRequested
? [loc.accept, loc.decline]
: [loc.accept, loc.askLater, loc.doNotAskAgain];
if (config === AzdataDeployOption.prompt || userRequested) {
Logger.show();
Logger.log(loc.promptForEulaLog(microsoftPrivacyStatementUrl, eulaUrl));
response = requireUserAction
? await vscode.window.showErrorMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses)
: await vscode.window.showInformationMessage(loc.promptForEula(microsoftPrivacyStatementUrl, eulaUrl), ...responses);
Logger.log(loc.userResponseToEulaPrompt(response));
}
if (response === loc.doNotAskAgain) {
await setConfig(azdataAcceptEulaKey, AzdataDeployOption.dontPrompt);
} else if (response === loc.accept) {
await memento.update(eulaAccepted, true); // save a memento that eula was accepted
await vscode.commands.executeCommand('setContext', eulaAccepted, true); // save a context key that eula was accepted so that command for accepting eula is no longer available in commandPalette
return true;
}
return false;
}
/**
* Downloads the Windows installer and runs it
*/
async function downloadAndInstallAzdataWin32(): Promise<void> {
const downLoadLink = await getPlatformDownloadLink();
const downloadFolder = os.tmpdir();
const downloadLogs = path.join(downloadFolder, 'ads_azdata_install_logs.log');
const downloadedFile = await HttpClient.downloadFile(downLoadLink, downloadFolder);
try {
await executeSudoCommand(`msiexec /qn /i "${downloadedFile}" /lvx "${downloadLogs}"`);
} catch (err) {
throw new Error(`${err.message}. See logs at ${downloadLogs} for more details.`);
}
}
/**
* Runs commands to install azdata on MacOS
*/
async function installAzdataDarwin(): Promise<void> {
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
await executeCommand('brew', ['update']);
await executeCommand('brew', ['install', 'azdata-cli']);
}
/**
* Runs commands to update azdata on MacOS
*/
async function updateAzdataDarwin(): Promise<void> {
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
await executeCommand('brew', ['update']);
await executeCommand('brew', ['upgrade', 'azdata-cli']);
}
/**
* Runs commands to install azdata on Linux
*/
async function installAzdataLinux(): Promise<void> {
// https://docs.microsoft.com/en-us/sql/big-data-cluster/deploy-install-azdata-linux-package
// Get packages needed for install process
await executeSudoCommand('apt-get update');
await executeSudoCommand('apt-get install gnupg ca-certificates curl wget software-properties-common apt-transport-https lsb-release -y');
// Download and install the signing key
await executeSudoCommand('curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null');
// Add the azdata repository information
const release = (await executeCommand('lsb_release', ['-rs'])).stdout.trim();
await executeSudoCommand(`add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/${release}/mssql-server-2019.list)"`);
// Update repository information and install azdata
await executeSudoCommand('apt-get update');
await executeSudoCommand('apt-get install -y azdata-cli');
}
/**
*/
async function findSpecificAzdata(): Promise<IAzdataTool> {
const path = await ((process.platform === 'win32') ? searchForCmd('azdata.cmd') : searchForCmd('azdata'));
const versionOutput = await executeAzdataCommand(`"${path}"`, ['--version']);
return new AzdataTool(path, parseVersion(versionOutput.stdout));
}
function getConfig(key: string): AzdataDeployOption | undefined {
const config = vscode.workspace.getConfiguration(azdataConfigSection);
const value = <AzdataDeployOption>config.get<AzdataDeployOption>(key);
Logger.log(loc.azdataUserSettingRead(key, value));
return value;
}
async function setConfig(key: string, value: string): Promise<void> {
const config = vscode.workspace.getConfiguration(azdataConfigSection);
await config.update(key, value, vscode.ConfigurationTarget.Global);
Logger.log(loc.azdataUserSettingUpdated(key, value));
}
/**
* Gets the latest azdata version available for a given platform
*/
export async function discoverLatestAvailableAzdataVersion(): Promise<SemVer> {
Logger.log(loc.checkingLatestAzdataVersion);
switch (process.platform) {
case 'darwin':
return await discoverLatestStableAzdataVersionDarwin();
// case 'linux':
// ideally we would not to discover linux package availability using the apt/apt-get/apt-cache package manager commands.
// However, doing discovery that way required apt update to be performed which requires sudo privileges. At least currently this code path
// gets invoked on extension start up and prompt user for sudo privileges is annoying at best. So for now basing linux discovery also on a releaseJson file.
default:
return await getPlatformReleaseVersion();
}
}
/**
* Parses out the azdata version from the raw azdata version output
* @param raw The raw version output from azdata --version
*/
function parseVersion(raw: string): string {
// Currently the version is a multi-line string that contains other version information such
// as the Python installation, with the first line being the version of azdata itself.
const lines = raw.split(os.EOL);
return lines[0].trim();
}
/**
* Gets the latest azdata version for MacOs clients
*/
async function discoverLatestStableAzdataVersionDarwin(): Promise<SemVer> {
// set brew tap to azdata-cli repository
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
await executeCommand('brew', ['update']);
let brewInfoAzdataCliJson;
// Get the package version 'info' about 'azdata-cli' from 'brew' as a json object
const brewInfoOutput = (await executeCommand('brew', ['info', 'azdata-cli', '--json'])).stdout;
try {
brewInfoAzdataCliJson = JSON.parse(brewInfoOutput);
} catch (e) {
throw Error(`failed to parse the JSON contents output of: 'brew info azdata-cli --json', text being parsed: '${brewInfoOutput}', error:${getErrorMessage(e)}`);
}
// Get the 'info' about 'azdata-cli' from 'brew' as a json object
const azdataPackageVersionInfo: AzdataDarwinPackageVersionInfo = brewInfoAzdataCliJson.shift();
Logger.log(loc.latestAzdataVersionAvailable(azdataPackageVersionInfo.versions.stable));
return new SemVer(azdataPackageVersionInfo.versions.stable);
}
async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<ProcessOutput> {
additionalEnvVars = Object.assign(additionalEnvVars, { 'ACCEPT_EULA': 'yes' });
const debug = vscode.workspace.getConfiguration(azdataConfigSection).get(debugConfigKey);
if (debug) {
args.push('--debug');
}
return executeCommand(command, args, additionalEnvVars);
}
/**
* Gets the latest azdata version for linux clients
* This method requires sudo permission so not suitable to be run during startup.
*/
// async function discoverLatestStableAzdataVersionLinux(): Promise<SemVer> {
// // Update repository information and install azdata
// await executeSudoCommand('apt-get update');
// const output = (await executeCommand('apt', ['list', 'azdata-cli', '--upgradeable'])).stdout;
// // the packageName (with version) string is the second space delimited token on the 2nd line
// const packageName = output.split('\n')[1].split(' ')[1];
// // the version string is the first part of the package sting before '~'
// const version = packageName.split('~')[0];
// Logger.log(loc.latestAzdataVersionAvailable(version));
// return new SemVer(version);
// }

View File

@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as loc from './localizedConstants';
import { SemVer } from 'semver';
import { HttpClient } from './common/httpClient';
import Logger from './common/logger';
import { getErrorMessage } from './common/utils';
import { azdataHostname, azdataReleaseJson } from './constants';
interface PlatformReleaseInfo {
version: string; // "20.0.1"
link?: string; // "https://aka.ms/azdata-msi"
}
export interface AzdataReleaseInfo {
win32: PlatformReleaseInfo,
darwin: PlatformReleaseInfo,
linux: PlatformReleaseInfo
}
function getPlatformAzdataReleaseInfo(releaseInfo: AzdataReleaseInfo): PlatformReleaseInfo {
switch (os.platform()) {
case 'win32':
return releaseInfo.win32;
case 'linux':
return releaseInfo.linux;
case 'darwin':
return releaseInfo.darwin;
default:
Logger.log(loc.platformUnsupported(os.platform()));
throw new Error(`Unsupported AzdataReleaseInfo platform '${os.platform()}`);
}
}
/**
* Gets the release version for the current platform from the release info - throwing an error if it doesn't exist.
* @param releaseInfo The AzdataReleaseInfo object
*/
export async function getPlatformReleaseVersion(): Promise<SemVer> {
const releaseInfo = await getAzdataReleaseInfo();
const platformReleaseInfo = getPlatformAzdataReleaseInfo(releaseInfo);
if (!platformReleaseInfo.version) {
Logger.log(loc.noReleaseVersion(os.platform(), JSON.stringify(releaseInfo)));
throw new Error(`No release version available for platform ${os.platform()}`);
}
Logger.log(loc.latestAzdataVersionAvailable(platformReleaseInfo.version));
return new SemVer(platformReleaseInfo.version);
}
/**
* Gets the download link for the current platform from the release info - throwing an error if it doesn't exist.
* @param releaseInfo The AzdataReleaseInfo object
*/
export async function getPlatformDownloadLink(): Promise<string> {
const releaseInfo = await getAzdataReleaseInfo();
const platformReleaseInfo = getPlatformAzdataReleaseInfo(releaseInfo);
if (!platformReleaseInfo.link) {
Logger.log(loc.noDownloadLink(os.platform(), JSON.stringify(releaseInfo)));
throw new Error(`No download link available for platform ${os.platform()}`);
}
return platformReleaseInfo.link;
}
async function getAzdataReleaseInfo(): Promise<AzdataReleaseInfo> {
const fileContents = await HttpClient.getTextContent(`${azdataHostname}/${azdataReleaseJson}`);
try {
return JSON.parse(fileContents);
} catch (e) {
Logger.log(loc.failedToParseReleaseInfo(`${azdataHostname}/${azdataReleaseJson}`, fileContents, e));
throw Error(`Failed to parse the JSON of contents at: ${azdataHostname}/${azdataReleaseJson}. Error: ${getErrorMessage(e)}`);
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AdditionalEnvVars } from 'azdata-ext';
import { AdditionalEnvVars } from 'az-ext';
import * as cp from 'child_process';
import * as sudo from 'sudo-prompt';
import * as loc from '../localizedConstants';

View File

@@ -10,7 +10,7 @@ export class Log {
private _output: vscode.OutputChannel;
constructor() {
this._output = vscode.window.createOutputChannel(loc.azdata);
this._output = vscode.window.createOutputChannel(loc.az);
}
log(msg: string): void {

View File

@@ -3,19 +3,30 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as which from 'which';
import * as loc from '../localizedConstants';
export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
export class NoAzureCLIError extends Error implements azExt.ErrorWithLink {
constructor() {
super(loc.noAzdata);
super(loc.noAzureCLI);
}
public get messageWithLink(): string {
return loc.noAzdataWithLink;
return loc.noAzureCLI;
}
}
export class AzureCLIArcExtError extends Error implements azExt.ErrorWithLink {
constructor() {
super(loc.arcdataExtensionNotInstalled);
}
public get messageWithLink(): string {
return loc.arcdataExtensionNotInstalled;
}
}
/**
* Searches for the first instance of the specified executable in the PATH environment variable
* @param exe The executable to search for

View File

@@ -4,21 +4,13 @@
*--------------------------------------------------------------------------------------------*/
// config setting keys
export const azdataConfigSection: string = 'azcli';
export const azdataAcceptEulaKey: string = 'acceptEula';
export const azdataInstallKey: string = 'install';
export const azdataUpdateKey: string = 'update';
export const azdatarequiredUpdateKey: string = 'requiredUpdate';
export const azConfigSection: string = 'azcli';
export const debugConfigKey = 'logDebugInfo';
export const azRequiredUpdateKey: string = 'requiredUpdate';
// context keys && memento keys
export const eulaAccepted = 'azcli.eulaAccepted';
export const azdataFound = 'azcli.found';
export const azFound = 'az.found';
// other constants
export const azdataHostname = 'https://aka.ms';
export const azdataUri = 'azdata-msi';
export const azdataReleaseJson = 'azdata/release.json';
export const microsoftPrivacyStatementUrl = 'https://privacy.microsoft.com/privacystatement';
export const eulaUrl = 'https://aka.ms/eula-azdata-en';
export const latestAzArcExtensionVersion = '1.0.0';

View File

@@ -3,70 +3,26 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
import * as rd from 'resource-deployment';
import * as vscode from 'vscode';
import { getExtensionApi } from './api';
import { checkAndInstallAzdata, checkAndUpdateAzdata, findAzdata, isEulaAccepted, promptForEula } from './azdata';
import Logger from './common/logger';
import * as constants from './constants';
import * as loc from './localizedConstants';
import { AzdataToolService } from './services/azdataToolService';
import { findAz } from './az';
import { ArcControllerConfigProfilesOptionsSource } from './providers/arcControllerConfigProfilesOptionsSource';
import { AzToolService } from './services/azToolService';
export async function activate(context: vscode.ExtensionContext): Promise<azdataExt.IExtension> {
const azdataToolService = new AzdataToolService();
let eulaAccepted: boolean = false;
vscode.commands.registerCommand('azcli.acceptEula', async () => {
await promptForEula(context.globalState, true /* userRequested */);
});
export async function activate(context: vscode.ExtensionContext): Promise<azExt.IExtension> {
const azToolService = new AzToolService();
vscode.commands.registerCommand('azcli.install', async () => {
azdataToolService.localAzdata = await checkAndInstallAzdata(true /* userRequested */);
});
azToolService.localAz = await findAz();
vscode.commands.registerCommand('azcli.update', async () => {
if (await checkAndUpdateAzdata(azdataToolService.localAzdata, true /* userRequested */)) { // if an update was performed
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
}
});
eulaAccepted = isEulaAccepted(context.globalState); // fetch eula acceptance state from memento
await vscode.commands.executeCommand('setContext', constants.eulaAccepted, eulaAccepted); // set a context key for current value of eulaAccepted state retrieved from memento so that command for accepting eula is available/unavailable in commandPalette appropriately.
Logger.log(loc.eulaAcceptedStateOnStartup(eulaAccepted));
// Don't block on this since we want the extension to finish activating without needing user input
const localAzdataDiscovered = checkAndInstallAzdata() // install if not installed and user wants it.
.then(async azdataTool => {
if (azdataTool !== undefined) {
azdataToolService.localAzdata = azdataTool;
if (!eulaAccepted) {
// Don't block on this since we want extension to finish activating without requiring user actions.
// If EULA has not been accepted then we will check again while executing azdata commands.
promptForEula(context.globalState)
.then(async (userResponse: boolean) => {
eulaAccepted = userResponse;
})
.catch((err) => console.log(err));
}
try {
//update if available and user wants it.
if (await checkAndUpdateAzdata(azdataToolService.localAzdata)) { // if an update was performed
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
}
} catch (err) {
vscode.window.showWarningMessage(loc.updateError(err));
}
}
return azdataTool;
});
const azdataApi = getExtensionApi(context.globalState, azdataToolService, localAzdataDiscovered);
const azApi = getExtensionApi(azToolService);
// register option source(s)
// TODO: Uncomment this once azdata extension is removed
// const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
// context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)));
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azApi)));
return azdataApi;
return azApi;
}
export function deactivate(): void { }

View File

@@ -5,69 +5,31 @@
import * as nls from 'vscode-nls';
import { getErrorMessage } from './common/utils';
import { azdataConfigSection, azdataInstallKey, azdataUpdateKey, azdatarequiredUpdateKey } from './constants';
const localize = nls.loadMessageBundle();
export const azdata = localize('azdata.azdata', "Azure Data CLI");
export const searchingForAzdata = localize('azdata.searchingForAzdata', "Searching for existing Azure Data CLI installation...");
export const foundExistingAzdata = (path: string, version: string): string => localize('azdata.foundExistingAzdata', "Found existing Azure Data CLI installation of version (v{0}) at path:{1}", version, path);
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('azdata.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
export const downloadFinished = localize('azdata.downloadFinished', "Download finished");
export const installingAzdata = localize('azdata.installingAzdata', "Installing Azure Data CLI...");
export const updatingAzdata = localize('azdata.updatingAzdata', "Updating Azure Data CLI...");
export const azdataInstalled = localize('azdata.azdataInstalled', "Azure Data CLI was successfully installed. Restarting Azure Data Studio is required to complete configuration - features will not be activated until this is done.");
export const azdataUpdated = (version: string) => localize('azdata.azdataUpdated', "Azure Data CLI was successfully updated to version: {0}.", version);
export const yes = localize('azdata.yes', "Yes");
export const no = localize('azdata.no', "No");
export const accept = localize('azdata.accept', "Accept");
export const decline = localize('azdata.decline', "Decline");
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
export const askLater = localize('azdata.askLater', "Ask Later");
export const downloadingTo = (name: string, url: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} from {1} to {2}", name, url, location);
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of Azure Data CLI");
export const gettingTextContentsOfUrl = (url: string): string => localize('azdata.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
export const foundAzdataVersionToUpdateTo = (newVersion: string, currentVersion: string): string => localize('azdata.versionForUpdate', "Found version: {0} that Azure Data CLI can be updated to from current version: {1}.", newVersion, currentVersion);
export const latestAzdataVersionAvailable = (version: string): string => localize('azdata.latestAzdataVersionAvailable', "Latest available Azure Data CLI version: {0}.", version);
export const couldNotFindAzdata = (err: any): string => localize('azdata.couldNotFindAzdata', "Could not find Azure Data CLI. Error: {0}", err.message ?? err);
export const currentlyInstalledVersionIsLatest = (currentVersion: string): string => localize('azdata.currentlyInstalledVersionIsLatest', "Currently installed version of Azure Data CLI: {0} is same or newer than any other version available", currentVersion);
export const promptLog = (logEntry: string) => localize('azdata.promptLog', "Prompting the user to accept the following: {0}", logEntry);
export const promptForAzdataInstall = localize('azdata.couldNotFindAzdataWithPrompt', "Could not find Azure Data CLI, install it now? If not then some features will not be able to function.");
export const promptForAzdataInstallLog = promptLog(promptForAzdataInstall);
export const promptForAzdataUpdate = (version: string): string => localize('azdata.promptForAzdataUpdate', "A new version of Azure Data CLI ( {0} ) is available, do you wish to update to it now?", version);
export const promptForRequiredAzdataUpdate = (requiredVersion: string, latestVersion: string): string => localize('azdata.promptForRequiredAzdataUpdate', "This extension requires Azure Data CLI >= {0} to be installed, do you wish to update to the latest version ({1}) now? If you do not then some functionality may not work.", requiredVersion, latestVersion);
export const requiredVersionNotAvailable = (requiredVersion: string, currentVersion: string): string => localize('azdata.requiredVersionNotAvailable', "This extension requires Azure Data CLI >= {0} to be installed, but the current version available is only {1}. Install the correct version manually from [here](https://docs.microsoft.com/sql/azdata/install/deploy-install-azdata) and then restart Azure Data Studio.", requiredVersion, currentVersion);
export const promptForAzdataUpdateLog = (version: string): string => promptLog(promptForAzdataUpdate(version));
export const promptForRequiredAzdataUpdateLog = (requiredVersion: string, latestVersion: string): string => promptLog(promptForRequiredAzdataUpdate(requiredVersion, latestVersion));
export const missingRequiredVersion = (requiredVersion: string): string => localize('azdata.missingRequiredVersion', "Azure Data CLI >= {0} is required for this feature. Run the 'Azure Data CLI: Check for Update' command to install this and then try again.", requiredVersion);
export const downloadError = localize('azdata.downloadError', "Error while downloading");
export const installError = (err: any): string => localize('azdata.installError', "Error installing Azure Data CLI: {0}", err.message ?? err);
export const updateError = (err: any): string => localize('azdata.updateError', "Error updating Azure Data CLI: {0}", err.message ?? err);
export const platformUnsupported = (platform: string): string => localize('azdata.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
export const unexpectedCommandError = (errMsg: string): string => localize('azdata.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
export const noAzdata = localize('azdata.noAzdata', "No Azure Data CLI is available, run the command 'Azure Data CLI: Install' to enable the features that require it.");
export const noAzdataWithLink = localize('azdata.noAzdataWithLink', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
export const skipInstall = (config: string): string => localize('azdata.skipInstall', "Skipping installation of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataInstallKey, config);
export const skipUpdate = (config: string): string => localize('azdata.skipUpdate', "Skipping update of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataUpdateKey, config);
export const skipRequiredUpdate = (config: string): string => localize('azdata.skipRequiredUpdate', "Skipping required update of Azure Data CLI, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdatarequiredUpdateKey, config);
export const noReleaseVersion = (platform: string, releaseInfo: string): string => localize('azdata.noReleaseVersion', "No release version available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
export const noDownloadLink = (platform: string, releaseInfo: string): string => localize('azdata.noDownloadLink', "No download link available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
export const failedToParseReleaseInfo = (url: string, fileContents: string, err: any): string => localize('azdata.failedToParseReleaseInfo', "Failed to parse the JSON of contents at: {0}.\nFile contents:\n{1}\nError: {2}", url, fileContents, getErrorMessage(err));
export const azdataUserSettingRead = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingReadLog', "Azure Data CLI user setting: {0}.{1} read, value: {2}", azdataConfigSection, configName, configValue);
export const azdataUserSettingUpdated = (configName: string, configValue: string): string => localize('azdata.azdataUserSettingUpdatedLog', "Azure Data CLI user setting: {0}.{1} updated, newValue: {2}", azdataConfigSection, configName, configValue);
export const userResponseToInstallPrompt = (response: string | undefined): string => localize('azdata.userResponseInstall', "User Response on prompt to install Azure Data CLI: {0}", response);
export const userResponseToUpdatePrompt = (response: string | undefined): string => localize('azdata.userResponseUpdate', "User Response on prompt to update Azure Data CLI: {0}", response);
export const userRequestedInstall = localize('azdata.userRequestedInstall', "User requested to install Azure Data CLI using 'Azure Data CLI: Install' command");
export const userRequestedUpdate = localize('azdata.userRequestedUpdate', "User requested to update Azure Data CLI using 'Azure Data CLI: Check for Update' command");
export const userRequestedAcceptEula = localize('azdata.acceptEula', "User requested to be prompted for accepting EULA by invoking 'Azure Data CLI: Accept EULA' command");
export const updateCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new Azure Data CLI version availability performed as Azure Data CLI was not found to be installed");
export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA](command:azdata.acceptEula) to accept EULA to enable the features that requires Azure Data CLI.");
export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl);
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
export const endpointOrNamespaceRequired = localize('azdata.endpointOrNamespaceRequired', "Either an endpoint or a namespace must be specified");
export const az = localize('az.az', "Azure CLI");
export const searchingForAz = localize('az.searchingForAz', "Searching for existing Azure CLI installation...");
export const foundExistingAz = (path: string, version: string): string => localize('az.foundExistingAz', "Found existing Azure CLI installation of version (v{0}) at path:{1}", version, path);
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('az.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
export const downloadFinished = localize('az.downloadFinished', "Download finished");
export const downloadingTo = (name: string, url: string, location: string): string => localize('az.downloadingTo', "Downloading {0} from {1} to {2}", name, url, location);
export const executingCommand = (command: string, args: string[]): string => localize('az.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
export const stdoutOutput = (stdout: string): string => localize('az.stdoutOutput', "stdout: {0}", stdout);
export const stderrOutput = (stderr: string): string => localize('az.stderrOutput', "stderr: {0}", stderr);
export const gettingTextContentsOfUrl = (url: string): string => localize('az.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
export const promptLog = (logEntry: string) => localize('az.promptLog', "Prompting the user to accept the following: {0}", logEntry);
export const downloadError = localize('az.downloadError', "Error while downloading");
export const platformUnsupported = (platform: string): string => localize('az.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
export const unexpectedCommandError = (errMsg: string): string => localize('az.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
export const unexpectedExitCode = (code: number, err: string): string => localize('az.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
export const noReleaseVersion = (platform: string, releaseInfo: string): string => localize('az.noReleaseVersion', "No release version available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
export const noDownloadLink = (platform: string, releaseInfo: string): string => localize('az.noDownloadLink', "No download link available for platform '{0}'\nRelease info: ${1}", platform, releaseInfo);
export const failedToParseReleaseInfo = (url: string, fileContents: string, err: any): string => localize('az.failedToParseReleaseInfo', "Failed to parse the JSON of contents at: {0}.\nFile contents:\n{1}\nError: {2}", url, fileContents, getErrorMessage(err));
export const endpointOrNamespaceRequired = localize('az.endpointOrNamespaceRequired', "Either an endpoint or a namespace must be specified");
export const arcdataExtensionNotInstalled = localize('az.arcdataExtensionNotInstalled', "This extension requires the Azure CLI extension 'arcdata' to be installed. Install the latest version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.");
export const noAzureCLI = localize('az.noAzureCLI', "No Azure CLI is available. Install the latest version manually from [here](https://docs.microsoft.com/cli/azure/install-azure-cli) and then restart Azure Data Studio.");
export const requiredArcDataVersionNotAvailable = (requiredVersion: string, currentVersion: string): string => localize('az.requiredVersionNotAvailable', "This extension requires the Azure CLI extension 'arcdata' version >= {0} to be installed, but the current version available is only {1}. Install the correct version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.", requiredVersion, currentVersion);
export const unsupportedArcDataVersion = (requiredVersion: string, currentVersion: string): string => localize('az.unsupportedArcDataVersion', "Your downloaded version {1} of the Azure CLI extension 'arcdata' is not yet supported. The latest version is is {0}. Install the correct version manually from [here](https://docs.microsoft.com/azure/azure-arc/data/install-arcdata-extension) and then restart Azure Data Studio.", requiredVersion, currentVersion);
export const doNotAskAgain = localize('az.doNotAskAgain', "Don't Ask Again");
export const askLater = localize('az.askLater', "Ask Later");
export const azOutputParseErrorCaught = (command: string): string => localize('az.azOutputParseErrorCaught', "An error occurred while parsing the output of az command: {0}. The output is not JSON.", command);

View File

@@ -4,19 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as rd from 'resource-deployment';
import * as azdataExt from 'azdata-ext';
import * as azExt from 'az-ext';
/**
* Class that provides options sources for an Arc Data Controller
*/
export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider {
readonly id = 'arc.controller.config.profiles';
constructor(private _azdataExtApi: azdataExt.IExtension) { }
readonly id = 'azcli.arc.controller.config.profiles';
constructor(private _azExtApi: azExt.IExtension) { }
async getOptions(): Promise<string[]> {
const isEulaAccepted = await this._azdataExtApi.isEulaAccepted();
if (!isEulaAccepted) { // if eula has not yet be accepted then give user a chance to accept it
await this._azdataExtApi.promptForEula();
}
return (await this._azdataExtApi.azdata.arc.dc.config.list()).result;
return (await this._azExtApi.az.arcdata.dc.config.list()).stdout;
}
}

View File

@@ -3,25 +3,24 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAzdataTool } from '../azdata';
import { IAzTool } from '../az';
export class AzdataToolService {
private _localAzdata: IAzdataTool | undefined;
export class AzToolService {
private _localAz: IAzTool | undefined;
constructor() {
}
/**
* Gets the localAzdata that was last saved
* Gets the localAz that was last saved
*/
get localAzdata(): IAzdataTool | undefined {
return this._localAzdata;
get localAz(): IAzTool | undefined {
return this._localAz;
}
/**
* Sets the localAzdata object to be used for azdata operations
* Sets the localAz object to be used for az operations
*/
set localAzdata(azdata: IAzdataTool | undefined) {
this._localAzdata = azdata;
set localAz(az: IAzTool | undefined) {
this._localAz = az;
}
}

View File

@@ -1,121 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// /*---------------------------------------------------------------------------------------------
// * Copyright (c) Microsoft Corporation. All rights reserved.
// * Licensed under the Source EULA. See License.txt in the project root for license information.
// *--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as childProcess from '../common/childProcess';
import * as sinon from 'sinon';
import * as should from 'should';
import * as vscode from 'vscode';
import * as TypeMoq from 'typemoq';
import { getExtensionApi, throwIfNoAzdataOrEulaNotAccepted } from '../api';
import { AzdataToolService } from '../services/azdataToolService';
import { assertRejected } from './testUtils';
import { AzdataTool, IAzdataTool, AzdataDeployOption } from '../azdata';
// import * as azExt from 'az-ext';
// import * as childProcess from '../common/childProcess';
// import * as sinon from 'sinon';
// import * as vscode from 'vscode';
// import * as TypeMoq from 'typemoq';
// import { getExtensionApi } from '../api';
// import { AzToolService } from '../services/azToolService';
// import { assertRejected } from './testUtils';
// import { AzTool } from '../azdata';
describe('api', function (): void {
afterEach(function (): void {
sinon.restore();
});
describe('throwIfNoAzdataOrEulaNotAccepted', function (): void {
it('throws when no azdata', function (): void {
should(() => throwIfNoAzdataOrEulaNotAccepted(undefined, false)).throw();
});
it('throws when EULA not accepted', function (): void {
should(() => throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, false)).throw();
});
it('passes with AzdataTool and EULA accepted', function (): void {
throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, true);
});
});
describe('getExtensionApi', function (): void {
it('throws when no azdata', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const azdataToolService = new AzdataToolService();
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(undefined));
await assertRejected(api.isEulaAccepted(), 'isEulaAccepted');
await assertApiCalls(api, assertRejected);
});
// describe('api', function (): void {
// afterEach(function (): void {
// sinon.restore();
// });
// describe('getExtensionApi', function (): void {
// it('throws when no az', async function (): Promise<void> {
// const azToolService = new AzToolService();
// const api = getExtensionApi(azToolService);
// await assertApiCalls(api, assertRejected);
// });
it('throws when EULA not accepted', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => false);
const azdataToolService = new AzdataToolService();
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
should(await api.isEulaAccepted()).be.false('EULA should not be accepted');
await assertApiCalls(api, assertRejected);
});
// it('succeed when az present and EULA accepted', async function (): Promise<void> {
// const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
// mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
// const azTool = new AzTool('', '99.0.0');
// const azToolService = new AzToolService();
// azToolService.localAz = azTool;
// // Not using a mock here because it'll hang when resolving mocked objects
// const api = getExtensionApi(azToolService);
// sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
// // Version needs to be valid so it can be parsed correctly
// if (args[0] === '--version') {
// return { stdout: `99.0.0`, stderr: '' };
// }
// console.log(args[0]);
// return { stdout: `{ }`, stderr: '' };
// });
// await assertApiCalls(api, async (promise, message) => {
// try {
// await promise;
// } catch (err) {
// throw new Error(`API call to ${message} should have succeeded. ${err}`);
// }
// });
// });
it('succeed when azdata present and EULA accepted', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
const azdataTool = new AzdataTool('', '99.0.0');
const azdataToolService = new AzdataToolService();
azdataToolService.localAzdata = azdataTool;
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(azdataTool));
should(await api.isEulaAccepted()).be.true('EULA should be accepted');
sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
// Version needs to be valid so it can be parsed correctly
if (args[0] === '--version') {
return { stdout: `99.0.0`, stderr: '' };
}
console.log(args[0]);
return { stdout: `{ }`, stderr: '' };
});
await assertApiCalls(api, async (promise, message) => {
try {
await promise;
} catch (err) {
throw new Error(`API call to ${message} should have succeeded. ${err}`);
}
});
});
// /**
// * Asserts that calls to the Az API behave as expected
// * @param api The API object to test the calls with
// * @param assertCallback The function to assert that the results are as expected
// */
// async function assertApiCalls(api: azExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
// await assertCallback(api.az.getPath(), 'getPath');
// await assertCallback(api.az.getSemVersion(), 'getSemVersion');
// await assertCallback(api.az.version(), 'version');
it('promptForEula', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
const azdataToolService = new AzdataToolService();
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage');
should(await api.promptForEula()).be.false();
should(showErrorMessageStub.called).be.true('User should have been prompted to accept');
});
// await assertCallback(api.az.arcdata.dc.config.list(), 'arc dc config list');
// await assertCallback(api.az.arcdata.dc.config.show(), 'arc dc config show');
/**
* Asserts that calls to the Azdata API behave as expected
* @param api The API object to test the calls with
* @param assertCallback The function to assert that the results are as expected
*/
async function assertApiCalls(api: azdataExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
await assertCallback(api.azdata.getPath(), 'getPath');
await assertCallback(api.azdata.getSemVersion(), 'getSemVersion');
await assertCallback(api.azdata.login({ endpoint: 'https://127.0.0.1' }, '', ''), 'login');
await assertCallback(api.azdata.login({ namespace: 'namespace' }, '', ''), 'login');
await assertCallback(api.azdata.version(), 'version');
await assertCallback(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
await assertCallback(api.azdata.arc.dc.config.list(), 'arc dc config list');
await assertCallback(api.azdata.arc.dc.config.show(), 'arc dc config show');
await assertCallback(api.azdata.arc.dc.endpoint.list(), 'arc dc endpoint list');
await assertCallback(api.azdata.arc.sql.mi.list(), 'arc sql mi list');
await assertCallback(api.azdata.arc.sql.mi.delete(''), 'arc sql mi delete');
await assertCallback(api.azdata.arc.sql.mi.show(''), 'arc sql mi show');
await assertCallback(api.azdata.arc.sql.mi.edit('', {}), 'arc sql mi edit');
await assertCallback(api.azdata.arc.postgres.server.list(), 'arc sql postgres server list');
await assertCallback(api.azdata.arc.postgres.server.delete(''), 'arc sql postgres server delete');
await assertCallback(api.azdata.arc.postgres.server.show(''), 'arc sql postgres server show');
await assertCallback(api.azdata.arc.postgres.server.edit('', {}), 'arc sql postgres server edit');
}
});
});
// await assertCallback(api.az.arcdata.dc.endpoint.list(), 'arc dc endpoint list');
// await assertCallback(api.az.sql.miarc.list(), 'arc sql mi list');
// await assertCallback(api.az.sql.miarc.delete(''), 'arc sql mi delete');
// await assertCallback(api.az.sql.miarc.show(''), 'arc sql mi show');
// await assertCallback(api.az.sql.miarc.edit('', {}), 'arc sql mi edit');
// await assertCallback(api.az.postgres.arcserver.list(), 'arc sql postgres server list');
// await assertCallback(api.az.postgres.arcserver.delete(''), 'arc sql postgres server delete');
// await assertCallback(api.az.postgres.arcserver.show(''), 'arc sql postgres server show');
// await assertCallback(api.az.postgres.arcserver.edit('', {}), 'arc sql postgres server edit');
// }
// });
// });

View File

@@ -5,228 +5,128 @@
import * as should from 'should';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import * as azdata from '../azdata';
import * as childProcess from '../common/childProcess';
import { HttpClient } from '../common/httpClient';
import * as utils from '../common/utils';
import * as loc from '../localizedConstants';
import * as os from 'os';
import * as fs from 'fs';
import { AzdataReleaseInfo } from '../azdataReleaseInfo';
import * as TypeMoq from 'typemoq';
import { eulaAccepted } from '../constants';
import * as azdata from '../az';
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', azdata.MIN_AZDATA_VERSION.raw);
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
/**
* This matches the schema of the JSON file used to determine the current version of
* azdata - do not modify unless also updating the corresponding JSON file
*/
const releaseJson: AzdataReleaseInfo = {
win32: {
'version': '9999.999.999',
'link': 'https://download.com/azdata-20.0.1.msi'
},
darwin: {
'version': '9999.999.999'
},
linux: {
'version': '9999.999.999'
}
};
let executeSudoCommandStub: sinon.SinonStub;
describe('azdata', function () {
describe('az', function () {
afterEach(function (): void {
sinon.restore();
});
describe('azdataTool', function (): void {
const azdataTool = new azdata.AzdataTool(os.tmpdir(), '1.0.0');
describe('azTool', function (): void {
const azTool = new azdata.AzTool('C:/Program Files (x86)/Microsoft SDKs/Azure/CLI2/wbin/az.cmd', '2.26.0');
let executeCommandStub: sinon.SinonStub;
const namespace = 'myNamespace';
const name = 'myName';
const connectivityMode = 'myConnectivityMode';
const resourceGroup = 'myResourceGroup';
const location = 'myLocation';
const subscription = 'mySubscription';
const profileName = 'myProfileName';
const storageClass = 'myStorageClass';
const namespace = 'arc4';
const name = 'cy-dc-4';
beforeEach(function (): void {
executeCommandStub = sinon.stub(childProcess, 'executeCommand').resolves({ stdout: '{}', stderr: '' });
});
describe('arc', function (): void {
describe('arcdata', function (): void {
describe('dc', function (): void {
it('create', async function (): Promise<void> {
await azdataTool.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
verifyExecuteCommandCalledWithArgs([
'arc', 'dc', 'create',
namespace,
name,
connectivityMode,
resourceGroup,
location,
subscription,
profileName,
storageClass]);
});
describe('endpoint', async function (): Promise<void> {
it('list', async function (): Promise<void> {
await azdataTool.arc.dc.endpoint.list();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'endpoint', 'list']);
await azTool.arcdata.dc.endpoint.list(namespace);
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'endpoint', 'list', '--k8s-namespace', namespace, '--use-k8s']);
});
});
describe('config', async function (): Promise<void> {
it('list', async function (): Promise<void> {
await azdataTool.arc.dc.config.list();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'list']);
await azTool.arcdata.dc.config.list();
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'config', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.dc.config.show();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show']);
await azTool.arcdata.dc.config.show(namespace);
verifyExecuteCommandCalledWithArgs(['arcdata', 'dc', 'config', 'show', '--k8s-namespace', namespace, '--use-k8s']);
});
});
});
describe('postgres', function (): void {
describe('server', function (): void {
it('delete', async function (): Promise<void> {
await azdataTool.arc.postgres.server.delete(name);
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'delete', name]);
});
it('list', async function (): Promise<void> {
await azdataTool.arc.postgres.server.list();
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.postgres.server.show(name);
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'show', name]);
});
it('edit', async function (): Promise<void> {
const args = {
adminPassword: true,
coresLimit: 'myCoresLimit',
coresRequest: 'myCoresRequest',
engineSettings: 'myEngineSettings',
extensions: 'myExtensions',
memoryLimit: 'myMemoryLimit',
memoryRequest: 'myMemoryRequest',
noWait: true,
port: 1337,
replaceEngineSettings: true,
workers: 2
};
await azdataTool.arc.postgres.server.edit(name, args);
verifyExecuteCommandCalledWithArgs([
'arc', 'postgres', 'server', 'edit',
name,
'--admin-password',
args.coresLimit,
args.coresRequest,
args.engineSettings,
args.extensions,
args.memoryLimit,
args.memoryRequest,
'--no-wait',
args.port.toString(),
'--replace-engine-settings',
args.workers.toString()]);
});
it('edit no optional args', async function (): Promise<void> {
await azdataTool.arc.postgres.server.edit(name, {});
verifyExecuteCommandCalledWithArgs([
'arc', 'postgres', 'server', 'edit',
name]);
verifyExecuteCommandCalledWithoutArgs([
'--admin-password',
'--cores-limit',
'--cores-request',
'--engine-settings',
'--extensions',
'--memory-limit',
'--memory-request',
'--no-wait',
'--port',
'--replace-engine-settings',
'--workers']);
});
});
});
describe('sql', function (): void {
describe('mi', function (): void {
it('delete', async function (): Promise<void> {
await azdataTool.arc.sql.mi.delete(name);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'delete', name]);
});
it('list', async function (): Promise<void> {
await azdataTool.arc.sql.mi.list();
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.sql.mi.show(name);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'show', name]);
});
});
});
it('general error throws', async function (): Promise<void> {
const err = new Error();
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
}
});
it('ExitCodeError handled and parsed correctly', async function (): Promise<void> {
const errorInnerText = 'my error text';
const err = new childProcess.ExitCodeError(1, `ERROR { "stderr": "${errorInnerText}"}`);
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
should((error as childProcess.ExitCodeError).stderr).equal(errorInnerText);
}
});
it('ExitCodeError general error with azdata tool existing rethrows original error', async function (): Promise<void> {
sinon.stub(fs.promises, 'access').resolves();
const err = new childProcess.ExitCodeError(1, 'some other error');
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
}
});
it('ExitCodeError general error with azdata tool not existing throws NoAzdataError', async function (): Promise<void> {
sinon.stub(fs.promises, 'access').throws(new Error('not found'));
const err = new childProcess.ExitCodeError(1, 'some other error');
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error instanceof utils.NoAzdataError).be.true('error should have been instance of NoAzdataError');
}
});
});
it('login', async function (): Promise<void> {
const endpoint = 'myEndpoint';
const username = 'myUsername';
const password = 'myPassword';
await azdataTool.login({ endpoint: endpoint }, username, password);
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
describe('postgres', function (): void {
describe('arc-server', function (): void {
it('delete', async function (): Promise<void> {
await azTool.postgres.arcserver.delete(name, namespace);
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'delete', name, '--k8s-namespace', namespace]);
});
it('list', async function (): Promise<void> {
await azTool.postgres.arcserver.list(namespace);
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'list', '--k8s-namespace', namespace]);
});
it('show', async function (): Promise<void> {
await azTool.postgres.arcserver.show(name, namespace);
verifyExecuteCommandCalledWithArgs(['postgres', 'arc-server', 'show', name, '--k8s-namespace', namespace]);
});
it('edit', async function (): Promise<void> {
const args = {
adminPassword: true,
coresLimit: 'myCoresLimit',
coresRequest: 'myCoresRequest',
engineSettings: 'myEngineSettings',
extensions: 'myExtensions',
memoryLimit: 'myMemoryLimit',
memoryRequest: 'myMemoryRequest',
noWait: true,
port: 1337,
replaceEngineSettings: true,
workers: 2
};
await azTool.postgres.arcserver.edit(name, args, namespace);
verifyExecuteCommandCalledWithArgs([
'postgres', 'arc-server', 'edit',
name,
'--admin-password',
args.coresLimit,
args.coresRequest,
args.engineSettings,
args.extensions,
args.memoryLimit,
args.memoryRequest,
'--no-wait',
args.port.toString(),
'--replace-engine-settings',
args.workers.toString()]);
});
it('edit no optional args', async function (): Promise<void> {
await azTool.postgres.arcserver.edit(name, {}, namespace);
verifyExecuteCommandCalledWithArgs([
'postgres', 'arc-server', 'edit',
name]);
verifyExecuteCommandCalledWithoutArgs([
'--admin-password',
'--cores-limit',
'--cores-request',
'--engine-settings',
'--extensions',
'--memory-limit',
'--memory-request',
'--no-wait',
'--port',
'--replace-engine-settings',
'--workers']);
});
});
});
describe('sql', function (): void {
describe('mi-arc', function (): void {
it('delete', async function (): Promise<void> {
await azTool.sql.miarc.delete(name, namespace);
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'delete', name, '--k8s-namespace', namespace, '--use-k8s']);
});
it('list', async function (): Promise<void> {
await azTool.sql.miarc.list(namespace);
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'list', '--k8s-namespace', namespace, '--use-k8s']);
});
it('show', async function (): Promise<void> {
await azTool.sql.miarc.show(name, namespace);
verifyExecuteCommandCalledWithArgs(['sql', 'mi-arc', 'show', name, '--k8s-namespace', namespace, '--use-k8s']);
});
});
});
it('version', async function (): Promise<void> {
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
await azdataTool.version();
await azTool.version();
verifyExecuteCommandCalledWithArgs(['--version']);
});
@@ -249,557 +149,4 @@ describe('azdata', function () {
}
});
describe('findAzdata', function (): void {
it('successful', async function (): Promise<void> {
// Mock searchForCmd to return a path to azdata.cmd
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
// Mock call to --version to simulate azdata being installed
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '1.0.0', stderr: '' }));
await should(azdata.findAzdata()).not.be.rejected();
});
it('unsuccessful', async function (): Promise<void> {
if (process.platform === 'win32') {
// Mock searchForCmd to return a failure to find azdata.cmd
sinon.stub(utils, 'searchForCmd').returns(Promise.reject(new Error('Could not find azdata')));
} else {
// Mock call to executeCommand to simulate azdata --version returning error
sinon.stub(childProcess, 'executeCommand').returns(Promise.reject({ stdout: '', stderr: 'command not found: azdata' }));
}
await should(azdata.findAzdata()).be.rejected();
});
});
describe('installAzdata', function (): void {
let errorMessageStub: sinon.SinonStub;
beforeEach(function (): void {
errorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(<any>loc.yes));
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
});
it('successful install', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SuccessfulInstall();
break;
case 'darwin':
await testDarwinSuccessfulInstall();
break;
case 'linux':
await testLinuxSuccessfulInstall();
break;
}
});
it('skipped install - dont prompt config', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => 'dontPrompt');
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SkippedInstall();
break;
case 'darwin':
await testDarwinSkippedInstall();
break;
case 'linux':
await testLinuxSkippedInstall();
break;
}
});
it('skipped install - user chose not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
errorMessageStub.resolves(<any>loc.doNotAskAgain);
switch (process.platform) {
case 'win32':
await testWin32SkippedInstall();
break;
case 'darwin':
await testDarwinSkippedInstall();
break;
case 'linux':
await testLinuxSkippedInstall();
break;
}
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
if (process.platform === 'win32') {
it('unsuccessful download - win32', async function (): Promise<void> {
sinon.stub(HttpClient, 'downloadFile').rejects();
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
const azdataTool = await azdata.checkAndInstallAzdata();
should(azdataTool).be.undefined();
});
}
it('unsuccessful install', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32UnsuccessfulInstall();
break;
case 'darwin':
await testDarwinUnsuccessfulInstall();
break;
case 'linux':
await testLinuxUnsuccessfulInstall();
break;
}
});
});
describe('updateAzdata', function (): void {
let showInformationMessageStub: sinon.SinonStub;
beforeEach(function (): void {
showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(releaseJson));
});
it('successful update', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SuccessfulUpdate();
break;
case 'darwin':
await testDarwinSuccessfulUpdate();
break;
case 'linux':
await testLinuxSuccessfulUpdate();
break;
}
});
it('successful update - always prompt if user requested', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SuccessfulUpdate(true);
break;
case 'darwin':
await testDarwinSuccessfulUpdate(true);
break;
case 'linux':
await testLinuxSuccessfulUpdate(true);
break;
}
});
it('skipped update - config set not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdateDontPrompt();
break;
case 'darwin':
await testDarwinSkippedUpdateDontPrompt();
break;
case 'linux':
await testLinuxSkippedUpdateDontPrompt();
break;
}
});
it('skipped update - user chose to never prompt again', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
showInformationMessageStub.resolves(<any>loc.doNotAskAgain);
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdateDontPrompt();
break;
case 'darwin':
await testDarwinSkippedUpdateDontPrompt();
break;
case 'linux':
await testLinuxSkippedUpdateDontPrompt();
break;
}
// Config should have been updated since user chose never to prompt again
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('skipped update - no new version', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdate();
break;
case 'darwin':
await testDarwinSkippedUpdate();
break;
case 'linux':
await testLinuxSkippedUpdate();
break;
}
});
it('skipped update - no azdata', async function (): Promise<void> {
const result = await azdata.checkAndUpdateAzdata();
should(result).be.false();
});
it('unsuccessful update', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32UnsuccessfulUpdate();
break;
case 'darwin':
await testDarwinUnsuccessfulUpdate();
break;
case 'linux':
await testLinuxUnsuccessfulUpdate();
}
});
describe('discoverLatestAvailableAzdataVersion', function (): void {
it('finds latest available version of azdata successfully', async function (): Promise<void> {
await azdata.discoverLatestAvailableAzdataVersion();
});
});
});
describe('promptForEula', function (): void {
it('skipped because of config', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const result = await azdata.promptForEula(mementoMock.object);
should(result).be.false();
});
it('always prompt if user requested', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
const result = await azdata.promptForEula(mementoMock.object, true);
should(result).be.false();
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('prompt if config set to do so', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
const result = await azdata.promptForEula(mementoMock.object);
should(result).be.false();
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('update config if user chooses not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.doNotAskAgain);
const result = await azdata.promptForEula(mementoMock.object);
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
should(result).be.false('EULA should not have been accepted');
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('user accepted EULA', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.accept);
const result = await azdata.promptForEula(mementoMock.object);
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
should(result).be.true('EULA should have been accepted');
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('user accepted EULA - require user action', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.accept);
const result = await azdata.promptForEula(mementoMock.object, true, true);
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
should(result).be.true('EULA should have been accepted');
should(showErrorMessage.calledOnce).be.true('showErrorMessage should have been called to prompt user');
});
});
describe('isEulaAccepted', function (): void {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
should(azdata.isEulaAccepted(mementoMock.object)).be.true();
});
});
async function testLinuxUnsuccessfulUpdate() {
executeSudoCommandStub.rejects();
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false();
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testDarwinUnsuccessfulUpdate() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
});
})
.onCall(5) //6th call is the first one to do actual update, the call number are 0 indexed
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => { // by default return success
return Promise.resolve({ stderr: '', stdout: 'success' });
});
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false();
should(executeCommandStub.callCount).equal(6);
}
async function testWin32UnsuccessfulUpdate() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
executeSudoCommandStub.rejects();
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false('Update should not have been successful');
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testLinuxSuccessfulUpdate(userRequested = false) {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeSudoCommandStub.callCount).be.equal(6);
should(executeCommandStub.calledOnce).be.true();
}
async function testDarwinSuccessfulUpdate(userRequested = false) {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.getCall(2).args[0]).be.equal('brew', '3rd call should have been to brew');
should(executeCommandStub.getCall(2).args[1]).deepEqual(['info', 'azdata-cli', '--json'], '3rd call did not have expected arguments');
}
async function testWin32SuccessfulUpdate(userRequested = false) {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
}
async function testLinuxSkippedUpdate() {
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
}
async function testDarwinSkippedUpdateDontPrompt() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
}
async function testWin32SkippedUpdateDontPrompt() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeSudoCommandStub.notCalled).be.true(`executeSudoCommand should not have been called ${executeSudoCommandStub.getCalls().join(os.EOL)}`);
}
async function testLinuxSkippedUpdateDontPrompt() {
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
}
async function testDarwinSkippedUpdate() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
}
async function testWin32SkippedUpdate() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
}
async function testDarwinSkippedInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
});
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeCommandStub.callCount).be.equal(0);
}
async function testLinuxSkippedInstall() {
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found'))
.resolves({ stdout: '0.0.0', stderr: '' });
executeSudoCommandStub
.resolves({ stdout: 'success', stderr: '' });
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeSudoCommandStub.callCount).be.equal(0);
}
async function testWin32SkippedInstall() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
executeSudoCommandStub
.returns({ stdout: '', stderr: '' });
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
}
async function testWin32SuccessfulInstall() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
executeSudoCommandStub
.returns({ stdout: '', stderr: '' });
await azdata.checkAndInstallAzdata();
should(executeCommandStub.calledTwice).be.true(`executeCommand should have been called twice. Actual ${executeCommandStub.getCalls().length}`);
should(executeSudoCommandStub.calledOnce).be.true(`executeSudoCommand should have been called once. Actual ${executeSudoCommandStub.getCalls().length}`);
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
}
async function testDarwinSuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
});
await azdata.checkAndInstallAzdata();
should(executeCommandStub.callCount).be.equal(5);
}
async function testLinuxSuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found'))
.resolves({ stdout: '0.0.0', stderr: '' });
executeSudoCommandStub
.resolves({ stdout: 'success', stderr: '' });
await azdata.checkAndInstallAzdata();
should(executeSudoCommandStub.callCount).be.equal(6);
should(executeCommandStub.calledThrice).be.true();
}
async function testLinuxUnsuccessfulInstall() {
executeSudoCommandStub.rejects();
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testDarwinUnsuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeCommandStub.calledOnce).be.true();
}
async function testWin32UnsuccessfulInstall() {
executeSudoCommandStub.rejects();
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeSudoCommandStub.calledOnce).be.true();
}

View File

@@ -1,75 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as sinon from 'sinon';
import { HttpClient } from '../common/httpClient';
import { getPlatformReleaseVersion, getPlatformDownloadLink, AzdataReleaseInfo } from '../azdataReleaseInfo';
const emptyReleaseJson = {
win32: {},
darwin: {},
linux: {}
};
const releaseVersion = '999.999.999';
const releaseLink = 'https://microsoft.com';
const validReleaseJson: AzdataReleaseInfo = {
win32: {
version: releaseVersion,
link: releaseLink
},
darwin: {
version: releaseVersion,
link: releaseLink
},
linux: {
version: releaseVersion,
link: releaseLink
}
};
describe('azdataReleaseInfo', function (): void {
afterEach(function (): void {
sinon.restore();
});
describe('getPlatformReleaseVersion', function(): void {
it('gets version successfully', async function(): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
const version = await getPlatformReleaseVersion();
should(version.format()).equal(releaseVersion);
});
it('throws with invalid JSON', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
await should(getPlatformReleaseVersion()).be.rejected();
});
it('throws when no version', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
await should(getPlatformReleaseVersion()).be.rejected();
});
});
describe('getPlatformDownloadLink', function(): void {
it('gets link successfully', async function(): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
const link = await getPlatformDownloadLink();
should(link).equal(releaseLink);
});
it('throws with invalid JSON', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
await should(getPlatformDownloadLink()).be.rejected();
});
it('throws when no version', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
await should(getPlatformDownloadLink()).be.rejected();
});
});
});

View File

@@ -2,25 +2,17 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import { NoAzdataError, searchForCmd as searchForExe } from '../../common/utils';
// import * as should from 'should';
// import { searchForCmd as searchForExe } from '../../common/utils';
describe('utils', function () {
describe('searchForExe', function (): void {
it('finds exe successfully', async function (): Promise<void> {
await searchForExe('node');
});
it('throws for non-existent exe', async function (): Promise<void> {
await should(searchForExe('someFakeExe')).be.rejected();
});
});
// describe('utils', function () {
// describe('searchForExe', function (): void {
// it('finds exe successfully', async function (): Promise<void> {
// await searchForExe('node');
// });
// it('throws for non-existent exe', async function (): Promise<void> {
// await should(searchForExe('someFakeExe')).be.rejected();
// });
// });
describe('NoAzdataError', function (): void {
it('error contains message with and without links', function (): void {
const error = new NoAzdataError();
should(error.message).not.be.empty();
should(error.messageWithLink).not.be.empty();
should(error.message).not.equal(error.messageWithLink, 'Messages should not be equal');
});
});
});
// });

View File

@@ -1,38 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdataExt from 'azdata-ext';
import * as should from 'should';
import * as sinon from 'sinon';
import { ArcControllerConfigProfilesOptionsSource } from '../../providers/arcControllerConfigProfilesOptionsSource';
describe('arcControllerConfigProfilesOptionsSource', async function (): Promise<void> {
afterEach(function(): void {
sinon.restore();
});
it('eula accepted returns list', async function (): Promise<void> {
const options = ['option1', 'option2'];
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
sinon.stub(api, 'isEulaAccepted').resolves(true);
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
const source = new ArcControllerConfigProfilesOptionsSource(api);
const result = await source.getOptions();
should(result).deepEqual(options);
});
it('eula not accepted prompts for acceptance', async function (): Promise<void> {
const options = ['option1', 'option2'];
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
sinon.stub(api, 'isEulaAccepted').resolves(false);
const promptStub = sinon.stub(api, 'promptForEula').resolves(true);
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
const source = new ArcControllerConfigProfilesOptionsSource(api);
const result = await source.getOptions();
should(result).deepEqual(options);
should(promptStub.calledOnce).be.true('promptForEula should have been called');
});
});

View File

@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import { AzdataTool } from '../../azdata';
import { AzdataToolService } from '../../services/azdataToolService';
import { AzTool } from '../../az';
import { AzToolService } from '../../services/azToolService';
describe('azdataToolService', function (): void {
describe('azToolService', function (): void {
it('Tool should be set correctly', async function (): Promise<void> {
const service = new AzdataToolService();
should(service.localAzdata).be.undefined();
service.localAzdata = new AzdataTool('my path', '1.0.0');
const service = new AzToolService();
should(service.localAz).be.undefined();
service.localAz = new AzTool('my path', '1.0.0');
should(service).not.be.undefined();
});
});

View File

@@ -3,18 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'azdata-ext' {
declare module 'az-ext' {
import { SemVer } from 'semver';
import * as vscode from 'vscode';
/**
* Covers defining what the azdata extension exports to other extensions
* Covers defining what the az extension exports to other extensions
*
* IMPORTANT: THIS IS NOT A HARD DEFINITION unlike vscode; therefore no enums or classes should be defined here
* (const enums get evaluated when typescript -> javascript so those are fine)
*/
export const enum extension {
name = 'Microsoft.azdata'
name = 'Microsoft.azcli'
}
export type AdditionalEnvVars = { [key: string]: string };
@@ -258,11 +257,9 @@ declare module 'azdata-ext' {
}
}
export interface AzdataOutput<R> {
logs: string[],
result: R,
export interface AzOutput<R> {
stdout: R,
stderr: string[],
stdout: string[],
code?: number
}
@@ -270,92 +267,75 @@ declare module 'azdata-ext' {
endpoint?: string,
namespace?: string
}
export interface IAzdataApi {
arc: {
export interface IAzApi {
arcdata: {
dc: {
create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
endpoint: {
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcEndpointListResult[]>>
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcEndpointListResult[]>>
},
config: {
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigListResult[]>>,
show(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<DcConfigShowResult>>
}
},
postgres: {
server: {
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerListResult[]>>,
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<PostgresServerShowResult>>,
edit(
name: string,
args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
coordinatorEngineSettings?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workerEngineSettings?: string,
workers?: number
},
additionalEnvVars?: AdditionalEnvVars,
azdataContext?: string
): Promise<AzdataOutput<void>>
}
},
sql: {
mi: {
delete(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
list(additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiListResult[]>>,
show(name: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<SqlMiShowResult>>,
edit(
name: string,
args: {
coresLimit?: string,
coresRequest?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
},
additionalEnvVars?: AdditionalEnvVars,
azdataContext?: string
): Promise<AzdataOutput<void>>
list(additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcConfigListResult[]>>,
show(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<DcConfigShowResult>>
}
}
},
postgres: {
arcserver: {
delete(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<void>>,
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<PostgresServerListResult[]>>,
show(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<PostgresServerShowResult>>,
edit(
name: string,
args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
coordinatorEngineSettings?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workerEngineSettings?: string,
workers?: number
},
namespace?: string,
additionalEnvVars?: AdditionalEnvVars
): Promise<AzOutput<void>>
}
},
sql: {
miarc: {
delete(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<void>>,
list(namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<SqlMiListResult[]>>,
show(name: string, namespace?: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzOutput<SqlMiShowResult>>,
edit(
name: string,
args: {
coresLimit?: string,
coresRequest?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
},
namespace?: string,
additionalEnvVars?: AdditionalEnvVars
): Promise<AzOutput<void>>
}
},
getPath(): Promise<string>,
login(endpointOrNamespace: EndpointOrNamespace, username: string, password: string, additionalEnvVars?: AdditionalEnvVars, azdataContext?: string): Promise<AzdataOutput<void>>,
/**
* The semVersion corresponding to this installation of azdata. version() method should have been run
* The semVersion corresponding to this installation of az. version() method should have been run
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
* Az has gotten reinstalled in the background after this IAzApi object was constructed.
*/
getSemVersion(): Promise<SemVer>,
version(): Promise<AzdataOutput<string>>
version(): Promise<AzOutput<string>>
}
export interface IExtension {
azdata: IAzdataApi;
/**
* returns true if AZDATA CLI EULA has been previously accepted by the user.
*/
isEulaAccepted(): Promise<boolean>;
/**
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
* @param requireUserAction - if the prompt is required to be acted upon by the user. This is typically 'true' when this method is called to address an Error when the EULA needs to be accepted to proceed.
*
* pre-requisite, the calling code has to ensure that the EULA has not yet been previously accepted by the user. The code can use @see isEulaAccepted() call to ascertain this.
* returns true if the user accepted the EULA.
*/
promptForEula(requireUserAction?: boolean): Promise<boolean>;
az: IAzApi;
}
}

View File

@@ -185,10 +185,10 @@ declare module 'azdata-ext' {
},
spec: {
engine: {
extensions: {
extensions?: {
name: string // "citus"
}[],
settings: {
settings?: {
default: { [key: string]: string }, // { "max_connections": "101", "work_mem": "4MB" }
roles: {
coordinator: { [key: string]: string },
@@ -208,7 +208,7 @@ declare module 'azdata-ext' {
limits: SchedulingOptions
}
},
roles: {
roles?: {
coordinator: {
resources: {
requests: SchedulingOptions,

View File

@@ -17,7 +17,8 @@ const externals = {
'utf-8-validate': 'commonjs utf-8-validate',
'keytar': 'commonjs keytar',
'@azure/arm-subscriptions': 'commonjs @azure/arm-subscriptions',
'@azure/arm-resourcegraph': 'commonjs @azure/arm-resourcegraph'
'@azure/arm-resourcegraph': 'commonjs @azure/arm-resourcegraph',
'@azure/storage-blob': 'commonjs @azure/storage-blob'
};
// conditionally add ws if we are going to be running in a node environment

View File

@@ -332,6 +332,7 @@
"dependencies": {
"@azure/arm-resourcegraph": "^4.0.0",
"@azure/arm-subscriptions": "^3.0.0",
"@azure/storage-blob": "^12.6.0",
"axios": "^0.21.1",
"node-fetch": "^2.6.1",
"qs": "^6.9.1",

View File

@@ -18,7 +18,8 @@ const enum SettingIds {
ossrdbms = 'ossrdbms',
vault = 'vault',
ado = 'ado',
ala = 'ala'
ala = 'ala',
storage = 'storage'
}
const publicAzureSettings: ProviderSettings = {
@@ -74,6 +75,12 @@ const publicAzureSettings: ProviderSettings = {
endpoint: 'https://api.loganalytics.io',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.windows.net',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
scopes: [
'openid', 'email', 'profile', 'offline_access',
@@ -128,6 +135,12 @@ const usGovAzureSettings: ProviderSettings = {
endpoint: 'https://api.loganalytics.us',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.usgovcloudapi.net',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
scopes: [
'openid', 'email', 'profile', 'offline_access',
@@ -181,6 +194,12 @@ const usNatAzureSettings: ProviderSettings = {
endpoint: 'https://api.loganalytics.azure.eaglex.ic.gov',
azureResourceId: AzureResource.AzureLogAnalytics,
},
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.eaglex.ic.gov',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
scopes: [
'openid', 'email', 'profile', 'offline_access',
@@ -220,7 +239,18 @@ const germanyAzureSettings: ProviderSettings = {
endpoint: 'https://vault.microsoftazure.de',
azureResourceId: AzureResource.AzureKeyVault
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/'
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.cloudapi.de',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
scopes: [
'openid', 'email', 'profile', 'offline_access',
'https://management.microsoftazure.de/user_impersonation'
],
portalEndpoint: 'https://portal.microsoftazure.de/'
}
}
};
@@ -231,8 +261,13 @@ const chinaAzureSettings: ProviderSettings = {
displayName: localize('chinaCloudDisplayName', "Azure (China)"),
id: 'azure_chinaCloud',
settings: {
host: 'https://login.chinacloudapi.cn/',
host: 'https://login.partner.microsoftonline.cn/',
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
microsoftResource: {
id: SettingIds.marm,
endpoint: 'https://management.core.chinacloudapi.cn/',
azureResourceId: AzureResource.MicrosoftResourceManagement
},
graphResource: {
id: SettingIds.graph,
endpoint: 'https://graph.chinacloudapi.cn',
@@ -245,9 +280,14 @@ const chinaAzureSettings: ProviderSettings = {
},
armResource: {
id: SettingIds.arm,
endpoint: 'https://managemement.chinacloudapi.net',
endpoint: 'https://management.chinacloudapi.cn',
azureResourceId: AzureResource.ResourceManagement
},
sqlResource: {
id: SettingIds.sql,
endpoint: 'https://database.chinacloudapi.cn/',
azureResourceId: AzureResource.Sql
},
azureKeyVaultResource: {
id: SettingIds.vault,
endpoint: 'https://vault.azure.cn',
@@ -258,8 +298,18 @@ const chinaAzureSettings: ProviderSettings = {
endpoint: 'https://api.loganalytics.azure.cn',
azureResourceId: AzureResource.AzureLogAnalytics,
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/'
azureStorageResource: {
id: SettingIds.storage,
endpoint: '',
endpointSuffix: '.core.chinacloudapi.cn',
azureResourceId: AzureResource.AzureStorage
},
redirectUri: 'https://vscode-redirect.azurewebsites.net/',
scopes: [
'openid', 'email', 'profile', 'offline_access',
'https://management.chinacloudapi.cn/user_impersonation'
],
portalEndpoint: 'https://portal.azure.cn/'
}
}
};

View File

@@ -6,6 +6,8 @@
declare module 'azureResource' {
import { TreeDataProvider } from 'vscode';
import { DataProvider, Account, TreeItem } from 'azdata';
import { BlobItem } from '@azure/storage-blob';
export namespace azureResource {
/**
@@ -149,5 +151,7 @@ declare module 'azureResource' {
export interface BlobContainer extends AzureResource { }
export interface FileShare extends AzureResource { }
export interface Blob extends BlobItem { }
}
}

View File

@@ -16,7 +16,7 @@ import { azureResource } from 'azureResource';
export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabaseServer> {
private static readonly containerId = 'azure.resource.providers.AzureMonitorContainer';
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Azure Monitor Workspace");
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Log Analytics workspace");
public constructor(
databaseServerService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
@@ -50,7 +50,10 @@ export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<a
providerName: 'LOGANALYTICS',
saveProfile: false,
options: {},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'LOGANALYTICS',
type: ExtensionNodeType.Server

Some files were not shown because too many files have changed in this diff Show More