Compare commits

..

278 Commits

Author SHA1 Message Date
Karl Burtram
796abbdf5f Update Sql Tools for Feb Hotfix (#14408) 2021-02-23 14:50:51 -08:00
Christopher Suh
995095f696 Bumped turndown version (#14343) (#14348)
* Bumped turndown version (#14343)

* bumped turndown

* fix WYSIWYG test

* remove comment

Co-authored-by: Vasu Bhog <vabhog@microsoft.com>

* update yarn

Co-authored-by: Vasu Bhog <vabhog@microsoft.com>
2021-02-18 18:36:20 -08:00
Maddy
37727b7911 fix for parameterized notebook closure. (#14324) (#14334)
* Two options

* remove commented line and add the null check

* fix tests: pass isDirty explicitly

* remove commented line

Co-authored-by: chlafreniere <hichise@gmail.com>

Co-authored-by: chlafreniere <hichise@gmail.com>
2021-02-17 21:30:24 -08:00
Charles Gagnon
20b24a89e7 [Port] Added Server Group Nodes status table to Overview page (#14194) (#14300)
* Added Server Group Nodes status table to Overview page (#14194)

* Add podstatus to spec

* Added image to table and fixed spacing.

* Added pod status to spec

* PR fixes

* Edited so that when page is closed and reopened, does not have empty server group node table

* Bump azdata version

Co-authored-by: nasc17 <69922333+nasc17@users.noreply.github.com>
2021-02-16 09:55:44 -08:00
Karl Burtram
1bc2057a83 Changes to avoid flickering and ensuring the target DB is always selected correctly even if corresponding source is not present (#14295) (#14298)
Co-authored-by: Udeesha Gautam <46980425+udeeshagautam@users.noreply.github.com>
2021-02-16 08:54:51 -08:00
Charles Gagnon
0423480148 Fix error when listing MIAA databases (#14286) (#14289)
(cherry picked from commit 238a0c60d9)
2021-02-13 20:02:28 -08:00
Chris LaFreniere
3419dd5498 Fix notebook connection dropdown opening multiple connection dialogs (#14283) (#14293)
Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
2021-02-12 21:44:20 -08:00
Barbara Valdez
f212ddc253 Fix search results display (#14265) (#14288)
* fix search results

* fix paths for windows
2021-02-12 17:48:18 -08:00
Charles Gagnon
83ef008306 [Port] Bump min azdata version (#14278)
* Update min azdata version (#14277)


(cherry picked from commit 6eba54d819)

* bump version
2021-02-12 14:57:26 -08:00
Barbara Valdez
166045b3c5 Add await to refresh book method (#14236) (#14268)
* add await to refresh book method

* change name of method

* Reload tree view

* address pr comments

* adding finally on finally
2021-02-11 22:49:04 -08:00
Charles Gagnon
abea315abb Fix hygiene/compile issues (release/1.26) (#14261)
* Fix hygiene issues

* Fix strict compile

* fixes

* compile fix

* more fixes

* more compile fixes

* last one?!

* fix tests
2021-02-11 22:02:55 -08:00
Lucy Zhang
4b87a0869f catch getpythonuserdir error (#14193) (#14263) 2021-02-11 15:29:01 -08:00
Charles Gagnon
b17ae9a24e Fix race condition in account management (#14218) (#14231)
(cherry picked from commit d78d89e326)
2021-02-11 12:24:30 -08:00
Charles Gagnon
75772d6f74 Add connect button to MIAA dashboard (#14183) (#14258)
* Add connect button to MIAA dashboard

* PR comments

(cherry picked from commit da01c75dcf)
2021-02-11 12:23:47 -08:00
Charles Gagnon
22c13aaf7e Search function to be case insensitive (#14196) (#14202)
* Search function does looks at all uppercase

* Add toUpperCase call only in filterParameters function

(cherry picked from commit 7a0ac71b98)

Co-authored-by: nasc17 <69922333+nasc17@users.noreply.github.com>
2021-02-11 10:04:02 -08:00
Charles Gagnon
614a130811 Fix ModelView addItem/withItem ordering (#14205) (#14208)
(cherry picked from commit 6f192f9af5)
2021-02-11 10:03:44 -08:00
Karl Burtram
de83a33075 Bump electron to 9.4.3 (#14217) (#14233)
* Bump electron to 9.4.3 (#14217)

* Add additional electron bump metadata
2021-02-11 09:58:53 -08:00
chgagnon
676f3d3acd vBump azdata extension 2021-02-10 14:10:42 -08:00
Barbara Valdez
06bace1fcf replace backlash when moving notebooks on windows (#14186) 2021-02-07 20:17:30 -08:00
Charles Gagnon
86aa24e198 Fix service dependency startup warning (#14180)
* Fix service dependency startup warning

* fix tests
2021-02-06 15:01:16 -08:00
Maddy
63f9be6b5f Fix/navigating find results on html elements (#14079)
* highlight current range in the marked html

* find ranges on textContent not innerHtml

* add comment

* highlight all and mark the specific in orange

* register highlights in color themes

* fix the closure on the last style

* undo delete locproj file

* add comment

* undo format on locProj file
2021-02-05 20:50:01 -08:00
Barbara Valdez
467a34e878 Fix book editing issues (#14177)
* Fix issue moving a duplicated section to another book
2021-02-05 18:50:29 -08:00
Lucy Zhang
67829af0c5 Notebooks: add multi_connection field to notebook metadata (#14097)
* add multi_connection to nb metadata

* address PR comments

* pr comments

* adsd notebookmetadatakeys object

* pr comments

* use isUUID util method
2021-02-05 13:46:31 -08:00
Charles Gagnon
ac5ff2ec7f Remove error message properties from events (#14175) 2021-02-05 12:28:07 -08:00
Charles Gagnon
ddd80b982c Add display names to cache tasks in build (#14176)
* Add display names to cache tasks in build

* Undo changes to vs files

* Undo
2021-02-05 10:54:16 -08:00
Barbara Valdez
3c4ffd2a9c Fix book tests on Windows. (#14173)
* fix tests

* fix reveal active item test

* show error message

* revert trusted book test back to original
2021-02-05 10:45:59 -08:00
Charles Gagnon
49fa56369c Fixes for HDFS node expansion on BDC connections (#14174)
* Change cluster test connect to use endpoints route

* Add more logging

* More logging

* Only connect to controller if needed

* Add comments
2021-02-05 10:12:15 -08:00
Aasim Khan
1944813c4a Adding help text for resourceTypes (#14166)
* Adding help text to arc resource deployment.

* Fixing null help text and agreement logic

* Made some types optional
Fixed a language specific link
2021-02-04 23:45:29 -08:00
Aasim Khan
71d9c91551 Removing other changes (#14163) 2021-02-04 18:47:59 -08:00
Charles Gagnon
350034cbb1 Fix arc dashboard component order error (#14164) 2021-02-04 15:53:29 -08:00
Charles Gagnon
c027ce4f00 Update ads-extensions-telemetry package (#14162) 2021-02-04 14:57:54 -08:00
nasc17
e99b4a7bed Making the Postgres Properties Page Visible (#14161)
* Make properties page visible

* Set max length to text box
2021-02-04 12:39:51 -08:00
nasc17
814dfba338 Making the Postgres Parameters Page Visible (#14156)
* Having parameters page be visible and fixing information bubbles

* Added sessions to edit commands

* Fix table style
2021-02-04 09:52:39 -08:00
Charles Gagnon
6907c8edab Use cryptography 3.2.1 for sparkmagic dependency (#14154)
* Use cryptography 3.2.1 for sparkmagic dependency

* pip -> conda

* fix typing

* fix tests

* prefix with required
2021-02-04 09:52:08 -08:00
Barbara Valdez
cce8a961ac Fix decorations for modified notebooks in Books View (#14152)
* Tree item resource uri should be the root when it's a book and notebook path for notebooks
2021-02-03 18:17:08 -08:00
Charles Gagnon
3a89731036 Add version to azdata api (#14155) 2021-02-03 17:56:01 -08:00
Kim Santiago
bcc73dfbcf fix intermittent sql project test failure (#14148) 2021-02-03 13:26:06 -08:00
Charles Gagnon
395dfd6c52 Add setFilter to DeclarativeTableComponent (#14143)
* Add setFilter to DeclarativeTableComponent

* fix tests

* Update param name
2021-02-03 12:18:36 -08:00
Charles Gagnon
dcf17cc08b Remove async keyword from abstract functions (#14150) 2021-02-03 12:16:50 -08:00
Alan Ren
52a642f351 apply css style at the right element (#14144)
* apply css style at the right element

* make mergeCss protected
2021-02-03 10:27:12 -08:00
Aasim Khan
b390052c86 Exposing Azure HTTP requests method in Azurecore extension for external consumption (#14135)
* Exposing azure HTTP request in azurecore
Moving migration specific request from azurecore to migration
Created a migrations typing file

* Deleting the typings file in sql migration

* Fixed typos in comments

* Adding default host for azure https requests

* Fixed operator in service url modification

* Changed path and host logic

* Made chagned requested in the PR

* Fixed variable names

* Adding / check for path.

* Changing error code check

* Fixed status logic in azure rest request
Fixed comment logic
Converting error array to string while throwing

* Fixed status check

* Fixed typos and cleaning up
2021-02-03 09:31:59 -08:00
Barbara Valdez
9ac180d772 Editing Books (#13535)
* start work on ui

* Move notebooks complete

* Simplify version handling

* fix issues with add section method

* fix issues with the edit experience and add the quick pick for editing

* add version handler for re-writing tocs

* fix book toc manager tests

* add notebook test

* fix localize constant

* normalize paths on windows

* check that a section has sections before attempting to mve files

* Implement method for renaming duplicated files

* moving last notebook from section converts the section into notebook

* Add recovery method restore original state of file system

* Add clean up, for files that are copied instead of renamed

* remove dir complexity

* divide edit book in methods for easier testing and remove promise chain

* Keep structure of toc

* normalize paths on windows

* fix external link

* Add other fields for versions

* fix paths in uri of findSection

* add section to section test

* Add error messages

* address pr comments and add tests

* check that the new path of a notebook is different from the original path before deleting
2021-02-02 20:39:11 -08:00
Kim Santiago
41915bda8d update db dropdown in create project from database dialog to not be editable (#14142) 2021-02-02 17:31:09 -08:00
Charles Gagnon
86a5cb27b7 Add debounce to pg parameters search filter (#14140)
* Add debounce to pg parameters search filter

* Update tsconfig
2021-02-02 16:15:31 -08:00
Benjin Dubishar
661e7c361d Adding schema compare UX support for external streams/streaming jobs (#14134) 2021-02-01 21:57:46 -08:00
Cory Rivera
a0656d6f8d Add unit tests for PythonPathLookup class. (#14133) 2021-02-01 16:37:17 -08:00
Leila Lali
9b43ee6287 version update for ml 0.7.0 (#14130) 2021-02-01 16:12:06 -08:00
Chris LaFreniere
0e84dd240b Notebook Cell Conversion Language Fixes (#14120)
* Cell language override fixes

* PR feedback
2021-02-01 14:21:24 -08:00
Charles Gagnon
2f49513bae Rename name field of resourceSubType to resourceName (#14103)
* Rename name field of resourceSubType to resourceName

* update comment

* bump arc version
2021-02-01 13:04:05 -08:00
Justin M
8f5dc1526a Initial commit for dSTS Auth (#13802)
* Initial commit for dSTS Auth

* Removed getDstsToken from accountManagementService. Renamed azureAccount to token.

* Renamed dstsToken to _token in connectionWidget

* Code Review Feedback. Renamed token to authToken in onFetchDatabases.

* Removed dsts options from Kusto package.json
2021-02-01 10:48:16 -08:00
Benjin Dubishar
1c0259f4c5 Telemetry points for SQL Database Projects extension (#14088)
* Added publish and schema compare telemetry
* Adding telemetry points for add/exclude/delete
* telemetry for validation
* Adding telemetry from project roundtrip updates, editing sqlproj, and db refs
* Changed method for obtaining extension ID during registration
* Fixing test failures, updating ads telemetry package dependency
* replacing action strings with enums
* change database project string actions to enums
* Changed action name to better match dialog
* PR feedback
2021-01-31 16:38:48 -08:00
Alan Ren
15fa03876d revert the changes (#14119) 2021-01-29 16:42:29 -08:00
Charles Gagnon
f6e39a7211 Fix other fields being editable in password reprompt for Arc (#14117) 2021-01-29 16:27:20 -08:00
Lucy Zhang
667c12abdc Notebooks: Fix Notebook validation Error when opened in Jupyter (#14110)
* delete transient node for display data

* add comment
2021-01-29 14:40:02 -08:00
Charles Gagnon
72b8d96dbc Bump ads-extension-telemetry (#14115)
* Bump ads-extension-telemetry

* Fix import
2021-01-29 14:11:44 -08:00
nasc17
d98ac86bd9 Postgres Parameters Page (#13855)
* Addition: properties page with link to dashboard

* Include new page

* Initial Parameter page start

* Include new changes from merged PRs

* Including new constants

* Git errors

* Add parameter commands and help

* Reset command

* Added chart

* git fix

* Fixed string issues

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Initial start to adding connect to sql for postgres instance

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* Messy dialog showing from button

* removed this._completionPromise.reject

* Cleaning up code

* Set connectSqlDialog to be an abstract class. Separated out Miaa and Postgres.  (#13532)

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* removed this._completionPromise.reject

* Connect button clean up

* Format

* Format doc

* Fixed compile errors

* Cleaning up page

* Clean up

* clean up refresh

* Format doc

* Removed ellipse

* Cleaning up problems

* Updating localized constants

* Missing username update

* Get connection profile added to Resource model, abstract method created for calling connection dialog

* Added createConnectionProfile

* took out import

* Pulled in new changes, fixed usercancellederror

* Getting engine settings

* Git errors

* Git errors

* Git errors fix

* Fixing Css

* Freezes, Search function working, 20 parameters

* Fixed re

* Git errors

* Save and reset commands working

* Discard works, updated how engine settings refresh with model

* Updated search, add back loading for when trying to connect

* Cleaning up comments left in code

* Git error

* Corrected names of icons and constants, Fixed Miaa dialog title

* Removed using any on page, added void return types, took out commented code

* Changed gear svg, made postgres extension a loc constant, fixed formatting

* Fixed controller model name

* Put connection profile and id in resource model, changed back controller model in base class

* Fixed a comment

* Added loading component for waiting for postgres extension to be installed

* Fix parameters page to show parameters if engine settings are already loaded (#13996)

* Added progress message for installing postgres extension

* Minor styling updates

* Making sure search box and rest buttons are enabled when opening page with loaded data. Update refresh

* Git errors

* change name

* Code review updates: Combined create parameters and refresh table.

* Update sql-assessment to use latest ads-extension-telemetry npm package (#14003)

* Change configure Jupyter server steps from async to sync (#13937)

* change config steps to sync

* fix tests

* use pathexistsSync

* remove pathExistsSync call

* address PR comments

* Use a minimum cell height to prevent whitespace markdown cells from becoming invisible. (#14008)

* Fix validation errors (#14009)

* Fix validation errors

* fix compile

* update return type

* Cleanup model component wrapper event handlers (#14012)

* Clean up button component disposables (#14011)

* Clean up button component disposables

* consolidate logic

* Adding Dacpac extension telemetry and core wizard/page telemetry updates(#13859)

* Dacpac telmetry code changes

* Removed added spaces

* Generate deployScript accessibility changed back to public

* code review suggessions updates

* dacpac extension tests fixes

* Updated time and filesize methods allowing general return values

* Telemetry code updates

* Dacpac Telemetry potential data loss capture and PII error excluded

* Dacpac telemetry code updates for comments

* Wizard pages navigation telemetry event capture moved to the core

* DacpacTelemetry code updates

* Extension wizard cancel telemetry for data loss

* Dacpac telemetry pagename and small code updates

* final Dacpac telemetry code updates...

* migrated loc files (#14015)

* Took out some info bubbles and addingitems

* Update search function

* Handle special value occasions

* Undo change

* Undo change

Co-authored-by: chgagnon <chgagnon@microsoft.com>
Co-authored-by: Lucy Zhang <luczhan@microsoft.com>
Co-authored-by: Cory Rivera <corivera@microsoft.com>
Co-authored-by: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com>
Co-authored-by: khoiph1 <khoiph@microsoft.com>
2021-01-29 13:48:35 -08:00
Charles Gagnon
927e435aea Fix deployment wizard for filtered options (#14114) 2021-01-29 13:20:19 -08:00
Aasim Khan
90b33b256d Fixing the null check for agreements (#14111) 2021-01-29 11:34:16 -08:00
Charles Gagnon
dd3fe7b613 Fix error when rendering empty declarative table (#14102) 2021-01-29 11:08:43 -08:00
Alex Ma
09d5a8b88b removal of ADP on machine (#14100) 2021-01-28 18:25:19 -08:00
Charles Gagnon
db845754c6 Add resource deployment filtering by option values (#14101)
* Add resource deployment filtering by option values

* Fix compile error
2021-01-28 17:19:08 -08:00
Hale Rankin
5bf1f640e8 Notebook - text cell - minimum height fix (#14080)
* Adding minimum height to text cell element so whether empty or occupied with a single line of content, the container is the same height.

* Added styles to remove top padding/margin from first child element and set top-padding of container.

* Switch top padding to  px value

* Removed line-height and min-height as these are no longer necessary to maintain text-cell height.
2021-01-28 15:52:40 -08:00
Alan Ren
23999951d0 vbump server-report extension (#14099)
* vbump

* fix url
2021-01-28 15:50:47 -08:00
Christopher C
ac8f950147 breadcrumbs for sql assessment extension (#14098) 2021-01-28 15:33:15 -08:00
Charles Gagnon
feb92b96da Add MIAA icon back in (#14096) 2021-01-28 14:08:21 -08:00
Charles Gagnon
c3a00c2cc6 Clean up telemetry keys (#14093) 2021-01-28 13:33:12 -08:00
Alan Ren
c16aee760f move server report ext to extensions (#14087)
* move server report ext to extensions

* limit the size of data

* comments
2021-01-28 13:23:30 -08:00
Kim Santiago
f9ea6430ee fix projects view title commands (#14086) 2021-01-28 10:15:02 -08:00
Charles Gagnon
ff6f30505c Skip adding empty list of items (#14084) 2021-01-28 09:08:40 -08:00
Charles Gagnon
8677ffc68c Fix deployment wizard to not close when cancelling out of password prompt (#14083) 2021-01-28 09:00:46 -08:00
Aasim Khan
14cf6add73 Added SQL MI tile and sub resource types to resource deployment (#14043)
* Made azure arc as resourcesubtype
Added new azure arc resource type
Added support for different eula statement for different subtypes
Consolidated getSelectedOption

* Fixed some PR based comments

* Fixed more pr comments

* Fixed the error in unit test by deep copying extension resourceTypes (to keep the original one intact)

* Fixed property name 'agreement' to 'agreements'

* Cloning subresourceTypes
2021-01-27 22:06:53 -08:00
Kim Santiago
8651db1e7e Show notification if there are projects that haven't been added to the workspace (#14046)
* add check for if there are projects that haven't been added to the workspace

* add check when workspace folders change

* add comment

* update wording

* filter for multiple file extensions at the same time

* add test

* addressing comments
2021-01-27 16:06:33 -08:00
Barbara Valdez
fa150fbe67 Initialize releases when creating remoteBookController (#14064)
* Check that releases if not undefined

* Initialize model with empty releases
2021-01-27 15:17:32 -08:00
Alan Ren
db8d06d628 update the email address (#14076) 2021-01-27 14:44:55 -08:00
Charles Gagnon
79cb27898d Fix issue reporter URLs for extensions (#14077)
* Fix issue reporter URLs for extensions

* Also clean up reporter URL

* sql carbon edit
2021-01-27 14:31:31 -08:00
Charles Gagnon
45bdf2ed95 Fix tabbed panel error (#14074) 2021-01-27 13:07:09 -08:00
Chris LaFreniere
26bb3ca033 WYSIWYG Improvements to Nested Lists (#14052)
* indent/outdent working properly

* Add tests

* Latest fixes and tests

* PR comments + comments
2021-01-27 12:57:23 -08:00
Charles Gagnon
b3891ff68b Clear nuget sources (#14071) 2021-01-27 10:17:15 -08:00
Aasim Khan
028568971f Fixed the migration controller typos in typing (#14068) 2021-01-27 03:32:37 -08:00
Charles Gagnon
a04c2f3ba6 Fix resource viewer not showing on insiders builds (#14063) 2021-01-26 18:02:36 -08:00
Charles Gagnon
e79a80590a Add multiple ModelView child items at once (#14047) 2021-01-26 15:33:45 -08:00
Charles Gagnon
d828a1f042 Fix stringify error from resource deployment extension (#14059)
* Fix stringify error from resource deployment extension

* add comment
2021-01-26 12:00:05 -08:00
Charles Gagnon
1a84db089a Add more azdata tests (#14049)
* Add more azdata tests

* cleanup
2021-01-26 10:52:35 -08:00
Aasim Khan
300bce4070 Adding a temporary target selection page for sql migration (#14053)
* Adding a temporary target selection page

* Fixed the wizard page title
2021-01-26 10:50:02 -08:00
Alex Ma
db0464d07c Failover Cluster Notebook added (#14004)
* wip failover notebook

* notebook instructions changed

* added failover cluster notebook to toc and readme

* Failover Cluster cell added

* Added VMs to the cluster cell

* More changes made

* Offers added

* SqlVm Names added

* added secure passwords for account passwords

* wip parameters added for configure failover notebook

* updated storage account creation

* added offer and fixed resource group names

* added missing ` and boolean value

* added prerequisites for configure-failover notebook

* removed unnecessary ip address message

* Added secure string for storage account key

* changed name of storage account key variable.

* Changed resource group name parameter

* removed space

* changed sql version to match tutorial

* Added message about image SKU

* Changes made to failover cluster notebook (Working)

* added wait warning

* failover cluster notebook added with variable table

* readme is now divided into two sections

* simplified HADR
2021-01-26 09:42:44 -08:00
Christopher C
52fbdca2ed Cavonac/offmig (#13865)
* offline migration changes

updates to offline migration notebooks, toc, glossary, creating a new notebook

* variables table

* Update offline-mig readme

wording

* Update readme.md

* content

* overview and titles

for consistency, i updated the overview and titles

* Update readme.md

clarity and content

* removed create sql backups

* added term

* updating notebook

* adding code

* wording updates

* wording updates

* removed image no longer used

* avoid conflict with another PR
replaced with a notebook
2021-01-25 17:31:43 -08:00
Christopher C
728aeded01 updates to variables and subscription (#13695)
* updates to variablesand subscription

previous notebook was not taking Subscription into account. Here, I'm setting to use the az context explicitly with the $Subscription variable

* Converted appendix to nb, wording

* remove whitespace, add clarity
2021-01-25 17:31:32 -08:00
Aasim Khan
42a8680738 Adding Integration Runtime Page to Migration extension wizard (#14020)
* - Fixed GetMigrationController
- Added createMigrationController and getControllerAuthKeys API in azure core.
- Added typings for Migration Controller
- Fixed database backup page validation logic
- Added IR page with create controller

* Fixing all the comments from the PR

* Fixed typings
2021-01-25 14:46:39 -08:00
Aasim Khan
ed26938dc8 Adding cloud tag to BDC resource (#14040) 2021-01-25 10:07:59 -08:00
Maddy
5be2e01632 Notebook Telemetry initial changes (#13929)
* initial changes

* add ads-telemetry to nb extension

* add aiKey to notebook extension

* add run cell telemetry in cellmodel

* add NbTelemetryAction

* remove source

* remove telemtery for run notebook for now

* fix optional variable call

* addr comments

* changing the dependency package

* revert

* removed webpack config for telemetry compat

* add opentelemetry to externals

Co-authored-by: Benjin Dubishar <benjin.dubishar@gmail.com>
2021-01-25 09:42:37 -08:00
Alan Ren
d059032dee new component - infobox (#14027)
* new component: infobox

* comments

* new option

* add comments
2021-01-22 18:38:10 -08:00
Charles Gagnon
dd0fa50d32 Fix hang when expanding arc tree node without azdata (#14033) 2021-01-22 17:54:20 -08:00
Charles Gagnon
d4f2214055 Add lcl files to hygiene indent ignore list (#14035)
* Add lcl files to hygiene indent ignore list

* test

* Revert "test"

This reverts commit 4d22a3e265a2ba54591ee9b43a142892ee082e66.
2021-01-22 17:54:13 -08:00
Charles Gagnon
82b363fe90 Update ads-extension-telemetry package to official Microsoft org version (#14032)
* Update ads-extension-telemetry package to official Microsoft org version

* Update names
2021-01-22 16:40:59 -08:00
csigs
c5c1f183c3 LEGO: check in for main to temporary branch. (#14030) 2021-01-22 15:11:06 -08:00
Benjin Dubishar
c903cd87bf Telemetry for Data Workspaces extension (#13846)
* Add CodeQL Analysis workflow (#10195)

* Add CodeQL Analysis workflow

* Fix path

* test commit pls ignore

* telemetry points

* yarn lock changes

* making test xplat friendly

* PR feedback

* Adding additional telemetry points

Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
2021-01-22 14:18:43 -08:00
Aasim Khan
e280205340 Added changelog for 1.25.2 (#14023) 2021-01-22 09:35:59 -08:00
khoiph1
0b5b8c9261 migrated loc files (#14015) 2021-01-21 15:20:54 -08:00
Sai Avishkar Sreerama
0316d9ac57 Adding Dacpac extension telemetry and core wizard/page telemetry updates(#13859)
* Dacpac telmetry code changes

* Removed added spaces

* Generate deployScript accessibility changed back to public

* code review suggessions updates

* dacpac extension tests fixes

* Updated time and filesize methods allowing general return values

* Telemetry code updates

* Dacpac Telemetry potential data loss capture and PII error excluded

* Dacpac telemetry code updates for comments

* Wizard pages navigation telemetry event capture moved to the core

* DacpacTelemetry code updates

* Extension wizard cancel telemetry for data loss

* Dacpac telemetry pagename and small code updates

* final Dacpac telemetry code updates...
2021-01-21 17:00:37 -06:00
Charles Gagnon
07d798c949 Clean up button component disposables (#14011)
* Clean up button component disposables

* consolidate logic
2021-01-21 12:08:16 -08:00
Charles Gagnon
a96caf82c3 Cleanup model component wrapper event handlers (#14012) 2021-01-21 12:07:57 -08:00
Charles Gagnon
f986b8cf78 Fix validation errors (#14009)
* Fix validation errors

* fix compile

* update return type
2021-01-21 10:40:46 -08:00
Cory Rivera
5f61becd36 Use a minimum cell height to prevent whitespace markdown cells from becoming invisible. (#14008) 2021-01-20 16:20:50 -08:00
Lucy Zhang
068649cba4 Change configure Jupyter server steps from async to sync (#13937)
* change config steps to sync

* fix tests

* use pathexistsSync

* remove pathExistsSync call

* address PR comments
2021-01-20 15:45:54 -08:00
Charles Gagnon
58d4dda1e8 Update sql-assessment to use latest ads-extension-telemetry npm package (#14003) 2021-01-20 15:22:11 -08:00
Charles Gagnon
3cff682ae4 Remove unnecessary addItem from DeclarativeTable component (#13997) 2021-01-20 12:35:09 -08:00
Charles Gagnon
d27b376964 Update CODEOWNERS (#14002) 2021-01-20 11:36:42 -08:00
Charles Gagnon
1108560780 Clean up a few proposed azdata things (#14000) 2021-01-20 11:32:29 -08:00
Charles Gagnon
04dff9cdf2 Add acquireLoginSession API for Azdata (#13985)
* wip

* fix tests

* add tests

* PR comments
2021-01-20 10:35:26 -08:00
Cory Rivera
9e9fac2991 Skip checking if python is running when only doing package upgrades during python install. (#13995) 2021-01-19 20:01:59 -08:00
Aasim Khan
7d4fa0aa9b Adding get Migration Controller API to azure core. (#13991)
* - Adding null checks for onValueChanged
- Adding new event for radiobuttons onDIdChangeCheckedState

* Adding get MigrationController API to azure core.

* string templating
fixed typings

* Fixed typing and sorting logic

* Made the undefined check logic concise.
2021-01-19 17:18:12 -08:00
Leila Lali
a3eb9d29a9 Fixed the bug with refreshing the page over and over (#13966) 2021-01-19 14:21:12 -08:00
Aasim Khan
b089d880fc Fixing radioButton checked status (#13974)
* Fixing radioButton checked status

* Fixed all kinds of event bugs in radio buttons

* removing uneeded checks

* Fixing the logic for radiobutton onChange event generation for all possible scenarios.

* Made small changes in radioButton logic.
2021-01-19 13:30:45 -08:00
Charles Gagnon
bd8346a1b2 vBump azdata version (#13994) 2021-01-19 12:47:33 -08:00
Cory Rivera
94b697340d Remove unnecessary OutputChannel reference from PythonPathLookup class. (#13970) 2021-01-15 16:51:42 -08:00
Aasim Khan
462d4137d5 vbump import for future release (#13972) 2021-01-15 14:09:20 -08:00
Drew Skwiers-Koballa
a394a2146a increment version for fixes to tempdb xevents (#13979) 2021-01-15 14:02:13 -08:00
Aasim Khan
2161f323c3 Fixing errors in existing migration pages (#13977)
* - Adding null checks for onValueChanged
- Adding new event for radiobuttons onDIdChangeCheckedState

* changing the null checks in checkbox
2021-01-15 12:58:05 -08:00
Charles Gagnon
5810623e55 Don't capture query history events until service is started (#13983) 2021-01-15 12:54:29 -08:00
nasc17
7a0a56df2f Updating Arc resources link to match new provider. (#13963)
* Fixed support request link

* Updated other pages that opens to portal
2021-01-15 12:05:35 -08:00
Christopher C
e6bfbe7fa7 Cavonac/sqlassessment (#13900)
* Update sql-server-assessment.ipynb

code to query the assessment results
removed the registered servers to avoid confusion

* update link
2021-01-15 12:03:48 -08:00
Lucy Zhang
8671b6aa5d Fix redo keyboard shortcut in notebook markdown (#13967)
* add cmd+y for redo

* add ctrl+shift+z for redo

* remove listener for redo when in markdown mode
2021-01-15 08:24:06 -08:00
Alan Ren
09cce42233 fix tree component and update sqlservices (#13968) 2021-01-14 16:26:30 -08:00
Kim Santiago
9e3bfea922 fix flaky data workspace test (#13965) 2021-01-14 15:45:22 -08:00
Charles Gagnon
c9fcf4434a Always publish test results (#13962) 2021-01-14 12:44:39 -08:00
Charles Gagnon
591a0ca9ee Update azdata tool requirements to 20.2.6 (#13964) 2021-01-14 12:44:34 -08:00
Hale Rankin
a44b196a6f Revised section scrolling logic to fix broken user experience. (#13926) 2021-01-14 12:08:55 -08:00
Charles Gagnon
431da155a7 Update SqlToolsService to .68 (SqlClient 2.1.1) (#13943) 2021-01-14 12:04:00 -08:00
Alan Ren
b6c80557ba handle azure account load error (#13940)
* handle azure account load error

* comments
2021-01-14 10:40:31 -08:00
Alan Ren
c39c20cd4b fix focus trap issue (#13960) 2021-01-14 10:35:36 -08:00
Charles Gagnon
dd9d247950 Disable dacpac integration tests (#13961) 2021-01-14 09:17:49 -08:00
Charles Gagnon
452b7d744e Fix account store silently failing (#13956)
* Fix account store silently failing

* Combine calls

* undo
2021-01-13 16:06:35 -08:00
Charles Gagnon
8ef9b1cc1f Re-enable some unstable tests (#13944)
* Re-enable some unstable tests

* Remove some tests that have failed recently
2021-01-13 15:36:58 -08:00
Alan Ren
1d42431914 add confirm prompt for some profiler actions (#13952) 2021-01-13 15:02:05 -08:00
Lucy Zhang
1ec42a082e click first element in quick input result list (#13954) 2021-01-13 10:51:22 -08:00
Aasim Khan
bcae0d2b02 Changing how connection is stored in Migration Wizard and some misc code cleanup (#13951)
* Disabling pages that are not needed
Some source connection code cleanup

* Enabled some pages
2021-01-12 16:17:14 -08:00
Charles Gagnon
140a1fbc3e Only archive some folders (#13945)
* Only archive some folders

* remove extra folder
2021-01-12 11:06:03 -08:00
Benjin Dubishar
b69572a7c9 Preserve error messages if workspace validation fails in tests (#13901)
* preserving error messages if test fails

* change name of var to due scope change
2021-01-11 17:50:34 -08:00
Charles Gagnon
f37fb0b1d5 Fix spacing for log message (#13942) 2021-01-11 15:19:02 -08:00
Charles Gagnon
8a5b737fda Update log gathering instructions (#13939) 2021-01-11 15:11:11 -08:00
Lucy Zhang
569be8eb24 Add setting to customize notebook markdown preview line height (#13923)
* add md preview line height settings

* remove if condition

* listen for configuration change

* change setting description

* only change line height outside of edit mode

* set minimum line height to 1

* combine ondidchangeconfiguration callbacks
2021-01-08 14:45:26 -08:00
Vasu Bhog
df51f35be0 Enable Parameterization via Notebook URI (#13869)
* Enable Parameterization via Notebook URI

* Add Parameterization and Move notebookModel tests

* minor typos

* parameter typo

* Multiple parameters through uri fix

* Address PR comments and tests fix

* add new injected parameters after original injected parameters

* Add new test to verify notebookURi parameter is injected after both parameter and injected cell

* fix tests
2021-01-08 00:38:33 -08:00
Aasim Khan
5d1b328866 Fixing the checked variable in radio buttons (#13909)
* Fixing the checked variable in radio buttons

* Emitting the checked state of radio button.

* Adding onChanged event to radioButtons (exposing it)
Deprecating onClick event for radioButtons
Fixing radio button stubs

* Made some type fixes

* Firing event in checked event setter

* updating azdata-test to 1.1 in arc extension

* Some logic fixes in checked setter

* added proper typings and updated package version for azdata-data

* Renamed the event to onDidChangeCheckedState

* Fixed deprecation message

* Fixed broken Schema compare stubs
2021-01-07 23:25:21 -08:00
nasc17
e7fb44b3a2 Connect to SQL from Postgres Parameters Page (#13744)
* Addition: properties page with link to dashboard

* Include new page

* Initial Parameter page start

* Include new changes from merged PRs

* Including new constants

* Git errors

* Add parameter commands and help

* Reset command

* Added chart

* git fix

* Fixed string issues

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Initial start to adding connect to sql for postgres instance

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* Messy dialog showing from button

* removed this._completionPromise.reject

* Cleaning up code

* Set connectSqlDialog to be an abstract class. Separated out Miaa and Postgres.  (#13532)

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* removed this._completionPromise.reject

* Connect button clean up

* Format

* Format doc

* Fixed compile errors

* Cleaning up page

* Clean up

* clean up refresh

* Format doc

* Removed ellipse

* Cleaning up problems

* Updating localized constants

* Missing username update

* Get connection profile added to Resource model, abstract method created for calling connection dialog

* Added createConnectionProfile

* took out import

* Pulled in new changes, fixed usercancellederror

* Getting engine settings

* Git errors

* Corrected names of icons and constants, Fixed Miaa dialog title

* Changed gear svg, made postgres extension a loc constant, fixed formatting

* Fixed controller model name

* Put connection profile and id in resource model, changed back controller model in base class
2021-01-07 19:42:48 -08:00
Alan Ren
6c2e713a92 toggle focus between query and results (#13928)
* toggle focus between query and results

* focus on tab

* comments
2021-01-07 18:39:31 -08:00
Sakshi Sharma
81a1b1a55a Sakshis/scmp test (#13904)
* Fixed a few await issues

* Introduced ModelView as a class member in schemaCompareMainWindow

* Added a few button tests

* Fixed tests

* Add a missing await
2021-01-07 10:15:09 -08:00
Charles Gagnon
72be8045ec Update arc strings (#13924) 2021-01-06 17:02:15 -08:00
dependabot[bot]
f75665d488 Bump axios from 0.19.2 to 0.21.1 in /extensions/azurecore (#13922)
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 16:38:30 -08:00
Monica Gupta
4af333d362 Fix for query select command text in manage tab for Kusto tables (#13916)
* Fix for text in manage tab for Kusto tables

* addressed comment

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2021-01-06 12:59:18 -08:00
dependabot[bot]
c8d17f1ee3 Bump axios from 0.19.2 to 0.21.1 in /build/actions (#13918)
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 10:39:56 -08:00
Lucy Zhang
fae0600a46 Notebooks: add behavior checks to untrusted notebook smoke test (#13885)
* check untrusted notebook behavior

* remove skips

* check for elements after trusted nb is reopened

* move trusted element checks to notebook method

* make selector more specific
2021-01-06 07:16:35 -08:00
Daniel Grajeda
16943c68c6 Notebook Views Models (#13884)
* Add notebook editor

Introduce notebook editor component to allow for separate notebook displays in order to accomodate notebook views

* Localize notebook views configuration title

* Refactor view mode and remove the views configuration while it is unused

* Only fire view mode changed event when the value has been changed

* Remove notebook views contribution

* Add metadata capabilities

* Notebook views definitions

* Add notebook views models

* Views test

* Rename type arguments

* Additional tests

* Fix unused import

* Update resize cell test
2021-01-05 15:28:30 -08:00
Alan Ren
ab6d11596e remove update style (#13886)
* remove update style

* sample composite button
2021-01-05 13:28:31 -08:00
Christopher Suh
bc9719d5a8 Fix parentheses for firewall ip address (#13903)
* fix parentheses for firewall ip address

* use template string
2021-01-04 17:02:06 -05:00
Monica Gupta
67eebf7d14 Notification after copying query and results (#13902)
Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2021-01-04 11:27:06 -08:00
Monica Gupta
558d70b427 Fix Query formatting in copied html formatted query (#13633)
* Fix Query formatting in html formatted query

* Changed font sizes to match Kusto Explorer

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2021-01-04 11:18:52 -08:00
Vladimir Chernov
c329a2524b sql-asmt dep on azdata 1.26 (#13863) 2021-01-04 20:31:07 +03:00
Sakshi Sharma
30054e632c Add logging for unstable data-workspace extension unit test (#13883)
* Skip unstable test

* Unskip test and add logging
2020-12-29 14:06:36 -08:00
Sakshi Sharma
1d09dbf591 Cleanup unreferenced strings (#13882) 2020-12-28 13:05:19 -08:00
Daniel Grajeda
11f236ade1 Notebook Views (#13465)
* Add notebook editor

Introduce notebook editor component to allow for separate notebook displays in order to accomodate notebook views

* Localize notebook views configuration title

* Refactor view mode and remove the views configuration while it is unused

* Only fire view mode changed event when the value has been changed

* Remove notebook views contribution
2020-12-27 13:08:27 -08:00
Arvind Ranasaria
f314a9e1e6 Change azdata executions to point to kube config and cluster contexts (#13569) 2020-12-23 23:45:00 -08:00
Arvind Ranasaria
cf7c506d75 populate drop down options values from options source (#13870) 2020-12-23 19:13:27 -08:00
Alan Ren
c90933eb1b archive logs step (#13860)
* one zip per folder

* use bash

* fix error
2020-12-23 16:22:19 -08:00
Kim Santiago
bc1fc8527c fix editing workspace inputbox not working (#13858) 2020-12-21 11:29:22 -08:00
Lucy Zhang
57446faa1e Notebooks: Improve Jupyter server start time (#13838)
* let jupyter server find port

* add undefined to return type

* remove time log

* add test for port number

* check that match[1] exists
2020-12-21 08:03:26 -08:00
Drew Skwiers-Koballa
786d526e9c adds close workspace to data workspace view menu (#13809)
* adds close workspace to data workspace view menu

* moving vscode command out to main

* update context menu groups
2020-12-20 13:14:26 -08:00
Kim Santiago
c7303803ba update new workspace validation to throw errors to make code more reusable (#13856) 2020-12-18 13:32:42 -08:00
Sakshi Sharma
0b8b6064ed Add an informational message when a user tries to open an already opened project (#13853) 2020-12-18 12:33:33 -08:00
Alex Ma
72a3aa1207 Removal of Components folder, moving ADP notebook to its own notebook. (#13848)
* adp folder removed, notebook moved.

* moved ADP notebook to own folder.
2020-12-18 10:58:16 -08:00
Kim Santiago
59ad601664 Make new workspace inputbox editable in Create project from database dialog (#13842)
* update create project from database dialog to have editable new workspace

* add validation

* add test

* add error message

* Remove test for now

* cleanup

* add periods

* throw errors

* change return type to void
2020-12-18 10:24:38 -08:00
Aasim Khan
0b00533f99 Added fix for the infinite page refresh in resource Deployment tools page (#13813)
* Added fixed for the infinite loop in resource Deployment tools page
Generating events for select boxes only when the select box value is changed.

* Fixed the check logic in select method

* Reverted to old code and fixed some bugs

* Fixed event generation check logic
2020-12-18 10:06:12 -08:00
Chris LaFreniere
2d6074706a Have same connection logic for all nb int tests (#13844) 2020-12-17 15:38:41 -08:00
Chris LaFreniere
9aaa78e020 Stop forcing left text align on tables (#13840) 2020-12-17 13:15:40 -08:00
Hale Rankin
385800553c Removed padding-top / bottom declarations for text cell notebook-preview. This compacts the text cell by 14px on top and bottom. (#13815) 2020-12-17 12:33:22 -08:00
Alan Ren
d8f8abb644 comment out the unstable unit test step (#13834)
* remove --build flag

* comment out unstable test

* add build tag back
2020-12-17 10:44:54 -08:00
Kim Santiago
e3e1ae92b2 Fix unstable data-workspace tests (#13824)
* stub file existing validation

* add error message

* change back to calling dialog.validate()

* move tests to separate dialogbase file and add more error message validation
2020-12-17 10:28:42 -08:00
Leila Lali
f88e17a294 Dacpac - Showing error message to user if operation fails (#13830)
* Showing error message to user if operation fails.

* Added more tests
2020-12-17 10:27:36 -08:00
Charles Gagnon
60f556428f Fix select box event ordering (#13831)
* Fix select box event ordering

* more fixes

* Fix page

* Revert typing change

* Undo param

* Fix compile error

* Completely remove typings
2020-12-17 09:51:10 -08:00
Chris LaFreniere
e9964a4dfd Fix duplicate SVG rendering (#13828) 2020-12-16 18:25:05 -08:00
Charles Gagnon
598aaed500 Filter vscode delegate command events (#13832) 2020-12-16 16:58:21 -08:00
Charles Gagnon
7f38e87ef3 Use azdata-test modelview stubs (#13818) 2020-12-16 16:28:02 -08:00
Chris LaFreniere
7b06194199 Remove hardcoded search box height (#13823) 2020-12-16 15:08:06 -08:00
Lucy Zhang
60925aa3a9 add . as trigger character (#13811) 2020-12-16 13:36:58 -08:00
Vladimir Chernov
2abc11a1c7 cosmetic changes (#13820)
* cosmetic changes

* moved limitLongName function to the utils
2020-12-16 23:11:49 +03:00
Kim Santiago
94a777b23f mark a couple data workspace tests as unstable (#13822) 2020-12-16 10:50:12 -08:00
Aasim Khan
dcb6cddab4 Fixed the stray validation error message in Resource Deployment wizard (#13747)
* Fixed the stray validation error message

* Removed not working ' with validation
Adding back cancel button disabling
2020-12-16 10:32:51 -08:00
Charles Gagnon
4575f2bbe6 Fix paths for tests (#13816) 2020-12-15 16:04:36 -08:00
Kim Santiago
9adffbb950 Fix whitespace differences in sqlproj (#13805)
* add whiteSpaceAtEndOfSelfclosingTag

* update test baselines
2020-12-15 11:44:41 -08:00
Brian Bergeron
725cd9f82f Arc - Update Postgres name length limit (#13807)
Arc - Update Postgres name length limit. It was recently reduced from 12 to 11.
2020-12-15 10:50:18 -08:00
Drew Skwiers-Koballa
1903388b6b Server Reports extension: fix for start and stop xevent sessions (#13565)
* fix for start and stop xevent sessions

* vscode.open for help URL

* setup info messages for localization

@kburtram - I could use an assist on updating the v# and publishing the vsix, but there's no rush this can wait until after the holidays
2020-12-14 20:47:04 -08:00
Alan Ren
ae6494f3e4 table component improvement (#13801)
* hyperlink column

* fixed width for image only button - old behavior
2020-12-14 20:28:43 -08:00
Chris LaFreniere
1f630b9767 Notebook Deep Link to Section (#13795)
* Notebook deep link to section

* fragment wip
2020-12-14 17:20:29 -08:00
Charles Gagnon
7496aea1b0 Fix "not externalized correctly" warnings (#13806) 2020-12-14 16:24:15 -08:00
Benjin Dubishar
f28bdf0d62 vbump 2018 -> 2019 (#13800) 2020-12-14 17:50:16 -05:00
Kim Santiago
1aaf80c3ab Make project workspace selectable if no workspace is open yet (#13508)
* allow new workspace location to be editable

* fix workspace inputbox not showing up after toggling open workspace radio buttons

* add a few tests

* cleanup

* fix errors

* addressing comments

* fix filter for windows

* add error message if existing workspace file is selected and change picker to be folder only

* address comments

* fix typos and update tests
2020-12-14 13:24:36 -08:00
Charles Gagnon
c2de462955 Update CODEOWNERS (#13798) 2020-12-14 12:43:48 -08:00
Charles Gagnon
f5e737c760 Use correct Azure graph endpoint & cleanup (#13786)
* Use correct Azure graph endpoint

* Add enum
2020-12-14 10:12:01 -08:00
Charles Gagnon
63536eba9f Fire onDidSelect event when selecting event from code (#13691)
* Fire onDidSelect event when selecting event from code

* Fix import tests

* fix typo
2020-12-14 09:04:16 -08:00
Sakshi Sharma
9dcb7d4351 Remove redundant parameter in test scripts (#13755) 2020-12-14 08:07:14 -08:00
Vasu Bhog
133958ea47 renable kernels dropdown test (#13727)
Verify no skipped core tests
2020-12-11 17:49:20 -08:00
dependabot[bot]
965f2a90c7 Bump ini from 1.3.5 to 1.3.8 in /extensions/markdown-language-features (#13791)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-11 16:09:37 -08:00
dependabot[bot]
fcbb51ad6b Bump ini from 1.3.4 to 1.3.8 (#13792)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.4 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.4...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-11 16:09:12 -08:00
dependabot[bot]
95dc01558f Bump ini from 1.3.5 to 1.3.7 in /build (#13765)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-11 16:07:56 -08:00
dependabot[bot]
15b3d80e52 Bump ini from 1.3.5 to 1.3.7 in /samples/sqlservices (#13776)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-11 16:06:50 -08:00
Lucy Zhang
b46afde818 Log active element when notebook smoke test fails (#13724)
* add error log for active element

* fix active element selector

* await isActiveElement call

* call waitforactiveleement
2020-12-11 14:23:21 -08:00
Charles Gagnon
a926f87965 Clean up Loading Component typings (#13785)
* Clean up Loading Component typings

* add properties to impl
2020-12-11 12:57:46 -08:00
Kim Santiago
496fe0afa5 change afterClean to beforeBuild for removing the generated assests.json (#13736)
* change afterClean to beforeBuild for removing the generated assests.json

* fix merge conflict

* rename files
2020-12-11 11:54:35 -08:00
Aasim Khan
d2a525bbbe Adding database backup and accounts page to migration wizard (#13764)
* Added localized strings
Created a db backup page
added radio buttons

* created components for database backup page

* Added account selection page

* Added accounts page

* Some more work done

- Added page validations
- Almost done with db backup except for a few api calls.

* Some more progress
added graph api for storage account

* Finished hooking up all the endpoints on db page.

* Some code fixed and refactoring

* Fixed a ton of validation bugs

* Added common localized strings to the constants file

* some code cleanup

* changed method name to makeHttpGetRequest

* change http result class name

* Added return types and return values to the functions

* removed void returns

* Added more return types and values

* Storing accounts in the map with ids as key
Fixed a bug in case of no subscriptions found

* cleaning up the code

* Fixed localized strings

* Added comments to get request api
Added validation logic to database backup page
removed unnecessary page validations.

* Added some get resource functions in azure core

* Changed thenable to promise

* Added arm calls for file shares and blob storage

* Added field specific validation error message

* Added examples in validation error message.

* Fixed some typings and localized string

* Added live validations to dropdowns

* Fixed method name to getSQLVMservers

* Using older storage package

* Update typings/namings (#13767)

* Update typings

* more typings fixes

* switched fileshares and blobcontainers api to http requests

* removed the extra line

* Adding resource graph documentation link as a comment

* remove makeHttpRequest api from azurecore

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
2020-12-11 11:54:19 -08:00
Charles Gagnon
e82f49d390 Add BEGIN/END to snippet (#13784) 2020-12-11 11:35:25 -08:00
Chris LaFreniere
a5231ec0e5 Notebook Extension: First logging improvements (#13729)
* First logging improvements

* PR feedback for err output
2020-12-11 11:21:06 -08:00
Karl Burtram
2df67c4f78 Update changelog (#13773) 2020-12-10 20:49:13 -08:00
Benjin Dubishar
81ed7123c6 Adding base classes for data dev extension telemetry (#13763)
* adding telemetry dependencies for data-workspaces and sql-database-projects

* Adding telemetry dependencies for dacpac extension

* Adding telemetry base to data workspaces and projects

* Adding telemetry base code to the dacpac extension

Co-authored-by: Benjin Dubishar <benjin@Largo.local>
Co-authored-by: Sai Avishkar Sreerama <ssreerama@microsoft.com>
2020-12-10 23:31:10 -05:00
Alan Ren
4ab0f729e1 fix the recent list (#13770) 2020-12-10 18:35:31 -08:00
Charles Gagnon
95d22a03ae Update required azdata versions (#13762) 2020-12-10 14:04:33 -08:00
Charles Gagnon
94feb1a80d Update STS to revert SqlClient update (#13758) 2020-12-10 13:02:32 -08:00
Sakshi Sharma
254ecc4123 Un-skip and fix a few of the db projects tests (#13726)
* Un-skip and fix a few of the db projects tests

* Addressed comments

* Fix one test failure on Linux/Mac
2020-12-10 09:50:49 -08:00
Arvind Ranasaria
515b0794b0 Add kube config and kube cluster to arc data controller screens (#13551) 2020-12-10 02:47:39 -08:00
Charles Gagnon
dc8788b77f Add loading text properties for option sources (#13743)
* Add loading text to deployment radio options

* Fix loading race condition

* Update text
2020-12-09 18:32:05 -08:00
Charles Gagnon
147ee53e35 Revert "Added Accounts and Database Backup Page to Migration wizard (#13548)" (#13742)
This reverts commit e169005571.
2020-12-09 16:28:43 -08:00
Charles Gagnon
e7884b8b61 Make loading components not valid and improve RD radio group (#13738) 2020-12-09 13:15:35 -08:00
Aditya Bist
91522caa67 Change server group look (#13608)
* change server group look

* remove dead code

* add top padding

* add bot padding as well

* fix heights to account for padding

* fix arrow alignment

* fix ellipses and node length parity

* fix alignment
2020-12-09 10:54:47 -08:00
Karl Burtram
15b8fa72ec December release readme (#13733) 2020-12-09 10:51:34 -08:00
Kim Santiago
2cf3357301 vbump schema compare and sql database projects (#13730) 2020-12-08 16:01:53 -08:00
Charles Gagnon
aee8bc2759 Fix environment variables for controller create (#13732) 2020-12-08 15:02:56 -08:00
Sai Avishkar Sreerama
adf848c1ef Added developer name to the list of developers. (#13725)
onboarding commit: Added developer name to the list.
2020-12-08 15:56:02 -06:00
Charles Gagnon
7ad328ee95 Lint azdata.d.ts (#13728) 2020-12-08 13:20:17 -08:00
Sakshi Sharma
2f18753b1f Add workspace information in Import UI (#13648)
* Add workspace information in Import UI

* Addressed comments

* Reduced space between Workspace heading and the label
2020-12-08 09:09:33 -08:00
Leila Lali
e182649adc Fixed Schema compare integration tests by adding retry (#13649) 2020-12-08 08:43:58 -08:00
Charles Gagnon
a74119038f Use console.log for retry logging (#13722) 2020-12-08 08:42:45 -08:00
Aasim Khan
e169005571 Added Accounts and Database Backup Page to Migration wizard (#13548)
* Added localized strings
Created a db backup page
added radio buttons

* created components for database backup page

* Added account selection page

* Added accounts page

* Some more work done

- Added page validations
- Almost done with db backup except for a few api calls.

* Some more progress
added graph api for storage account

* Finished hooking up all the endpoints on db page.

* Some code fixed and refactoring

* Fixed a ton of validation bugs

* Added common localized strings to the constants file

* some code cleanup

* changed method name to makeHttpGetRequest

* change http result class name

* Added return types and return values to the functions

* removed void returns

* Added more return types and values

* Storing accounts in the map with ids as key
Fixed a bug in case of no subscriptions found

* cleaning up the code

* Fixed localized strings

* Added comments to get request api
Added validation logic to database backup page
removed unnecessary page validations.

* Added some get resource functions in azure core

* Changed thenable to promise

* Added arm calls for file shares and blob storage

* Added field specific validation error message

* Added examples in validation error message.

* Fixed some typings and localized string

* Added live validations to dropdowns

* Fixed method name to getSQLVMservers
2020-12-07 23:18:07 -08:00
Kim Santiago
b10b52e4fe switch schema compare to use inputbox instead of table headers (#13715) 2020-12-07 17:42:48 -08:00
Charles Gagnon
5f04a4d499 vBump Arc and Azdata (#13717) 2020-12-07 15:43:53 -08:00
Charles Gagnon
d6e1e8eb52 Retry getConfig (#13712)
* Retry getConfig

* Add logging
2020-12-07 15:11:05 -08:00
Leila Lali
9977e83380 Adding unit tests for schema compare service (#13642) 2020-12-07 14:42:38 -08:00
Charles Gagnon
099e94fa2d Rename action config file (#13709)
* Add action for responding to Needs Logs label

* Fix action name

* Rename config file

* remove quotes
2020-12-07 14:41:00 -08:00
Charles Gagnon
7732f5c0d1 Fix action name (#13708)
* Add action for responding to Needs Logs label

* Fix action name
2020-12-07 14:20:27 -08:00
Charles Gagnon
151a18e3ee Add action for responding to Needs Logs label (#13707) 2020-12-07 14:16:04 -08:00
Charles Gagnon
e59de59e61 Add scan suppressions (#13705) 2020-12-07 13:31:59 -08:00
Lucy Zhang
f96fd911c1 Notebooks: Remove result set summary from saved metadata (#13616)
* remove result set summary from metadata

* remove batchId and id from celloutputmetadata

* remove extra line
2020-12-07 12:28:07 -08:00
Charles Gagnon
6c89c61b0d Retry publish and always try adding asset (#13700)
* Retry publish and always try adding asset

* Undo asset upload change

* Add logging
2020-12-07 11:08:04 -08:00
Charles Gagnon
97a4de4111 Have resource deployment providers return disposables (#13690)
* Add dependent field provider to resource deployment

* Change name to value provider service

* Add error handling

* providerId -> id

* Set dropdown value correctly

* missed id

* back to providerId

* fix updating missed id

* Make resource deployment providers disposable
2020-12-07 10:27:37 -08:00
Charles Gagnon
a70dce7855 Add dependent field provider to resource deployment (#13664)
* Add dependent field provider to resource deployment

* Change name to value provider service

* Add error handling

* providerId -> id

* Set dropdown value correctly

* missed id

* back to providerId

* fix updating missed id

* remove placeholder
2020-12-04 17:21:30 -08:00
Charles Gagnon
757ac1d4aa Add descriptions and validation to connected mode (#13676) 2020-12-04 16:15:40 -08:00
Monica Gupta
4092b6493b Fix issue with pasting results in Teams (#13673)
* Fix issue with pasting results in Teams

* Addressed comment to change header tag to th

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2020-12-04 15:41:53 -08:00
Chris LaFreniere
6349f1bd49 Prevent Table from Disappearing due to exception when looking for tHead (#13680)
* Prevent exception when tHead doesn't exist at node

* Add test for no thead
2020-12-04 14:42:24 -08:00
Alan Ren
0c82024cf3 add ability to control the enabled state of checkbox cells (#13644)
* control enabled state of checkbox cells

* add more check
2020-12-04 11:00:09 -08:00
dependabot[bot]
131e0a6b45 Bump highlight.js in /extensions/markdown-language-features (#13675)
Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 9.15.10 to 10.4.1.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/master/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/9.15.10...10.4.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-04 10:59:36 -08:00
Charles Gagnon
aeb22011d2 Remove placeholder on deployment wizards when field is disabled dynamically (#13658) 2020-12-04 09:25:56 -08:00
Charles Gagnon
3d82074656 Remove debug console log (#13669) 2020-12-04 09:25:44 -08:00
Charles Gagnon
6dc07e5804 Add test for dynamic enablement (#13602)
* Add test for dynamic enablement

* update names
2020-12-04 09:15:34 -08:00
Alan Ren
89d515d3ae vbump asde deployment extension (#13662)
do a patch version update, will adjust if the next change is a major one.
2020-12-03 21:03:44 -08:00
Barbara Valdez
9df56c5c0f Normalize path to change (#13660) 2020-12-03 19:17:55 -08:00
Vasu Bhog
048f85d918 Fix notebook unordered grid values after papermill execution (#13614)
* Fix unordered table

* check entire first row schema:

* SQL Notebooks should not get affected

* delete unused variable and edit comments

* refactor for efficient table ordering

* nit naming
2020-12-03 19:37:22 -06:00
Sakshi Sharma
4d3443c192 Update Import UI to match other UIs (#13637)
* Update Import UI to match other UIs

* Fixed another bug
2020-12-03 13:30:47 -08:00
Karl Burtram
f69dc6a445 Update package.json (#13626) 2020-12-03 12:51:02 -08:00
Benjin Dubishar
fde031be48 Adding SQL Edge project template (#13558)
* Checkpoint

* removing flag for not creating subfolder

* Adding Edge template

* Removing janky map function

* Adding templates for additional objects

* Updating tests, fixing bug

* Added Edge project icon

* Updating strings to Drew-approved text

* Cleaning up template scripts and Edge project template names
2020-12-03 10:33:31 -08:00
Barbara Valdez
08735c9434 add right padding to notebook toolbar action item (#13640)
* add right padding to action item

* remove extra line and add space
2020-12-03 10:23:20 -08:00
Monica Gupta
f748a8c7bb Fix empty column issue (#13641)
Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2020-12-03 09:32:15 -08:00
nasc17
67e3d2ebdb Added engine version argument to edit command. (#13610)
* Added engine version argument to edit command. Neccessary for not using pg12

* Included for changing password in overview page

* Updated fakeazdataapi test
2020-12-03 08:46:48 -08:00
Christopher C
d17ca1561f Delete ConnectionDialogue.ipynb (#13634)
this nb was an attempt at creating a connection dialog. removing
not found in toc
2020-12-03 08:33:51 -08:00
Barbara Valdez
6f731fcd9e add await to thenable method (#13635) 2020-12-02 16:14:26 -08:00
Chris LaFreniere
d86e1eec10 WYSIWYG Improvements to highlight (#13032)
* Improvements to highlight

* wip

* Tests pass

* Leverage escaping mechanism

* Tweak highlight logic

* PR comments
2020-12-02 15:51:40 -08:00
nasc17
cb567989da Changed cores validation message (#13617)
* Changed cores validation message

* Missed validation

* Remove cores validation message

* Applied verification for cores change to miaa c+s page
2020-12-02 15:22:35 -08:00
Charles Gagnon
40675fcadb Revert "Fix windows insiders icons (#13579)" (#13630)
This reverts commit a0ef594792.
2020-12-02 12:58:38 -08:00
Karl Burtram
0299ef1d83 Bump distro to pickup new icons (#13598) 2020-12-02 10:29:34 -08:00
Charles Gagnon
f544ca3be0 vBump azdata and arc extensions (#13620) 2020-12-02 10:19:27 -08:00
Charles Gagnon
273f40e2b3 Re-order summary fields for arc deployment (#13619) 2020-12-02 10:14:44 -08:00
Arvind Ranasaria
8027993ab4 Make 'Script to notebook' consistent with 'Deploy' when user cancels during password re-acquisition for controller (#13557) 2020-12-01 22:57:00 -08:00
Benjin Dubishar
1078d67728 Update tools service to .61 (#13591) 2020-12-01 13:44:15 -08:00
Kim Santiago
593cb45a50 Update open existing dialog icons (#13571)
* update open existing dialog icons

* undo removing folder.svg

* remove max width and max height
2020-12-01 13:01:56 -08:00
Alan Ren
0b1239b755 accessibility support for filtering (#13581) 2020-12-01 10:13:39 -08:00
Charles Gagnon
a0ef594792 Fix windows insiders icons (#13579) 2020-11-30 22:05:34 -08:00
Monica Gupta
e2cf607896 Update kusto release to 0.4.0 for aria fix (#13550)
* aria cluster fix for kusto

* update latest sqltoolsservice version

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2020-11-30 17:29:37 -08:00
Leila Lali
f0eeb76846 updating to 0.6.0 (#13576) 2020-11-30 16:03:50 -08:00
Charles Gagnon
5da30e6111 Update to CU8 version of BDC book (#13578) 2020-11-30 14:32:23 -08:00
Benjin Dubishar
a9eb6880d4 Adding additional parameter to data workspace provider API (#13570) 2020-11-30 12:52:08 -08:00
Vladimir Chernov
426f1ae99b tableComponent restore focus after grid append command (#13561) 2020-11-30 22:57:28 +03:00
Lucy Zhang
64dd0f0cad dont add column header in continue request (#13568) 2020-11-30 11:29:50 -08:00
760 changed files with 282228 additions and 14542 deletions

View File

@@ -12,6 +12,10 @@
{ {
"file": "build\\actions\\AutoMerge\\dist\\index.js", "file": "build\\actions\\AutoMerge\\dist\\index.js",
"_justification": "False positive from webpacked code" "_justification": "False positive from webpacked code"
},
{
"file": ".devcontainer\\devcontainer.json",
"_justification": "Local development environment - not used in production"
} }
] ]
} }

13
.github/CODEOWNERS vendored
View File

@@ -2,10 +2,15 @@
# Each line is a file pattern followed by one or more owners. # Each line is a file pattern followed by one or more owners.
# Syntax can be found here: https://docs.github.com/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # Syntax can be found here: https://docs.github.com/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
/src/sql/*.d.ts @alanrenmsft @Charles-Gagnon @ranasaria /extensions/admin-tool-ext-win @Charles-Gagnon
/extensions/resource-deployment/ @ranasaria /extensions/arc/ @Charles-Gagnon
/extensions/arc/ @ranasaria /extensions/azdata/ @Charles-Gagnon
/extensions/azdata/ @ranasaria /extensions/big-data-cluster/ @Charles-Gagnon
/extensions/dacpac/ @kisantia /extensions/dacpac/ @kisantia
/extensions/query-history/ @Charles-Gagnon
/extensions/resource-deployment/ @Charles-Gagnon
/extensions/schema-compare/ @kisantia /extensions/schema-compare/ @kisantia
/extensions/sql-database-projects/ @Benjin @kisantia /extensions/sql-database-projects/ @Benjin @kisantia
/extensions/mssql/config.json @Charles-Gagnon @alanrenmsft @kburtram
/src/sql/*.d.ts @alanrenmsft @Charles-Gagnon

27
.github/label-actions.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
Needs Logs:
comment: "We need more info to debug your particular issue. If you could attach your logs to the issue (ensure no private data is in them), it would help us fix the issue much faster.
There are two types of logs to collect:
**Console Logs**
- Open Developer Tools (Help -> Toggle Developer Tools)
- Click the **Console** tab
- Click in the log area and select all text (CTRL+A)
- Save this text into a file named console.log and attach it to this issue.
**Application Logs**
- Open command palette (Click **View** -> **Command Palette**)
- Run the command: **`Developer: Open Logs Folder`**
- This will open the log folder locally. Please zip up this folder and attach it to the issue."

15
.github/workflows/on-label.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: On Label
on:
issues:
types: [labeled]
jobs:
processLabelAction:
name: Process Label Action
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Process Label Action
uses: hramos/label-actions@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron" disturl "https://electronjs.org/headers"
target "9.3.0" target "9.4.3"
runtime "electron" runtime "electron"

View File

@@ -1,5 +1,24 @@
# Change Log # Change Log
## Version 1.25.2
* Release date: January 22, 2021
* Release status: General Availability
* Fixes https://github.com/microsoft/azuredatastudio/issues/13899
## Version 1.25.1
* Release date: December 10, 2020
* Release status: General Availability
* Fixes https://github.com/microsoft/azuredatastudio/issues/13751
## Version 1.25.0
* Release date: December 8, 2020
* Release status: General Availability
* Kusto extension improvements
* SQL Project extension improvements
* Notebook improvements
* Azure Browse Connections Preview performance improvements
* Bug Fixes
## Version 1.24.0 ## Version 1.24.0
* Release date: November 12, 2020 * Release date: November 12, 2020
* Release status: General Availability * Release status: General Availability

View File

@@ -131,10 +131,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](LICENSE.txt). Licensed under the [Source EULA](LICENSE.txt).
[win-user]: https://go.microsoft.com/fwlink/?linkid=2148607 [win-user]: https://go.microsoft.com/fwlink/?linkid=2150927
[win-system]: https://go.microsoft.com/fwlink/?linkid=2148907 [win-system]: https://go.microsoft.com/fwlink/?linkid=2150928
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2148908 [win-zip]: https://go.microsoft.com/fwlink/?linkid=2151312
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2148710 [osx-zip]: https://go.microsoft.com/fwlink/?linkid=2151311
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2148708 [linux-zip]: https://go.microsoft.com/fwlink/?linkid=2151508
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2148709 [linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2151407
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2148806 [linux-deb]: https://go.microsoft.com/fwlink/?linkid=2151506

View File

@@ -17,7 +17,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/github": "^2.1.1", "@actions/github": "^2.1.1",
"axios": "^0.19.2", "axios": "^0.21.1",
"ts-node": "^8.6.2", "ts-node": "^8.6.2",
"typescript": "^3.8.3" "typescript": "^3.8.3"
} }

View File

@@ -144,12 +144,12 @@ atob-lite@^2.0.0:
resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=
axios@^0.19.2: axios@^0.21.1:
version "0.19.2" version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies: dependencies:
follow-redirects "1.5.10" follow-redirects "^1.10.0"
before-after-hook@^2.0.0: before-after-hook@^2.0.0:
version "2.1.0" version "2.1.0"
@@ -177,13 +177,6 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0" shebang-command "^1.2.0"
which "^1.2.9" which "^1.2.9"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
deprecation@^2.0.0, deprecation@^2.3.1: deprecation@^2.0.0, deprecation@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
@@ -214,12 +207,10 @@ execa@^1.0.0:
signal-exit "^3.0.0" signal-exit "^3.0.0"
strip-eof "^1.0.0" strip-eof "^1.0.0"
follow-redirects@1.5.10: follow-redirects@^1.10.0:
version "1.5.10" version "1.13.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
dependencies:
debug "=3.1.0"
get-stream@^4.0.0: get-stream@^4.0.0:
version "4.1.0" version "4.1.0"
@@ -275,11 +266,6 @@ make-error@^1.1.1:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
nice-try@^1.0.4: nice-try@^1.0.4:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"

View File

@@ -8,6 +8,7 @@ steps:
versionSpec: "1.x" versionSpec: "1.x"
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -19,6 +20,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'

View File

@@ -12,6 +12,7 @@ steps:
displayName: Prepare cache flag displayName: Prepare cache flag
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'
@@ -49,7 +50,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
displayName: Prepare tooling displayName: Prepare tooling
@@ -61,6 +62,7 @@ steps:
displayName: Merge distro displayName: Merge distro
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -75,6 +77,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -199,7 +202,7 @@ steps:
testResultsFiles: 'test-results.xml' testResultsFiles: 'test-results.xml'
searchFolder: '$(Build.SourcesDirectory)' searchFolder: '$(Build.SourcesDirectory)'
continueOnError: true continueOnError: true
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) condition: and(succeededOrFailed(), eq(variables['RUN_TESTS'], 'true'))
- task: PublishCodeCoverageResults@1 - task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage from $(Build.SourcesDirectory)/.build/coverage/cobertura-coverage.xml' displayName: 'Publish code coverage from $(Build.SourcesDirectory)/.build/coverage/cobertura-coverage.xml'

View File

@@ -28,7 +28,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git"

View File

@@ -22,7 +22,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
displayName: Prepare tooling displayName: Prepare tooling
@@ -34,6 +34,7 @@ steps:
displayName: Merge distro displayName: Merge distro
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -48,6 +49,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'

View File

@@ -17,6 +17,7 @@ steps:
versionSpec: "1.x" versionSpec: "1.x"
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -28,6 +29,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'

View File

@@ -9,6 +9,7 @@ steps:
displayName: Prepare cache flag displayName: Prepare cache flag
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'
@@ -45,7 +46,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
displayName: Prepare tooling displayName: Prepare tooling
@@ -57,6 +58,7 @@ steps:
displayName: Merge distro displayName: Merge distro
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -71,6 +73,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -149,11 +152,17 @@ steps:
continueOnError: true continueOnError: true
condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true')) condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true'))
- script: | - bash: |
set -e set -e
mkdir -p $(Build.ArtifactStagingDirectory)/logs/linux-x64 mkdir -p $(Build.ArtifactStagingDirectory)/logs/linux-x64
cd /tmp cd /tmp
tar -czvf $(Build.ArtifactStagingDirectory)/logs/linux-x64/logs-linux-x64.tar.gz adsuser* for folder in adsuser*/
do
folder=${folder%/}
# Only archive directories we want for debugging purposes
tar -czvf $(Build.ArtifactStagingDirectory)/logs/linux-x64/$folder.tar.gz $folder/User $folder/logs
done
displayName: Archive Logs displayName: Archive Logs
continueOnError: true continueOnError: true
condition: succeededOrFailed() condition: succeededOrFailed()
@@ -226,7 +235,7 @@ steps:
testResultsFiles: '*.xml' testResultsFiles: '*.xml'
searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' searchFolder: '$(Build.ArtifactStagingDirectory)/test-results'
continueOnError: true continueOnError: true
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) condition: and(succeededOrFailed(), eq(variables['RUN_TESTS'], 'true'))
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop' displayName: 'Publish Artifact: drop'

View File

@@ -6,6 +6,7 @@ steps:
displayName: Prepare cache flag displayName: Prepare cache flag
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'
@@ -36,7 +37,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
displayName: Prepare tooling displayName: Prepare tooling
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
@@ -50,6 +51,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -62,6 +64,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -123,6 +126,7 @@ steps:
displayName: 'Publish Artifact: drop' displayName: 'Publish Artifact: drop'
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'

View File

@@ -6,6 +6,7 @@ steps:
displayName: Prepare cache flag displayName: Prepare cache flag
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'
@@ -42,7 +43,7 @@ steps:
password $(github-distro-mixin-password) password $(github-distro-mixin-password)
EOF EOF
git config user.email "andresse@microsoft.com" git config user.email "sqltools@service.microsoft.com"
git config user.name "AzureDataStudio" git config user.name "AzureDataStudio"
displayName: Prepare tooling displayName: Prepare tooling
@@ -54,6 +55,7 @@ steps:
displayName: Merge distro displayName: Merge distro
# - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 # - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
# displayName: Restore Cache - Node Modules
# inputs: # inputs:
# keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' # keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
# targetfolder: '**/node_modules, !**/node_modules/**/node_modules' # targetfolder: '**/node_modules, !**/node_modules/**/node_modules'
@@ -66,6 +68,7 @@ steps:
# condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) # condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
# - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 # - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
# displayName: Save Cache - Node Modules
# inputs: # inputs:
# keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' # keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock'
# targetfolder: '**/node_modules, !**/node_modules/**/node_modules' # targetfolder: '**/node_modules, !**/node_modules/**/node_modules'

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<clear />
<add key="ESRP" value="https://microsoft.pkgs.visualstudio.com/_packaging/ESRP/nuget/v3/index.json" /> <add key="ESRP" value="https://microsoft.pkgs.visualstudio.com/_packaging/ESRP/nuget/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

View File

@@ -13,6 +13,7 @@ steps:
addToPath: true addToPath: true
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -26,6 +27,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules # {{SQL CARBON EDIT}}
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'

View File

@@ -6,6 +6,7 @@ steps:
displayName: Prepare cache flag displayName: Prepare cache flag
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Compiled Files
inputs: inputs:
keyfile: 'build/.cachesalt, .build/commit, .build/quality' keyfile: 'build/.cachesalt, .build/commit, .build/quality'
targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min'
@@ -44,7 +45,7 @@ steps:
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
"machine github.com`nlogin azuredatastudio`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII "machine github.com`nlogin azuredatastudio`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII
exec { git config user.email "andresse@microsoft.com" } exec { git config user.email "sqltools@service.microsoft.com" }
exec { git config user.name "AzureDataStudio" } exec { git config user.name "AzureDataStudio" }
displayName: Prepare tooling displayName: Prepare tooling
@@ -55,6 +56,7 @@ steps:
displayName: Merge distro displayName: Merge distro
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: Restore Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -71,6 +73,7 @@ steps:
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: Save Cache - Node Modules
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
@@ -135,13 +138,13 @@ steps:
displayName: Run integration tests (Electron) displayName: Run integration tests (Electron)
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
- powershell: | # - powershell: |
. build/azure-pipelines/win32/exec.ps1 # . build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop" # $ErrorActionPreference = "Stop"
exec { .\scripts\test-unstable.bat --build --tfs } # exec { .\scripts\test-unstable.bat --build --tfs }
continueOnError: true # continueOnError: true
condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true')) # condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true'))
displayName: Run unstable tests # displayName: Run unstable tests
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
displayName: 'Sign out code' displayName: 'Sign out code'
@@ -290,7 +293,7 @@ steps:
searchFolder: '$(Build.SourcesDirectory)' searchFolder: '$(Build.SourcesDirectory)'
failTaskOnFailedTests: true failTaskOnFailedTests: true
continueOnError: true continueOnError: true
condition: and(succeeded(), eq(variables['RUN_UNSTABLE_TESTS'], 'true')) condition: and(succeededOrFailed(), eq(variables['RUN_UNSTABLE_TESTS'], 'true'))
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
displayName: 'Component Detection' displayName: 'Component Detection'

View File

@@ -99,4 +99,4 @@ steps:
mergeTestResults: true mergeTestResults: true
failTaskOnFailedTests: true failTaskOnFailedTests: true
continueOnError: true continueOnError: true
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) condition: and(succeededOrFailed(), eq(variables['RUN_TESTS'], 'true'))

View File

@@ -98,7 +98,7 @@ const indentationFilter = [
// {{SQL CARBON EDIT}} // {{SQL CARBON EDIT}}
'!**/*.gif', '!**/*.gif',
'!build/actions/**/*.js', '!build/actions/**/*.js',
'!**/*.{xlf,docx,sql,vsix,bacpac,ipynb,jpg}', '!**/*.{xlf,lcl,docx,sql,vsix,bacpac,ipynb,jpg}',
'!extensions/mssql/sqltoolsservice/**', '!extensions/mssql/sqltoolsservice/**',
'!extensions/import/flatfileimportservice/**', '!extensions/import/flatfileimportservice/**',
'!extensions/admin-tool-ext-win/ssmsmin/**', '!extensions/admin-tool-ext-win/ssmsmin/**',

View File

@@ -223,9 +223,10 @@ const externalExtensions = [
'profiler', 'profiler',
'query-history', 'query-history',
'schema-compare', 'schema-compare',
'server-report',
'sql-assessment', 'sql-assessment',
'sql-database-projects', 'sql-database-projects',
'sql-migration', 'sql-migration'
]; ];
// extensions that require a rebuild since they have native parts // extensions that require a rebuild since they have native parts
const rebuildExtensions = [ const rebuildExtensions = [

View File

@@ -257,9 +257,10 @@ const externalExtensions = [
'profiler', 'profiler',
'query-history', 'query-history',
'schema-compare', 'schema-compare',
'server-report',
'sql-assessment', 'sql-assessment',
'sql-database-projects', 'sql-database-projects',
'sql-migration', 'sql-migration'
]; ];
// extensions that require a rebuild since they have native parts // extensions that require a rebuild since they have native parts

View File

@@ -1860,9 +1860,9 @@ inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@~1.3.0: ini@~1.3.0:
version "1.3.5" version "1.3.7"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
is-absolute@^1.0.0: is-absolute@^1.0.0:
version "1.0.0" version "1.0.0"

View File

@@ -60,12 +60,12 @@
"git": { "git": {
"name": "electron", "name": "electron",
"repositoryUrl": "https://github.com/electron/electron", "repositoryUrl": "https://github.com/electron/electron",
"commitHash": "fb03807cd21915ddc3aa2521ba4f5ba14597bd7e" "commitHash": "ca82414364002efa665ffa7427e267adf76ed1f3"
} }
}, },
"isOnlyProductionDependency": true, "isOnlyProductionDependency": true,
"license": "MIT", "license": "MIT",
"version": "9.3.0" "version": "9.4.3"
}, },
{ {
"component": { "component": {

View File

@@ -91,7 +91,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"ads-extension-telemetry": "^1.0.0", "@microsoft/ads-extension-telemetry": "^1.1.3",
"service-downloader": "0.2.1", "service-downloader": "0.2.1",
"vscode-nls": "^3.2.1" "vscode-nls": "^3.2.1"
}, },

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import AdsTelemetryReporter from 'ads-extension-telemetry'; import AdsTelemetryReporter from '@microsoft/ads-extension-telemetry';
import * as Utils from './utils'; import * as Utils from './utils';

View File

@@ -182,6 +182,13 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@microsoft/ads-extension-telemetry@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.1.3.tgz#54160eefa21f2a536622b0617f3c3f2018cf9c87"
integrity sha512-+h6hM9oOA6Zj/N0nCGPzRgydR0YHiHpNJoNlv6a/ziWXO3RYSbQX+3U/PpT3gEA6+8RwByf6RVICo7uIGBy1LQ==
dependencies:
vscode-extension-telemetry "^0.1.6"
"@types/mocha@^5.2.5": "@types/mocha@^5.2.5":
version "5.2.7" version "5.2.7"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
@@ -192,13 +199,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
ads-extension-telemetry@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ads-extension-telemetry/-/ads-extension-telemetry-1.0.0.tgz#840b363a6ad958447819b9bc59fdad3e49de31a9"
integrity sha512-ouxZVECe4tsO0ek0dLdnAZEz1Lrytv1uLbbGZhRbZsHITsUYNjnkKnA471uWh0Dj80s+orvv49/j3/tNBDP/SQ==
dependencies:
vscode-extension-telemetry "0.1.2"
agent-base@4, agent-base@^4.3.0: agent-base@4, agent-base@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
@@ -225,15 +225,15 @@ append-transform@^2.0.0:
dependencies: dependencies:
default-require-extensions "^3.0.0" default-require-extensions "^3.0.0"
applicationinsights@1.4.0: applicationinsights@1.7.4:
version "1.4.0" version "1.7.4"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.0.tgz#e17e436427b6e273291055181e29832cca978644" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.7.4.tgz#e7d96435594d893b00cf49f70a5927105dbb8749"
integrity sha512-TV8MYb0Kw9uE2cdu4V/UvTKdOABkX2+Fga9iDz0zqV7FLrNXfmAugWZmmdTx4JoynYkln3d5CUHY3oVSUEbfFw== integrity sha512-XFLsNlcanpjFhHNvVWEfcm6hr7lu9znnb6Le1Lk5RE03YUV9X2B2n2MfM4kJZRrUdV+C0hdHxvWyv+vWoLfY7A==
dependencies: dependencies:
cls-hooked "^4.2.2" cls-hooked "^4.2.2"
continuation-local-storage "^3.2.1" continuation-local-storage "^3.2.1"
diagnostic-channel "0.2.0" diagnostic-channel "0.2.0"
diagnostic-channel-publishers "^0.3.2" diagnostic-channel-publishers "^0.3.3"
async-hook-jl@^1.7.6: async-hook-jl@^1.7.6:
version "1.7.6" version "1.7.6"
@@ -397,7 +397,7 @@ default-require-extensions@^3.0.0:
dependencies: dependencies:
strip-bom "^4.0.0" strip-bom "^4.0.0"
diagnostic-channel-publishers@^0.3.2: diagnostic-channel-publishers@^0.3.3:
version "0.3.5" version "0.3.5"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536"
integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ==
@@ -974,12 +974,12 @@ to-fast-properties@^2.0.0:
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
vscode-extension-telemetry@0.1.2: vscode-extension-telemetry@^0.1.6:
version "0.1.2" version "0.1.6"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.2.tgz#049207f5453930888ff68ca925b07bab08f2c955" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.6.tgz#048b70c93243413036a8315cda493b8e7342980c"
integrity sha512-FSbaZKlIH3VKvBJsKw7v5bESWHXzltji2rtjaJeJglpQH4tfClzwHMzlMXUZGiblV++djEzb1gW8mb5E+wxFsg== integrity sha512-rbzSg7k4NnsCdF4Lz0gI4jl3JLXR0hnlmfFgsY8CSDYhXgdoIxcre8jw5rjkobY0xhSDhbG7xCjP8zxskySJ/g==
dependencies: dependencies:
applicationinsights "1.4.0" applicationinsights "1.7.4"
vscode-nls@^3.2.1: vscode-nls@^3.2.1:
version "3.2.5" version "3.2.5"

View File

@@ -30,9 +30,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
return this.model.dialogMode; return this.model.dialogMode;
} }
protected abstract async updateModel(): Promise<void>; protected abstract updateModel(): Promise<void>;
protected abstract async initializeDialog(dialog: azdata.window.Dialog): Promise<void>; protected abstract initializeDialog(dialog: azdata.window.Dialog): Promise<void>;
public async openDialog(dialogName?: string) { public async openDialog(dialogName?: string) {
if (!this._isOpen) { if (!this._isOpen) {

View File

@@ -0,0 +1,10 @@
<svg id="a2f0dd32-c564-48d6-97d7-86323bfba35b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
<defs>
<linearGradient id="b863127b-2eb8-42a1-a46b-989a6a8d258c" x1="9" y1="18" x2="9" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#32bedd" />
<stop offset="0.576" stop-color="#32ceef" />
<stop offset="1" stop-color="#32d4f5" />
</linearGradient>
</defs>
<path d="M18,9.972V7.92l-.288-.108-2.2-.72-.576-1.4,1.116-2.376-1.44-1.44-.288.144L12.276,3.06l-1.4-.576L9.972,0H7.92L7.812.288l-.72,2.2-1.4.576L3.348,1.944l-1.44,1.44.144.288L3.1,5.724l-.576,1.4L0,8.028V10.08l.288.108,2.2.72.576,1.4L1.944,14.688l1.44,1.44.288-.144L5.724,14.94l1.4.576L8.028,18H10.08l.108-.288.72-2.2,1.4-.576,2.376,1.116,1.44-1.44-.144-.288L14.94,12.276l.576-1.4ZM9,12.95A3.95,3.95,0,1,1,12.95,9,3.947,3.947,0,0,1,9,12.95Z" fill="url(#b863127b-2eb8-42a1-a46b-989a6a8d258c)" />
</svg>

After

Width:  |  Height:  |  Size: 909 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 5H2V0H3V3.3L3.8 2.4L4.6 1.7L5.4 1L6.3 0.5C6.59362 0.297586 6.94345 0.192635 7.3 0.2L8.5 0L9.9 0.2L11.3 0.8L12.4 1.6C12.7233 1.95924 12.9927 2.36344 13.2 2.8C13.459 3.20359 13.6609 3.64107 13.8 4.1C13.9226 4.55696 13.9897 5.027 14 5.5C13.9637 6.21504 13.8291 6.92168 13.6 7.6C13.2924 8.22655 12.8874 8.80035 12.4 9.3L5.9 15.9L5.1 15.1L11.7 8.6C12.0904 8.19804 12.3964 7.72203 12.6 7.2C12.8748 6.67624 13.0125 6.09136 13 5.5C13.0218 4.90769 12.8836 4.32046 12.6 3.8C12.4219 3.23995 12.072 2.75016 11.6 2.4C11.2498 1.928 10.76 1.57815 10.2 1.4C9.67954 1.11642 9.09231 0.978247 8.5 1C7.9834 0.981133 7.4696 1.08389 7 1.3L5.8 2L4.8 2.9L3.7 4H7V5Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@@ -2,7 +2,8 @@
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"name": "python3", "name": "python3",
"display_name": "Python 3" "display_name": "Python 3",
"language": "python"
}, },
"language_info": { "language_info": {
"name": "python", "name": "python",
@@ -114,6 +115,8 @@
"# Login to the data controller.\n", "# Login to the data controller.\n",
"#\n", "#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\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", "cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
"out=run_command()" "out=run_command()"
], ],

View File

@@ -2,7 +2,8 @@
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"name": "python3", "name": "python3",
"display_name": "Python 3" "display_name": "Python 3",
"language": "python"
}, },
"language_info": { "language_info": {
"name": "python", "name": "python",
@@ -114,6 +115,8 @@
"# Login to the data controller.\n", "# Login to the data controller.\n",
"#\n", "#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\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", "cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
"out=run_command()" "out=run_command()"
], ],

View File

@@ -2,14 +2,14 @@
"name": "arc", "name": "arc",
"displayName": "%arc.displayName%", "displayName": "%arc.displayName%",
"description": "%arc.description%", "description": "%arc.description%",
"version": "0.6.5", "version": "0.7.3",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
"icon": "images/extension.png", "icon": "images/extension.png",
"engines": { "engines": {
"vscode": "*", "vscode": "*",
"azdata": ">=1.25.0" "azdata": ">=1.26.0"
}, },
"activationEvents": [ "activationEvents": [
"onCommand:arc.connectToController", "onCommand:arc.connectToController",
@@ -189,7 +189,9 @@
"editable": false, "editable": false,
"options": { "options": {
"source": { "source": {
"providerId": "arc.controller.config.profiles" "providerId": "arc.controller.config.profiles",
"loadingText": "%arc.data.controller.cluster.config.profile.loading%",
"loadingCompletedText": "%arc.data.controller.cluster.config.profile.loadingcompleted%"
}, },
"defaultValue": "azure-arc-aks-default-storage", "defaultValue": "azure-arc-aks-default-storage",
"optionsType": "radio" "optionsType": "radio"
@@ -290,11 +292,13 @@
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE", "target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
"value": "direct" "value": "direct"
}, },
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$", "type": "regex_match",
"description": "%arc.data.controller.spclientid.validation.description%" "regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
}] "description": "%arc.data.controller.spclientid.validation.description%"
}
]
}, },
{ {
"label": "%arc.data.controller.spclientsecret%", "label": "%arc.data.controller.spclientsecret%",
@@ -315,16 +319,18 @@
"type": "text", "type": "text",
"required": true, "required": true,
"defaultValue": "", "defaultValue": "",
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "enabled": false,
"enabled": { "valueProvider": {
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE", "providerId": "subscription-id-to-tenant-id",
"value": "direct" "triggerField": "AZDATA_NB_VAR_ARC_SUBSCRIPTION"
}, },
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$", "type": "regex_match",
"description": "%arc.data.controller.sptenantid.validation.description%" "regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
}] "description": "%arc.data.controller.sptenantid.validation.description%"
}
]
} }
] ]
} }
@@ -344,11 +350,13 @@
{ {
"type": "text", "type": "text",
"label": "%arc.data.controller.namespace%", "label": "%arc.data.controller.namespace%",
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$", "type": "regex_match",
"description": "%arc.data.controller.namespace.validation.description%" "regex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$",
}], "description": "%arc.data.controller.namespace.validation.description%"
}
],
"defaultValue": "arc", "defaultValue": "arc",
"required": true, "required": true,
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE" "variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE"
@@ -356,11 +364,13 @@
{ {
"type": "text", "type": "text",
"label": "%arc.data.controller.name%", "label": "%arc.data.controller.name%",
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$", "type": "regex_match",
"description": "%arc.data.controller.name.validation.description%" "regex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$",
}], "description": "%arc.data.controller.name.validation.description%"
}
],
"defaultValue": "arc-dc", "defaultValue": "arc-dc",
"required": true, "required": true,
"variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME" "variableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME"
@@ -554,18 +564,6 @@
{ {
"title": "%arc.data.controller.summary.azure%", "title": "%arc.data.controller.summary.azure%",
"fields": [ "fields": [
{
"label": "%arc.data.controller.summary.data.controller.namespace%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
},
{
"label": "%arc.data.controller.summary.data.controller.name%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
},
{ {
"label": "%arc.data.controller.summary.subscription%", "label": "%arc.data.controller.summary.subscription%",
"type": "readonly_text", "type": "readonly_text",
@@ -590,6 +588,18 @@
{ {
"title": "%arc.data.controller.summary.controller%", "title": "%arc.data.controller.summary.controller%",
"fields": [ "fields": [
{
"label": "%arc.data.controller.summary.data.controller.namespace%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
},
{
"label": "%arc.data.controller.summary.data.controller.name%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
},
{ {
"label": "%arc.data.controller.connectivitymode%", "label": "%arc.data.controller.connectivitymode%",
"type": "readonly_text", "type": "readonly_text",
@@ -608,205 +618,13 @@
}, },
{ {
"name": "azdata", "name": "azdata",
"version": "20.2.0" "version": "20.3.0"
} }
], ],
"when": true "when": true
} }
] ]
}, },
{
"name": "arc.sql",
"displayName": "%resource.type.arc.sql.display.name%",
"description": "%resource.type.arc.sql.description%",
"platforms": "*",
"icon": "./images/miaa.svg",
"tags": [
"Hybrid",
"SQL Server"
],
"providers": [
{
"notebookWizard": {
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
"doneAction": {
"label": "%deploy.done.action%"
},
"scriptAction": {
"label": "%deploy.script.action%"
},
"codeCellInsertionPosition": 5,
"title": "%arc.sql.wizard.title%",
"name": "arc.sql.wizard",
"labelPosition": "left",
"generateSummaryPage": false,
"pages": [
{
"title": "%arc.sql.wizard.page1.title%",
"labelWidth": "175px",
"inputWidth": "280px",
"sections": [
{
"title": "%arc.sql.connection.settings.section.title%",
"fields": [
{
"label": "%arc.controller%",
"variableName": "",
"type": "options",
"editable": false,
"required": true,
"options": {
"source": {
"providerId": "arc.controllers",
"variableNames": {
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
}
},
"optionsType": "dropdown"
}
},
{
"label": "%arc.sql.instance.name%",
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
"type": "text",
"defaultValue": "sqlinstance1",
"required": true,
"validations" : [{
"type": "regex_match",
"regex": "^[a-z]([-a-z0-9]{0,11}[a-z0-9])?$",
"description": "%arc.sql.invalid.instance.name%"
}]
},
{
"label": "%arc.sql.username%",
"variableName": "AZDATA_NB_VAR_SQL_USERNAME",
"type": "text",
"required": true,
"validations" : [{
"type": "regex_match",
"regex": "^(?!sa$)",
"description": "%arc.sql.invalid.username%"
}]
},
{
"label": "%arc.password%",
"variableName": "AZDATA_NB_VAR_SQL_PASSWORD",
"type": "sql_password",
"userName": "sa",
"confirmationRequired": true,
"confirmationLabel": "%arc.confirm.password%",
"defaultValue": "",
"required": true
}
]
},
{
"title": "%arc.sql.instance.settings.section.title%",
"fields": [
{
"label": "%arc.storage-class.data.label%",
"description": "%arc.sql.storage-class.data.description%",
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA",
"type": "kube_storage_class",
"required": true
},
{
"label": "%arc.storage-class.logs.label%",
"description": "%arc.sql.storage-class.logs.description%",
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS",
"type": "kube_storage_class",
"required": true
},
{
"label": "%arc.cores-request.label%",
"description": "%arc.sql.cores-request.description%",
"variableName": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
"type": "number",
"min": 1,
"required": false,
"validations": [
{
"type": "<=",
"target": "AZDATA_NB_VAR_SQL_CORES_LIMIT",
"description": "%requested.cores.less.than.or.equal.to.cores.limit%"
}
]
},
{
"label": "%arc.cores-limit.label%",
"description": "%arc.sql.cores-limit.description%",
"variableName": "AZDATA_NB_VAR_SQL_CORES_LIMIT",
"type": "number",
"min": 1,
"required": false,
"validations": [
{
"type": ">=",
"target": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
"description": "%cores.limit.greater.than.or.equal.to.requested.cores%"
}
]
},
{
"label": "%arc.memory-request.label%",
"description": "%arc.sql.memory-request.description%",
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
"type": "number",
"min": 2,
"required": false,
"validations": [{
"type": "<=",
"target": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
"description": "%requested.memory.less.than.or.equal.to.memory.limit%"
}]
},
{
"label": "%arc.memory-limit.label%",
"description": "%arc.sql.memory-limit.description%",
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
"type": "number",
"min": 2,
"required": false,
"validations": [{
"type": ">=",
"target": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
"description": "%memory.limit.greater.than.or.equal.to.requested.memory%"
}]
}
]
}
]
}
]
},
"requiredTools": [
{
"name": "kubectl"
},
{
"name": "azdata",
"version": "20.2.0"
}
],
"when": "true"
}
],
"agreement": {
"template": "%arc.agreement%",
"links": [
{
"text": "%microsoft.agreement.privacy.statement%",
"url": "https://go.microsoft.com/fwlink/?LinkId=853010"
},
{
"text": "%arc.agreement.sql.terms.conditions%",
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
}
]
}
},
{ {
"name": "arc.postgres", "name": "arc.postgres",
"displayName": "%resource.type.arc.postgres.display.name%", "displayName": "%resource.type.arc.postgres.display.name%",
@@ -853,6 +671,8 @@
"variableNames": { "variableNames": {
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT", "endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME", "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" "password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
} }
}, },
@@ -864,11 +684,13 @@
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
"type": "text", "type": "text",
"description": "%arc.postgres.server.group.name.validation.description%", "description": "%arc.postgres.server.group.name.validation.description%",
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^[a-z]([-a-z0-9]{0,10}[a-z0-9])?$", "type": "regex_match",
"description": "%arc.postgres.server.group.name.validation.description%" "regex": "^[a-z]([-a-z0-9]{0,9}[a-z0-9])?$",
}], "description": "%arc.postgres.server.group.name.validation.description%"
}
],
"required": true "required": true
}, },
{ {
@@ -885,10 +707,12 @@
"description": "%arc.postgres.server.group.workers.description%", "description": "%arc.postgres.server.group.workers.description%",
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_WORKERS", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_WORKERS",
"type": "number", "type": "number",
"validations": [{ "validations": [
"type": "is_integer", {
"description": "%should.be.integer%" "type": "is_integer",
}], "description": "%should.be.integer%"
}
],
"defaultValue": "0", "defaultValue": "0",
"min": 0 "min": 0
}, },
@@ -896,10 +720,12 @@
"label": "%arc.postgres.server.group.port%", "label": "%arc.postgres.server.group.port%",
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PORT", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PORT",
"type": "number", "type": "number",
"validations": [{ "validations": [
"type": "is_integer", {
"description": "%should.be.integer%" "type": "is_integer",
}], "description": "%should.be.integer%"
}
],
"defaultValue": "5432", "defaultValue": "5432",
"min": 1, "min": 1,
"max": 65535 "max": 65535
@@ -981,11 +807,13 @@
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST",
"type": "number", "type": "number",
"min": 1, "min": 1,
"validations": [{ "validations": [
"type": "<=", {
"target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT", "type": "<=",
"description": "%requested.cores.less.than.or.equal.to.cores.limit%" "target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT",
}] "description": "%requested.cores.less.than.or.equal.to.cores.limit%"
}
]
}, },
{ {
"label": "%arc.postgres.server.group.cores.limit.label%", "label": "%arc.postgres.server.group.cores.limit.label%",
@@ -993,11 +821,13 @@
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_LIMIT",
"type": "number", "type": "number",
"min": 1, "min": 1,
"validations": [{ "validations": [
"type": ">=", {
"target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST", "type": ">=",
"description": "%cores.limit.greater.than.or.equal.to.requested.cores%" "target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_CORES_REQUEST",
}] "description": "%cores.limit.greater.than.or.equal.to.requested.cores%"
}
]
}, },
{ {
"label": "%arc.postgres.server.group.memory.request.label%", "label": "%arc.postgres.server.group.memory.request.label%",
@@ -1005,11 +835,13 @@
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST",
"type": "number", "type": "number",
"min": 0.25, "min": 0.25,
"validations": [{ "validations": [
"type": "<=", {
"target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT", "type": "<=",
"description": "%requested.memory.less.than.or.equal.to.memory.limit%" "target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT",
}] "description": "%requested.memory.less.than.or.equal.to.memory.limit%"
}
]
}, },
{ {
"label": "%arc.postgres.server.group.memory.limit.label%", "label": "%arc.postgres.server.group.memory.limit.label%",
@@ -1017,11 +849,13 @@
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT", "variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_LIMIT",
"type": "number", "type": "number",
"min": 0.25, "min": 0.25,
"validations": [{ "validations": [
"type": ">=", {
"target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST", "type": ">=",
"description": "%memory.limit.greater.than.or.equal.to.requested.memory%" "target": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_MEMORY_REQUEST",
}] "description": "%memory.limit.greater.than.or.equal.to.requested.memory%"
}
]
} }
] ]
} }
@@ -1035,12 +869,224 @@
}, },
{ {
"name": "azdata", "name": "azdata",
"version": "20.2.0" "version": "20.3.0"
} }
], ],
"when": "true" "when": "true"
} }
], ],
"agreements": [
{
"template": "%arc.agreement%",
"links": [
{
"text": "%microsoft.agreement.privacy.statement%",
"url": "https://go.microsoft.com/fwlink/?LinkId=853010"
},
{
"text": "%arc.agreement.postgres.terms.conditions%",
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
}
],
"when": "true"
}
]
}
],
"resourceDeploymentSubTypes": [
{
"resourceName": "azure-sql-mi",
"options": [
{
"name": "mi-type",
"values": [
{
"name": "arc-mi",
"displayName": "%resource.type.arc.sql.display.name%"
}
]
}
],
"tags": [
"Hybrid",
"SQL Server"
],
"provider": {
"notebookWizard": {
"notebook": "./notebooks/arcDeployment/deploy.sql.existing.arc.ipynb",
"doneAction": {
"label": "%deploy.done.action%"
},
"scriptAction": {
"label": "%deploy.script.action%"
},
"codeCellInsertionPosition": 5,
"title": "%arc.sql.wizard.title%",
"name": "arc.sql.wizard",
"labelPosition": "left",
"generateSummaryPage": false,
"pages": [
{
"title": "%arc.sql.wizard.page1.title%",
"labelWidth": "175px",
"inputWidth": "280px",
"sections": [
{
"title": "%arc.sql.connection.settings.section.title%",
"fields": [
{
"label": "%arc.controller%",
"variableName": "",
"type": "options",
"editable": false,
"required": true,
"options": {
"source": {
"providerId": "arc.controllers",
"variableNames": {
"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"
}
},
"optionsType": "dropdown"
}
},
{
"label": "%arc.sql.instance.name%",
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
"type": "text",
"defaultValue": "sqlinstance1",
"required": true,
"validations": [
{
"type": "regex_match",
"regex": "^[a-z]([-a-z0-9]{0,11}[a-z0-9])?$",
"description": "%arc.sql.invalid.instance.name%"
}
]
},
{
"label": "%arc.sql.username%",
"variableName": "AZDATA_NB_VAR_SQL_USERNAME",
"type": "text",
"required": true,
"validations": [
{
"type": "regex_match",
"regex": "^(?!sa$)",
"description": "%arc.sql.invalid.username%"
}
]
},
{
"label": "%arc.password%",
"variableName": "AZDATA_NB_VAR_SQL_PASSWORD",
"type": "sql_password",
"userName": "sa",
"confirmationRequired": true,
"confirmationLabel": "%arc.confirm.password%",
"defaultValue": "",
"required": true
}
]
},
{
"title": "%arc.sql.instance.settings.section.title%",
"fields": [
{
"label": "%arc.storage-class.data.label%",
"description": "%arc.sql.storage-class.data.description%",
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA",
"type": "kube_storage_class",
"required": true
},
{
"label": "%arc.storage-class.logs.label%",
"description": "%arc.sql.storage-class.logs.description%",
"variableName": "AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS",
"type": "kube_storage_class",
"required": true
},
{
"label": "%arc.cores-request.label%",
"description": "%arc.sql.cores-request.description%",
"variableName": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
"type": "number",
"min": 1,
"required": false,
"validations": [
{
"type": "<=",
"target": "AZDATA_NB_VAR_SQL_CORES_LIMIT",
"description": "%requested.cores.less.than.or.equal.to.cores.limit%"
}
]
},
{
"label": "%arc.cores-limit.label%",
"description": "%arc.sql.cores-limit.description%",
"variableName": "AZDATA_NB_VAR_SQL_CORES_LIMIT",
"type": "number",
"min": 1,
"required": false,
"validations": [
{
"type": ">=",
"target": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
"description": "%cores.limit.greater.than.or.equal.to.requested.cores%"
}
]
},
{
"label": "%arc.memory-request.label%",
"description": "%arc.sql.memory-request.description%",
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
"type": "number",
"min": 2,
"required": false,
"validations": [
{
"type": "<=",
"target": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
"description": "%requested.memory.less.than.or.equal.to.memory.limit%"
}
]
},
{
"label": "%arc.memory-limit.label%",
"description": "%arc.sql.memory-limit.description%",
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
"type": "number",
"min": 2,
"required": false,
"validations": [
{
"type": ">=",
"target": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
"description": "%memory.limit.greater.than.or.equal.to.requested.memory%"
}
]
}
]
}
]
}
]
},
"requiredTools": [
{
"name": "kubectl"
},
{
"name": "azdata",
"version": "20.3.0"
}
],
"when": "mi-type=arc-mi"
},
"agreement": { "agreement": {
"template": "%arc.agreement%", "template": "%arc.agreement%",
"links": [ "links": [
@@ -1049,10 +1095,21 @@
"url": "https://go.microsoft.com/fwlink/?LinkId=853010" "url": "https://go.microsoft.com/fwlink/?LinkId=853010"
}, },
{ {
"text": "%arc.agreement.postgres.terms.conditions%", "text": "%arc.agreement.sql.terms.conditions%",
"url": "https://go.microsoft.com/fwlink/?linkid=2045708" "url": "https://go.microsoft.com/fwlink/?linkid=2045708"
} }
] ],
"when": "mi-type=arc-mi"
},
"helpText": {
"template": "%arc.agreement.sql.help.text%",
"links": [
{
"text": "%arc.agreement.sql.help.text.learn.more%",
"url": "https://go.microsoft.com/fwlink/?linkid=2141849"
}
],
"when": "mi-type=arc-mi"
} }
} }
] ]
@@ -1070,6 +1127,7 @@
"@types/sinon": "^9.0.4", "@types/sinon": "^9.0.4",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/yamljs": "^0.2.31", "@types/yamljs": "^0.2.31",
"azdata-test": "^1.1.1",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mocha-junit-reporter": "^1.17.0", "mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7", "mocha-multi-reporters": "^1.1.7",

View File

@@ -20,6 +20,8 @@
"arc.data.controller.kube.cluster.context": "Cluster context", "arc.data.controller.kube.cluster.context": "Cluster context",
"arc.data.controller.cluster.config.profile.title": "Choose the config profile", "arc.data.controller.cluster.config.profile.title": "Choose the config profile",
"arc.data.controller.cluster.config.profile": "Config profile", "arc.data.controller.cluster.config.profile": "Config profile",
"arc.data.controller.cluster.config.profile.loading": "Loading config profiles",
"arc.data.controller.cluster.config.profile.loadingcompleted": "Loading config profiles complete",
"arc.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration", "arc.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
"arc.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.", "arc.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
"arc.data.controller.create.controllerconfig.title": "Controller Configuration", "arc.data.controller.create.controllerconfig.title": "Controller Configuration",
@@ -39,12 +41,12 @@
"arc.data.controller.connectivitymode": "Connectivity Mode", "arc.data.controller.connectivitymode": "Connectivity Mode",
"arc.data.controller.direct": "Direct", "arc.data.controller.direct": "Direct",
"arc.data.controller.indirect": "Indirect", "arc.data.controller.indirect": "Indirect",
"arc.data.controller.serviceprincipal.description": "When deploying a controller in direct connected mode a Service Principal is required for uploading metrics to Azure. {0} about how to create this Service Principal and assign it the correct roles.", "arc.data.controller.serviceprincipal.description": "When deploying a controller in direct connected mode a Service Principal is required for connecting to Azure. {0} about how to create this Service Principal and assign it the correct roles.",
"arc.data.controller.spclientid": "Service Principal Client ID", "arc.data.controller.spclientid": "Service Principal Client ID",
"arc.data.controller.spclientid.description": "The Application (client) ID of the created Service Principal", "arc.data.controller.spclientid.description": "The Client (application) ID of the created Service Principal",
"arc.data.controller.spclientid.validation.description": "The client ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "arc.data.controller.spclientid.validation.description": "The Client ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"arc.data.controller.spclientsecret": "Service Principal Client Secret", "arc.data.controller.spclientsecret": "Service Principal Client Secret",
"arc.data.controller.spclientsecret.description": "The password generated during creation of the Service Principal", "arc.data.controller.spclientsecret.description": "The secret (password) of the Service Principal",
"arc.data.controller.sptenantid": "Service Principal Tenant ID", "arc.data.controller.sptenantid": "Service Principal Tenant ID",
"arc.data.controller.sptenantid.description": "The Tenant ID of the Service Principal. This must be the same as the Tenant ID of the subscription selected to create this controller for.", "arc.data.controller.sptenantid.description": "The Tenant ID of the Service Principal. This must be the same as the Tenant ID of the subscription selected to create this controller for.",
"arc.data.controller.sptenantid.validation.description": "The tenant ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "arc.data.controller.sptenantid.validation.description": "The tenant ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
@@ -123,7 +125,7 @@
"arc.postgres.settings.resource.title": "Resource settings", "arc.postgres.settings.resource.title": "Resource settings",
"arc.postgres.settings.storage.title": "Storage settings", "arc.postgres.settings.storage.title": "Storage settings",
"arc.postgres.server.group.name": "Server group name", "arc.postgres.server.group.name": "Server group name",
"arc.postgres.server.group.name.validation.description": "Server group name must consist of lower case alphanumeric characters or '-', start with a letter, end with an alphanumeric character, and be 12 characters or fewer in length.", "arc.postgres.server.group.name.validation.description": "Server group name must consist of lower case alphanumeric characters or '-', start with a letter, end with an alphanumeric character, and be 11 characters or fewer in length.",
"arc.postgres.server.group.workers.label": "Number of workers", "arc.postgres.server.group.workers.label": "Number of workers",
"arc.postgres.server.group.workers.description": "The number of worker nodes to provision in a sharded cluster, or zero (the default) for single-node Postgres.", "arc.postgres.server.group.workers.description": "The number of worker nodes to provision in a sharded cluster, or zero (the default) for single-node Postgres.",
"arc.postgres.server.group.port": "Port", "arc.postgres.server.group.port": "Port",
@@ -151,5 +153,7 @@
"requested.cores.less.than.or.equal.to.cores.limit": "Requested cores must be less than or equal to cores limit", "requested.cores.less.than.or.equal.to.cores.limit": "Requested cores must be less than or equal to cores limit",
"cores.limit.greater.than.or.equal.to.requested.cores": "Cores limit must be greater than or equal to requested cores", "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", "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" "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"
} }

View File

@@ -4,11 +4,17 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as arc from 'arc'; import * as arc from 'arc';
import * as rd from 'resource-deployment';
import * as loc from '../localizedConstants';
import { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog'; import { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../ui/tree/controllerTreeNode'; import { ControllerTreeNode } from '../ui/tree/controllerTreeNode';
import { UserCancelledError } from './utils';
export class UserCancelledError extends Error implements rd.ErrorWithType {
public get type(): rd.ErrorType {
return rd.ErrorType.userCancelled;
}
}
export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension { export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension {
return { return {
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider), getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider),
@@ -16,12 +22,13 @@ export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtensi
reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo) reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo)
}; };
} }
export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> { export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
const dialog = new PasswordToControllerDialog(treeDataProvider); const dialog = new PasswordToControllerDialog(treeDataProvider);
dialog.showDialog(controllerInfo); dialog.showDialog(controllerInfo);
const model = await dialog.waitForClose(); const model = await dialog.waitForClose();
if (!model) { if (!model) {
throw new UserCancelledError(); throw new UserCancelledError(loc.userCancelledError);
} }
return model.password; return model.password;
} }

View File

@@ -1,83 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Deferred } from './promise';
const enum Status {
notStarted,
inProgress,
done
}
interface State<T> {
entry?: T,
error?: Error,
status: Status,
id: number,
pendingOperation: Deferred<void>
}
/**
* An implementation of Cache Manager which ensures that only one call to populate cache miss is pending at a given time.
* All remaining calls for retrieval are awaited until the one in progress finishes and then all awaited calls are resolved with the value
* from the cache.
*/
export class CacheManager<K, T> {
private _cache = new Map<K, State<T>>();
private _id = 0;
public async getCacheEntry(key: K, retrieveEntry: (key: K) => Promise<T>): Promise<T> {
const cacheHit: State<T> | undefined = this._cache.get(key);
// each branch either throws or returns the password.
if (cacheHit === undefined) {
// populate a new state entry and add it to the cache
const state: State<T> = {
status: Status.notStarted,
id: this._id++,
pendingOperation: new Deferred<void>()
};
this._cache.set(key, state);
// now that we have the state entry initialized, retry to fetch the cacheEntry
let returnValue: T = await this.getCacheEntry(key, retrieveEntry);
await state.pendingOperation;
return returnValue!;
} else {
switch (cacheHit.status) {
case Status.notStarted: {
cacheHit.status = Status.inProgress;
// retrieve and populate the missed cache hit.
try {
cacheHit.entry = await retrieveEntry(key);
} catch (error) {
cacheHit.error = error;
} finally {
cacheHit.status = Status.done;
// we do not reject here even in error case because we do not want our awaits on pendingOperation to throw
// We track our own error state and when all done we throw if an error had happened. This results
// in the rejection of the promised returned by this method.
cacheHit.pendingOperation.resolve();
}
return await this.getCacheEntry(key, retrieveEntry);
}
case Status.inProgress: {
await cacheHit.pendingOperation;
return await this.getCacheEntry(key, retrieveEntry);
}
case Status.done: {
if (cacheHit.error !== undefined) {
await cacheHit.pendingOperation;
throw cacheHit.error;
}
else {
await cacheHit.pendingOperation;
return cacheHit.entry!;
}
}
}
}
}
}

View File

@@ -13,6 +13,11 @@ export interface KubeClusterContext {
isCurrentContext: boolean; isCurrentContext: boolean;
} }
/**
* returns the cluster context defined in the {@see configFile}
*
* @param configFile
*/
export function getKubeConfigClusterContexts(configFile: string): Promise<KubeClusterContext[]> { export function getKubeConfigClusterContexts(configFile: string): Promise<KubeClusterContext[]> {
const config: any = yamljs.load(configFile); const config: any = yamljs.load(configFile);
const rawContexts = <any[]>config['contexts']; const rawContexts = <any[]>config['contexts'];
@@ -33,6 +38,38 @@ export function getKubeConfigClusterContexts(configFile: string): Promise<KubeCl
return Promise.resolve(contexts); return Promise.resolve(contexts);
} }
/**
* searches for {@see previousClusterContext} in the array of {@see clusterContexts}.
* if {@see previousClusterContext} was truthy and it was found in {@see clusterContexts}
* then it returns {@see previousClusterContext}
* else it returns the current cluster context from {@see clusterContexts} unless throwIfNotFound was set on input in which case an error is thrown instead.
* else it returns the current cluster context from {@see clusterContexts}
*
*
* @param clusterContexts
* @param previousClusterContext
* @param throwIfNotFound
*/
export function getCurrentClusterContext(clusterContexts: KubeClusterContext[], previousClusterContext?: string, throwIfNotFound: boolean = false): string {
if (previousClusterContext) {
if (clusterContexts.find(c => c.name === previousClusterContext)) { // if previous cluster context value is found in clusters then return that value
return previousClusterContext;
} else {
if (throwIfNotFound) {
throw new Error(loc.clusterContextNotFound(previousClusterContext));
}
}
}
// if not previousClusterContext or throwIfNotFound was false when previousCLusterContext was not found in the clusterContexts
const currentClusterContext = clusterContexts.find(c => c.isCurrentContext)?.name;
throwUnless(currentClusterContext !== undefined, loc.noCurrentClusterContext);
return currentClusterContext;
}
/**
* returns the default kube config file path
*/
export function getDefaultKubeConfigPath(): string { export function getDefaultKubeConfigPath(): string {
return path.join(os.homedir(), '.kube', 'config'); return path.join(os.homedir(), '.kube', 'config');
} }

View File

@@ -8,7 +8,7 @@
*/ */
export class Deferred<T> { export class Deferred<T> {
promise: Promise<T>; promise: Promise<T>;
resolve!: (value?: T | PromiseLike<T>) => void; resolve!: (value: T | PromiseLike<T>) => void;
reject!: (reason?: any) => void; reject!: (reason?: any) => void;
constructor() { constructor() {
this.promise = new Promise<T>((resolve, reject) => { this.promise = new Promise<T>((resolve, reject) => {

View File

@@ -9,8 +9,6 @@ import * as vscode from 'vscode';
import { ConnectionMode, IconPath, IconPathHelper } from '../constants'; import { ConnectionMode, IconPath, IconPathHelper } from '../constants';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
export class UserCancelledError extends Error { }
/** /**
* Converts the resource type name into the localized Display Name for that type. * Converts the resource type name into the localized Display Name for that type.
* @param resourceType The resource type name to convert * @param resourceType The resource type name to convert
@@ -111,7 +109,7 @@ export function getDatabaseStateDisplayText(state: string): string {
* @returns Promise resolving to the user's input if it passed validation, * @returns Promise resolving to the user's input if it passed validation,
* or undefined if the input box was closed for any other reason * or undefined if the input box was closed for any other reason
*/ */
async function promptInputBox(title: string, options: vscode.InputBoxOptions): Promise<string> { async function promptInputBox(title: string, options: vscode.InputBoxOptions): Promise<string | undefined> {
const inputBox = vscode.window.createInputBox(); const inputBox = vscode.window.createInputBox();
inputBox.title = title; inputBox.title = title;
inputBox.prompt = options.prompt; inputBox.prompt = options.prompt;
@@ -200,12 +198,16 @@ export function getErrorMessage(error: any, useMessageWithLink: boolean = false)
/** /**
* Parses an address into its separate ip and port values. Address must be in the form <ip>:<port> * Parses an address into its separate ip and port values. Address must be in the form <ip>:<port>
* or <ip>,<port>
* @param address The address to parse * @param address The address to parse
*/ */
export function parseIpAndPort(address: string): { ip: string, port: string } { export function parseIpAndPort(address: string): { ip: string, port: string } {
const sections = address.split(':'); let sections = address.split(':');
if (sections.length !== 2) { if (sections.length !== 2) {
throw new Error(`Invalid address format for ${address}. Address must be in the form <ip>:<port>`); sections = address.split(',');
if (sections.length !== 2) {
throw new Error(`Invalid address format for ${address}. Address must be in the form <ip>:<port> or <ip>,<port>`);
}
} }
return { return {
ip: sections[0], ip: sections[0],
@@ -297,3 +299,35 @@ export async function tryExecuteAction<T>(action: () => T | PromiseLike<T>): Pro
} }
return { result, error }; return { result, error };
} }
function decorate(decorator: (fn: Function, key: string) => Function): Function {
return (_target: any, key: string, descriptor: any) => {
let fnKey: string | null = null;
let fn: Function | null = null;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
} else if (typeof descriptor.get === 'function') {
fnKey = 'get';
fn = descriptor.get;
}
if (!fn || !fnKey) {
throw new Error('not supported');
}
descriptor[fnKey] = decorator(fn, key);
};
}
export function debounce(delay: number): Function {
return decorate((fn, key) => {
const timerKey = `$debounce$${key}`;
return function (this: any, ...args: any[]) {
clearTimeout(this[timerKey]);
this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
};
});
}

View File

@@ -34,6 +34,7 @@ export class IconPathHelper {
public static properties: IconPath; public static properties: IconPath;
public static networking: IconPath; public static networking: IconPath;
public static refresh: IconPath; public static refresh: IconPath;
public static reset: IconPath;
public static support: IconPath; public static support: IconPath;
public static wrench: IconPath; public static wrench: IconPath;
public static miaa: IconPath; public static miaa: IconPath;
@@ -44,6 +45,7 @@ export class IconPathHelper {
public static discard: IconPath; public static discard: IconPath;
public static fail: IconPath; public static fail: IconPath;
public static information: IconPath; public static information: IconPath;
public static gear: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) { public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.context = context; IconPathHelper.context = context;
@@ -95,6 +97,10 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/refresh.svg'), light: context.asAbsolutePath('images/refresh.svg'),
dark: context.asAbsolutePath('images/refresh.svg') dark: context.asAbsolutePath('images/refresh.svg')
}; };
IconPathHelper.reset = {
light: context.asAbsolutePath('images/reset.svg'),
dark: context.asAbsolutePath('images/reset.svg')
};
IconPathHelper.support = { IconPathHelper.support = {
light: context.asAbsolutePath('images/support.svg'), light: context.asAbsolutePath('images/support.svg'),
dark: context.asAbsolutePath('images/support.svg') dark: context.asAbsolutePath('images/support.svg')
@@ -135,6 +141,10 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/information.svg'), light: context.asAbsolutePath('images/information.svg'),
dark: context.asAbsolutePath('images/information.svg'), dark: context.asAbsolutePath('images/information.svg'),
}; };
IconPathHelper.gear = {
light: context.asAbsolutePath('images/gear.svg'),
dark: context.asAbsolutePath('images/gear.svg'),
};
} }
} }

View File

@@ -28,14 +28,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
}); });
vscode.commands.registerCommand('arc.connectToController', async () => { vscode.commands.registerCommand('arc.connectToController', async () => {
const nodes = await treeDataProvider.getChildren();
if (nodes.length > 0) {
const response = await vscode.window.showErrorMessage(loc.onlyOneControllerSupported, loc.yes, loc.no);
if (response !== loc.yes) {
return;
}
await treeDataProvider.removeController(nodes[0] as ControllerTreeNode);
}
const dialog = new ConnectToControllerDialog(treeDataProvider); const dialog = new ConnectToControllerDialog(treeDataProvider);
dialog.showDialog(); dialog.showDialog();
const model = await dialog.waitForClose(); const model = await dialog.waitForClose();
@@ -67,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
// register option sources // register option sources
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports; const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)); context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)));
return arcApi(treeDataProvider); return arcApi(treeDataProvider);
} }

View File

@@ -23,12 +23,15 @@ export const properties = localize('arc.properties', "Properties");
export const settings = localize('arc.settings', "Settings"); export const settings = localize('arc.settings', "Settings");
export const security = localize('arc.security', "Security"); export const security = localize('arc.security', "Security");
export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage"); export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage");
export const nodeParameters = localize('arc.nodeParameters', "Node Parameters");
export const compute = localize('arc.compute', "Compute"); export const compute = localize('arc.compute', "Compute");
export const backup = localize('arc.backup', "Backup"); export const backup = localize('arc.backup', "Backup");
export const newSupportRequest = localize('arc.newSupportRequest', "New support request"); export const newSupportRequest = localize('arc.newSupportRequest', "New support request");
export const diagnoseAndSolveProblems = localize('arc.diagnoseAndSolveProblems', "Diagnose and solve problems"); export const diagnoseAndSolveProblems = localize('arc.diagnoseAndSolveProblems', "Diagnose and solve problems");
export const supportAndTroubleshooting = localize('arc.supportAndTroubleshooting', "Support + troubleshooting"); export const supportAndTroubleshooting = localize('arc.supportAndTroubleshooting', "Support + troubleshooting");
export const resourceHealth = localize('arc.resourceHealth', "Resource health"); export const resourceHealth = localize('arc.resourceHealth', "Resource health");
export const parameterName = localize('arc.parameterName', "Parameter Name");
export const value = localize('arc.value', "Value");
export const newInstance = localize('arc.createNew', "New Instance"); export const newInstance = localize('arc.createNew', "New Instance");
export const deleteText = localize('arc.delete', "Delete"); export const deleteText = localize('arc.delete', "Delete");
@@ -61,16 +64,20 @@ export const yes = localize('arc.yes', "Yes");
export const no = localize('arc.no', "No"); export const no = localize('arc.no', "No");
export const feedback = localize('arc.feedback', "Feedback"); export const feedback = localize('arc.feedback', "Feedback");
export const selectConnectionString = localize('arc.selectConnectionString', "Select from available client connection strings below."); export const selectConnectionString = localize('arc.selectConnectionString', "Select from available client connection strings below.");
export const addingWokerNodes = localize('arc.addingWokerNodes', "adding worker nodes"); export const addingWorkerNodes = localize('arc.addingWorkerNodes', "adding worker nodes");
export const workerNodesDescription = localize('arc.workerNodesDescription', "Expand your server group and scale your database by adding worker nodes."); export const workerNodesDescription = localize('arc.workerNodesDescription', "Expand your server group and scale your database by adding worker nodes.");
export const postgresConfigurationInformation = localize('arc.postgres.configurationInformation', "You can configure the number of CPU cores and storage size that will apply to both worker nodes and coordinator node. Each worker node will have the same configuration. Adjust the number of CPU cores and memory settings for your server group."); export const postgresConfigurationInformation = localize('arc.postgres.configurationInformation', "You can configure the number of CPU cores and storage size that will apply to both worker nodes and coordinator node. Each worker node will have the same configuration. Adjust the number of CPU cores and memory settings for your server group.");
export const workerNodesInformation = localize('arc.workerNodeInformation', "In preview it is not possible to reduce the number of worker nodes. Please refer to documentation linked above for more information."); export const workerNodesInformation = localize('arc.workerNodeInformation', "In preview it is not possible to reduce the number of worker nodes. Please refer to documentation linked above for more information.");
export const vCores = localize('arc.vCores', "vCores"); export const vCores = localize('arc.vCores', "vCores");
export const ram = localize('arc.ram', "RAM"); export const ram = localize('arc.ram', "RAM");
export const refresh = localize('arc.refresh', "Refresh"); export const refresh = localize('arc.refresh', "Refresh");
export const resetAllToDefault = localize('arc.resetAllToDefault', "Reset all to default");
export const resetToDefault = localize('arc.resetToDefault', "Reset to default");
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot"); export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal."); export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal.");
export const running = localize('arc.running', "Running"); export const running = localize('arc.running', "Running");
export const ready = localize('arc.ready', "Ready");
export const notReady = localize('arc.notReady', "Not Ready");
export const pending = localize('arc.pending', "Pending"); export const pending = localize('arc.pending', "Pending");
export const failed = localize('arc.failed', "Failed"); export const failed = localize('arc.failed', "Failed");
export const unknown = localize('arc.unknown', "Unknown"); export const unknown = localize('arc.unknown', "Unknown");
@@ -79,19 +86,27 @@ export const indirect = localize('arc.indirect', "Indirect");
export const loading = localize('arc.loading', "Loading..."); export const loading = localize('arc.loading', "Loading...");
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials"); export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
export const noInstancesAvailable = localize('arc.noInstancesAvailable', "No instances available"); export const noInstancesAvailable = localize('arc.noInstancesAvailable', "No instances available");
export const connectToServer = localize('arc.connecToServer', "Connect to Server");
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller"); export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
export function connectToSql(name: string): string { return localize('arc.connectToSql', "Connect to SQL managed instance - Azure Arc ({0})", name); } export function connectToMSSql(name: string): string { return localize('arc.connectToMSSql', "Connect to SQL managed instance - Azure Arc ({0})", name); }
export function connectToPGSql(name: string): string { return localize('arc.connectToPGSql', "Connect to PostgreSQL Hyperscale - Azure Arc ({0})", name); }
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller"); export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
export const controllerUrl = localize('arc.controllerUrl', "Controller URL"); export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint"); export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
export const controllerName = localize('arc.controllerName', "Name"); export const controllerName = localize('arc.controllerName', "Name");
export const controllerKubeConfig = localize('arc.controllerKubeConfig', "Kube Config File Path");
export const controllerClusterContext = localize('arc.controllerClusterContext', "Cluster Context");
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc"); export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
export const postgresProviderName = localize('arc.postgresProviderName', "PGSQL");
export const miaaProviderName = localize('arc.miaaProviderName', "MSSQL");
export const username = localize('arc.username', "Username"); export const username = localize('arc.username', "Username");
export const password = localize('arc.password', "Password"); export const password = localize('arc.password', "Password");
export const rememberPassword = localize('arc.rememberPassword', "Remember Password"); export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
export const connect = localize('arc.connect', "Connect"); export const connect = localize('arc.connect', "Connect");
export const cancel = localize('arc.cancel', "Cancel"); export const cancel = localize('arc.cancel', "Cancel");
export const ok = localize('arc.ok', "Ok"); export const ok = localize('arc.ok', "Ok");
export const on = localize('arc.on', "On");
export const off = localize('arc.off', "Off");
export const notConfigured = localize('arc.notConfigured', "Not Configured"); export const notConfigured = localize('arc.notConfigured', "Not Configured");
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states // Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
@@ -120,6 +135,10 @@ export const databaseName = localize('arc.databaseName', "Database name");
export const enterNewPassword = localize('arc.enterNewPassword', "Enter a new password"); export const enterNewPassword = localize('arc.enterNewPassword', "Enter a new password");
export const confirmNewPassword = localize('arc.confirmNewPassword', "Confirm the new password"); export const confirmNewPassword = localize('arc.confirmNewPassword', "Confirm the new password");
export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients', "Learn more about Azure PostgreSQL Hyperscale client interfaces"); export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients', "Learn more about Azure PostgreSQL Hyperscale client interfaces");
export const nodeParametersDescription = localize('arc.nodeParametersDescription', " These server parameters of the Coordinator node and the Worker nodes can be set to custom (non-default) values. Search to find parameters.");
export const learnAboutNodeParameters = localize('arc.learnAboutNodeParameters', "Learn more about database engine settings for Azure Arc enabled PostgreSQL Hyperscale");
export const noNodeParametersFound = localize('arc.noNodeParametersFound', "No worker server parameters found...");
export const searchToFilter = localize('arc.searchToFilter', "Search to filter items...");
export const scalingCompute = localize('arc.scalingCompute', "scaling compute vCores and memory."); export const scalingCompute = localize('arc.scalingCompute', "scaling compute vCores and memory.");
export const postgresComputeAndStorageDescriptionPartOne = localize('arc.postgresComputeAndStorageDescriptionPartOne', "You can scale your Azure Arc enabled"); export const postgresComputeAndStorageDescriptionPartOne = localize('arc.postgresComputeAndStorageDescriptionPartOne', "You can scale your Azure Arc enabled");
export const miaaComputeAndStorageDescriptionPartOne = localize('arc.miaaComputeAndStorageDescriptionPartOne', "You can scale your Azure SQL managed instance - Azure Arc by"); export const miaaComputeAndStorageDescriptionPartOne = localize('arc.miaaComputeAndStorageDescriptionPartOne', "You can scale your Azure SQL managed instance - Azure Arc by");
@@ -139,7 +158,6 @@ export const coresRequest = localize('arc.coresRequest', "CPU request:");
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB):"); export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB):");
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB):"); export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB):");
export const workerValidationErrorMessage = localize('arc.workerValidationErrorMessage', "The number of workers cannot be decreased."); export const workerValidationErrorMessage = localize('arc.workerValidationErrorMessage', "The number of workers cannot be decreased.");
export const coresValidationErrorMessage = localize('arc.coresValidationErrorMessage', "Valid CPU resource quantities are strictly positive.");
export const memoryRequestValidationErrorMessage = localize('arc.memoryRequestValidationErrorMessage', "Memory request must be at least 0.25Gib"); export const memoryRequestValidationErrorMessage = localize('arc.memoryRequestValidationErrorMessage', "Memory request must be at least 0.25Gib");
export const memoryLimitValidationErrorMessage = localize('arc.memoryLimitValidationErrorMessage', "Memory limit must be at least 0.25Gib"); export const memoryLimitValidationErrorMessage = localize('arc.memoryLimitValidationErrorMessage', "Memory limit must be at least 0.25Gib");
export const arcResources = localize('arc.arcResources', "Azure Arc Resources"); export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
@@ -152,9 +170,15 @@ export const details = localize('arc.details', "Details");
export const lastUpdated = localize('arc.lastUpdated', "Last updated"); export const lastUpdated = localize('arc.lastUpdated', "Last updated");
export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available."); export const noExternalEndpoint = localize('arc.noExternalEndpoint', "No External Endpoint has been configured so this information isn't available.");
export const podsReady = localize('arc.podsReady', "pods ready"); export const podsReady = localize('arc.podsReady', "pods ready");
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 function rangeSetting(min: string, max: string): string { return localize('arc.rangeSetting', "Value is expected to be in the range {0} - {1}", min, max); }
export function allowedValue(value: string): string { return localize('arc.allowedValue', "Value is expected to be {0}", value); }
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); } export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); } export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); }
export function installingExtension(name: string): string { return localize('arc.installingExtension', "Installing extension '{0}'...", name); }
export function extensionInstalled(name: string): string { return localize('arc.extensionInstalled', "Extension '{0}' has been installed.", name); }
export function updatingInstance(name: string): string { return localize('arc.updatingInstance', "Updating instance '{0}'...", name); } 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 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 instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
@@ -172,36 +196,49 @@ export function numVCores(vCores: string | undefined): string {
} }
} }
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); } export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
export function validationMin(min: number): string { return localize('arc.validationMin', "Value must be greater than or equal to {0}.", min); }
// Errors // Errors
export const connectionRequired = localize('arc.connectionRequired', "A connection is required to show all properties. Click refresh to re-enter connection information"); export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A connection is required to show and set database engine settings.");
export const miaaConnectionRequired = localize('arc.miaaConnectionRequired', "A connection is required to list the databases on this instance.");
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration."); export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
export function outOfRange(min: string, max: string): string { return localize('arc.outOfRange', "The number must be in range {0} - {1}", min, max); }
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); } export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
export function resetFailed(error: any): string { return localize('arc.resetFailed', "Reset failed. {0}", getErrorMessage(error)); }
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); } export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
export function instanceDeletionFailed(name: string, error: any): string { return localize('arc.instanceDeletionFailed', "Failed to delete instance {0}. {1}", name, getErrorMessage(error)); } export function instanceDeletionFailed(name: string, error: any): string { return localize('arc.instanceDeletionFailed', "Failed to delete instance {0}. {1}", name, getErrorMessage(error)); }
export function instanceUpdateFailed(name: string, error: any): string { return localize('arc.instanceUpdateFailed', "Failed to update instance {0}. {1}", name, getErrorMessage(error)); } export function instanceUpdateFailed(name: string, error: any): string { return localize('arc.instanceUpdateFailed', "Failed to update instance {0}. {1}", name, getErrorMessage(error)); }
export function pageDiscardFailed(error: any): string { return localize('arc.pageDiscardFailed', "Failed to discard user input. {0}", getErrorMessage(error)); } export function pageDiscardFailed(error: any): string { return localize('arc.pageDiscardFailed', "Failed to discard user input. {0}", getErrorMessage(error)); }
export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); } export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, getErrorMessage(error)); }
export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); } export function connectToControllerFailed(url: string, error: any): string { return localize('arc.connectToControllerFailed', "Could not connect to controller {0}. {1}", url, getErrorMessage(error)); }
export function connectToSqlFailed(serverName: string, error: any): string { return localize('arc.connectToSqlFailed', "Could not connect to SQL managed instance - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); } export function connectToMSSqlFailed(serverName: string, error: any): string { return localize('arc.connectToMSSqlFailed', "Could not connect to SQL managed instance - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); }
export function connectToPGSqlFailed(serverName: string, error: any): string { return localize('arc.connectToPGSqlFailed', "Could not connect to PostgreSQL Hyperscale - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); }
export function missingExtension(extensionName: string): string { return localize('arc.missingExtension', "The {0} extension is required to view engine settings. Do you wish to install it now?", extensionName); }
export function extensionInstallationFailed(extensionName: string): string { return localize('arc.extensionInstallationFailed', "Failed to install extension {0}.", extensionName); }
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); } export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); } export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); } export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); } export function fetchDatabasesFailed(name: string, error: any): string { return localize('arc.fetchDatabasesFailed', "An unexpected error occurred retrieving the databases for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchEngineSettingsFailed(name: string, error: any): string { return localize('arc.fetchEngineSettingsFailed', "An unexpected error occurred retrieving the engine settings for '{0}'. {1}", name, getErrorMessage(error)); }
export function instanceDeletionWarning(name: string): string { return localize('arc.instanceDeletionWarning', "Warning! Deleting an instance is permanent and cannot be undone. To delete the instance '{0}' type the name '{0}' below to proceed.", name); } export function instanceDeletionWarning(name: string): string { return localize('arc.instanceDeletionWarning', "Warning! Deleting an instance is permanent and cannot be undone. To delete the instance '{0}' type the name '{0}' below to proceed.", name); }
export function invalidInstanceDeletionName(name: string): string { return localize('arc.invalidInstanceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); } export function invalidInstanceDeletionName(name: string): string { return localize('arc.invalidInstanceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); }
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); } export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); } export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error, true)); } export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error, true)); }
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); } export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again."); export const loginFailed = localize('arc.loginFailed', "Error logging into controller - wrong username or password");
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); } export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
export const onlyOneControllerSupported = localize('arc.onlyOneControllerSupported', "Only one controller connection is currently supported at this time. Do you wish to remove the existing connection and add a new one?");
export const noControllersConnected = localize('noControllersConnected', "No Azure Arc controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again"); export const noControllersConnected = localize('noControllersConnected', "No Azure Arc controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again");
export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName); export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName);
export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName); export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName);
export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "Controller Info could not be found with name: {0}", name); export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "Controller Info could not be found with name: {0}", name);
export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName); export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName);
export const clusterContextNotFound = (clusterContext: string) => localize('clusterContextNotFound', "Cluster Context with name: {0} not found in the Kube config file", clusterContext);
export const noCurrentClusterContext = localize('noCurrentClusterContext', "No current cluster context was found in the kube config file");
export const browse = localize('filePicker.browse', "Browse");
export const select = localize('button.label', "Select");
export const noContextFound = (configFile: string) => localize('noContextFound', "No 'contexts' found in the config file: {0}", configFile); export const noContextFound = (configFile: string) => localize('noContextFound', "No 'contexts' found in the config file: {0}", configFile);
export const noCurrentContextFound = (configFile: string) => localize('noCurrentContextFound', "No context is marked as 'current-context' in the config file: {0}", configFile); export const noCurrentContextFound = (configFile: string) => localize('noCurrentContextFound', "No context is marked as 'current-context' in the config file: {0}", configFile);
export const noNameInContext = (configFile: string) => localize('noNameInContext', "No name field was found in a cluster context in the config file: {0}", configFile); export const noNameInContext = (configFile: string) => localize('noNameInContext', "No name field was found in a cluster context in the config file: {0}", configFile);
export const userCancelledError = localize('arc.userCancelledError', "User cancelled the dialog");
export const clusterContextConfigNoLongerValid = (configFile: string, clusterContext: string, error: any) => localize('clusterContextConfigNoLongerValid', "The cluster context information specified by config file: {0} and cluster context: {1} is no longer valid. Error is:\n\t{2}\n Do you want to update this information?", configFile, clusterContext, getErrorMessage(error));

View File

@@ -6,7 +6,8 @@
import { ControllerInfo, ResourceType } from 'arc'; import { ControllerInfo, ResourceType } from 'arc';
import * as azdataExt from 'azdata-ext'; import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { UserCancelledError } from '../common/utils'; import { UserCancelledError } from '../common/api';
import { getCurrentClusterContext, getKubeConfigClusterContexts } from '../common/kubeUtils';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog'; import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
@@ -50,19 +51,42 @@ export class ControllerModel {
this._onInfoUpdated.fire(this._info); this._onInfoUpdated.fire(this._info);
} }
public get azdataAdditionalEnvVars(): azdataExt.AdditionalEnvVars {
return {
'KUBECONFIG': this.info.kubeConfigFilePath,
'KUBECTL_CONTEXT': this.info.kubeClusterContext
};
}
/** /**
* Calls azdata login to set the context to this controller * 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 * @param promptReconnect
*/ */
public async azdataLogin(promptReconnect: boolean = false): Promise<void> { public async acquireAzdataSession(promptReconnect: boolean = false): Promise<azdataExt.AzdataSession> {
// We haven't gotten our password yet or we want to prompt for a reconnect let promptForValidClusterContext: boolean = false;
if (!this._password || promptReconnect) { try {
const contexts = await 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 = ''; this._password = '';
if (this.info.rememberPassword) { if (this.info.rememberPassword) {
// It should be in the credentials store, get it from there // It should be in the credentials store, get it from there
this._password = await this.treeDataProvider.getPassword(this.info); this._password = await this.treeDataProvider.getPassword(this.info);
} }
if (promptReconnect || !this._password) { if (promptReconnect || !this._password || promptForValidClusterContext) {
// No password yet or we want to re-prompt for credentials so prompt for it from the user // No password yet or we want to re-prompt for credentials so prompt for it from the user
const dialog = new ConnectToControllerDialog(this.treeDataProvider); const dialog = new ConnectToControllerDialog(this.treeDataProvider);
dialog.showDialog(this.info, this._password); dialog.showDialog(this.info, this._password);
@@ -70,13 +94,14 @@ export class ControllerModel {
if (model) { if (model) {
await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false); await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false);
this._password = model.password; this._password = model.password;
this._info = model.controllerModel.info;
} else { } else {
throw new UserCancelledError(); throw new UserCancelledError(loc.userCancelledError);
} }
} }
} }
await this._azdataApi.azdata.login(this.info.url, this.info.username, this._password); return this._azdataApi.azdata.acquireSession(this.info.url, this.info.username, this._password, this.azdataAdditionalEnvVars);
} }
/** /**
@@ -91,62 +116,66 @@ export class ControllerModel {
} }
} }
public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise<void> { public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise<void> {
await this.azdataLogin(promptReconnect); const session = await this.acquireAzdataSession(promptReconnect);
const newRegistrations: Registration[] = []; const newRegistrations: Registration[] = [];
await Promise.all([ try {
this._azdataApi.azdata.arc.dc.config.show().then(result => { await Promise.all([
this._controllerConfig = result.result; this._azdataApi.azdata.arc.dc.config.show(this.azdataAdditionalEnvVars, session).then(result => {
this.configLastUpdated = new Date(); this._controllerConfig = result.result;
this._onConfigUpdated.fire(this._controllerConfig); this.configLastUpdated = new Date();
}).catch(err => { this._onConfigUpdated.fire(this._controllerConfig);
// If an error occurs show a message so the user knows something failed but still }).catch(err => {
// fire the event so callers can know to update (e.g. so dashboards don't show the // If an error occurs show a message so the user knows something failed but still
// loading icon forever) // fire the event so callers hooking into this can handle the error (e.g. so dashboards don't show the
if (showErrors) { // loading icon forever)
vscode.window.showErrorMessage(loc.fetchConfigFailed(this.info.name, err)); if (showErrors) {
} vscode.window.showErrorMessage(loc.fetchConfigFailed(this.info.name, err));
this._onConfigUpdated.fire(this._controllerConfig); }
throw err; this._onConfigUpdated.fire(this._controllerConfig);
}), throw err;
this._azdataApi.azdata.arc.dc.endpoint.list().then(result => {
this._endpoints = result.result;
this.endpointsLastUpdated = new Date();
this._onEndpointsUpdated.fire(this._endpoints);
}).catch(err => {
// If an error occurs show a message so the user knows something failed but still
// fire the event so callers can know to update (e.g. so dashboards don't show the
// loading icon forever)
if (showErrors) {
vscode.window.showErrorMessage(loc.fetchEndpointsFailed(this.info.name, err));
}
this._onEndpointsUpdated.fire(this._endpoints);
throw err;
}),
Promise.all([
this._azdataApi.azdata.arc.postgres.server.list().then(result => {
newRegistrations.push(...result.result.map(r => {
return {
instanceName: r.name,
state: r.state,
instanceType: ResourceType.postgresInstances
};
}));
}), }),
this._azdataApi.azdata.arc.sql.mi.list().then(result => { this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars, session).then(result => {
newRegistrations.push(...result.result.map(r => { this._endpoints = result.result;
return { this.endpointsLastUpdated = new Date();
instanceName: r.name, this._onEndpointsUpdated.fire(this._endpoints);
state: r.state, }).catch(err => {
instanceType: ResourceType.sqlManagedInstances // If an error occurs show a message so the user knows something failed but still
}; // fire the event so callers can know to update (e.g. so dashboards don't show the
})); // loading icon forever)
if (showErrors) {
vscode.window.showErrorMessage(loc.fetchEndpointsFailed(this.info.name, err));
}
this._onEndpointsUpdated.fire(this._endpoints);
throw err;
}),
Promise.all([
this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars, session).then(result => {
newRegistrations.push(...result.result.map(r => {
return {
instanceName: r.name,
state: r.state,
instanceType: ResourceType.postgresInstances
};
}));
}),
this._azdataApi.azdata.arc.sql.mi.list(this.azdataAdditionalEnvVars, session).then(result => {
newRegistrations.push(...result.result.map(r => {
return {
instanceName: r.name,
state: r.state,
instanceType: ResourceType.sqlManagedInstances
};
}));
})
]).then(() => {
this._registrations = newRegistrations;
this.registrationsLastUpdated = new Date();
this._onRegistrationsUpdated.fire(this._registrations);
}) })
]).then(() => { ]);
this._registrations = newRegistrations; } finally {
this.registrationsLastUpdated = new Date(); session.dispose();
this._onRegistrationsUpdated.fire(this._registrations); }
})
]);
} }
public get endpoints(): azdataExt.DcEndpointListResult[] { public get endpoints(): azdataExt.DcEndpointListResult[] {

View File

@@ -7,11 +7,11 @@ import { MiaaResourceInfo } from 'arc';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext'; import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { UserCancelledError } from '../common/api';
import { Deferred } from '../common/promise'; import { Deferred } from '../common/promise';
import { createCredentialId, parseIpAndPort, UserCancelledError } from '../common/utils'; import { parseIpAndPort } from '../common/utils';
import { credentialNamespace } from '../constants';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
import { ConnectToSqlDialog } from '../ui/dialogs/connectSqlDialog'; import { ConnectToMiaaSqlDialog } from '../ui/dialogs/connectMiaaDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerModel, Registration } from './controllerModel'; import { ControllerModel, Registration } from './controllerModel';
import { ResourceModel } from './resourceModel'; import { ResourceModel } from './resourceModel';
@@ -22,23 +22,19 @@ export class MiaaModel extends ResourceModel {
private _config: azdataExt.SqlMiShowResult | undefined; private _config: azdataExt.SqlMiShowResult | undefined;
private _databases: DatabaseModel[] = []; private _databases: DatabaseModel[] = [];
// The saved connection information
private _connectionProfile: azdata.IConnectionProfile | undefined = undefined;
// The ID of the active connection used to query the server
private _activeConnectionId: string | undefined = undefined;
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.SqlMiShowResult | undefined>(); private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.SqlMiShowResult | undefined>();
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>(); private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
private readonly _azdataApi: azdataExt.IExtension; private readonly _azdataApi: azdataExt.IExtension;
public onConfigUpdated = this._onConfigUpdated.event; public onConfigUpdated = this._onConfigUpdated.event;
public onDatabasesUpdated = this._onDatabasesUpdated.event; public onDatabasesUpdated = this._onDatabasesUpdated.event;
public configLastUpdated?: Date; public configLastUpdated: Date | undefined;
public databasesLastUpdated?: Date; public databasesLastUpdated: Date | undefined;
private _refreshPromise: Deferred<void> | undefined = undefined; private _refreshPromise: Deferred<void> | undefined = undefined;
constructor(private _controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) { constructor(_controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
super(_miaaInfo, registration); super(_controllerModel, _miaaInfo, registration);
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports; this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
} }
@@ -75,10 +71,11 @@ export class MiaaModel extends ResourceModel {
return this._refreshPromise.promise; return this._refreshPromise.promise;
} }
this._refreshPromise = new Deferred(); this._refreshPromise = new Deferred();
let session: azdataExt.AzdataSession | undefined = undefined;
try { try {
await this._controllerModel.azdataLogin(); session = await this.controllerModel.acquireAzdataSession();
try { try {
const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name); const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, session);
this._config = result.result; this._config = result.result;
this.configLastUpdated = new Date(); this.configLastUpdated = new Date();
this._onConfigUpdated.fire(this._config); this._onConfigUpdated.fire(this._config);
@@ -94,22 +91,16 @@ export class MiaaModel extends ResourceModel {
// If we have an external endpoint configured then fetch the databases now // If we have an external endpoint configured then fetch the databases now
if (this._config.status.externalEndpoint) { if (this._config.status.externalEndpoint) {
this.getDatabases().catch(err => { this.getDatabases(false).catch(_err => {
// If an error occurs show a message so the user knows something failed but still // If an error occurs still fire the event so callers can know to
// fire the event so callers can know to update (e.g. so dashboards don't show the // update (e.g. so dashboards don't show the loading icon forever)
// loading icon forever)
if (err instanceof UserCancelledError) { this.databasesLastUpdated = undefined;
vscode.window.showWarningMessage(loc.connectionRequired);
} else {
vscode.window.showErrorMessage(loc.fetchDatabasesFailed(this.info.name, err));
}
this.databasesLastUpdated = new Date();
this._onDatabasesUpdated.fire(this._databases); this._onDatabasesUpdated.fire(this._databases);
throw err;
}); });
} else { } else {
// Otherwise just fire the event so dashboards can update appropriately // Otherwise just fire the event so dashboards can update appropriately
this.databasesLastUpdated = new Date(); this.databasesLastUpdated = undefined;
this._onDatabasesUpdated.fire(this._databases); this._onDatabasesUpdated.fire(this._databases);
} }
@@ -118,52 +109,47 @@ export class MiaaModel extends ResourceModel {
this._refreshPromise.reject(err); this._refreshPromise.reject(err);
throw err; throw err;
} finally { } finally {
session?.dispose();
this._refreshPromise = undefined; this._refreshPromise = undefined;
} }
} }
private async getDatabases(): Promise<void> { public async getDatabases(promptForConnection: boolean = true): Promise<void> {
await this.getConnectionProfile(); if (!this._connectionProfile) {
if (this._connectionProfile) { await this.getConnectionProfile(promptForConnection);
// We haven't connected yet so do so now and then store the ID for the active connection
if (!this._activeConnectionId) {
const result = await azdata.connection.connect(this._connectionProfile, false, false);
if (!result.connected) {
throw new Error(result.errorMessage);
}
this._activeConnectionId = result.connectionId;
}
const provider = azdata.dataprotocol.getProvider<azdata.MetadataProvider>(this._connectionProfile.providerName, azdata.DataProviderType.MetadataProvider);
const ownerUri = await azdata.connection.getUriForConnection(this._activeConnectionId);
const databases = await provider.getDatabases(ownerUri);
if (!databases) {
throw new Error('Could not fetch databases');
}
if (databases.length > 0 && typeof (databases[0]) === 'object') {
this._databases = (<azdata.DatabaseInfo[]>databases).map(db => { return { name: db.options['name'], status: db.options['state'] }; });
} else {
this._databases = (<string[]>databases).map(db => { return { name: db, status: '-' }; });
}
this.databasesLastUpdated = new Date();
this._onDatabasesUpdated.fire(this._databases);
} }
// We haven't connected yet so do so now and then store the ID for the active connection
if (!this._activeConnectionId) {
const result = await azdata.connection.connect(this._connectionProfile!, false, false);
if (!result.connected) {
throw new Error(result.errorMessage);
}
this._activeConnectionId = result.connectionId;
}
const provider = azdata.dataprotocol.getProvider<azdata.MetadataProvider>(this._connectionProfile!.providerName, azdata.DataProviderType.MetadataProvider);
const ownerUri = await azdata.connection.getUriForConnection(this._activeConnectionId);
const databases = await provider.getDatabases(ownerUri);
if (!databases) {
throw new Error('Could not fetch databases');
}
if (databases.length > 0 && typeof (databases[0]) === 'object') {
this._databases = (<azdata.DatabaseInfo[]>databases).map(db => { return { name: db.options['name'], status: db.options['state'] }; });
} else {
this._databases = (<string[]>databases).map(db => { return { name: db, status: '-' }; });
}
this.databasesLastUpdated = new Date();
this._onDatabasesUpdated.fire(this._databases);
} }
/**
* Loads the saved connection profile associated with this model. Will prompt for one if
* we don't have one or can't find it (it was deleted)
*/
private async getConnectionProfile(): Promise<void> {
if (this._connectionProfile) {
return;
}
protected createConnectionProfile(): azdata.IConnectionProfile {
const ipAndPort = parseIpAndPort(this.config?.status.externalEndpoint || ''); const ipAndPort = parseIpAndPort(this.config?.status.externalEndpoint || '');
let connectionProfile: azdata.IConnectionProfile | undefined = { return {
serverName: `${ipAndPort.ip},${ipAndPort.port}`, serverName: `${ipAndPort.ip},${ipAndPort.port}`,
databaseName: '', databaseName: '',
authenticationType: 'SqlLogin', authenticationType: 'SqlLogin',
providerName: 'MSSQL', providerName: loc.miaaProviderName,
connectionName: '', connectionName: '',
userName: this._miaaInfo.userName || '', userName: this._miaaInfo.userName || '',
password: '', password: '',
@@ -174,48 +160,23 @@ export class MiaaModel extends ResourceModel {
groupId: undefined, groupId: undefined,
options: {} options: {}
}; };
}
// If we have the ID stored then try to retrieve the password from previous connections protected async promptForConnection(connectionProfile: azdata.IConnectionProfile): Promise<void> {
if (this.info.connectionId) { const connectToSqlDialog = new ConnectToMiaaSqlDialog(this.controllerModel, this);
try { connectToSqlDialog.showDialog(loc.connectToMSSql(this.info.name), connectionProfile);
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace); let profileFromDialog = await connectToSqlDialog.waitForClose();
const credentials = await credentialProvider.readCredential(createCredentialId(this._controllerModel.info.id, this.info.resourceType, this.info.name));
if (credentials.password) {
// Try to connect to verify credentials are still valid
connectionProfile.password = credentials.password;
// If we don't have a username for some reason then just continue on and we'll prompt for the username below
if (connectionProfile.userName) {
const result = await azdata.connection.connect(connectionProfile, false, false);
if (!result.connected) {
vscode.window.showErrorMessage(loc.connectToSqlFailed(connectionProfile.serverName, result.errorMessage));
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
connectToSqlDialog.showDialog(connectionProfile);
connectionProfile = await connectToSqlDialog.waitForClose();
}
}
}
} catch (err) {
console.warn(`Unexpected error fetching password for MIAA instance ${err}`);
// ignore - something happened fetching the password so just reprompt
}
}
if (!connectionProfile?.userName || !connectionProfile?.password) { if (profileFromDialog) {
// Need to prompt user for password since we don't have one stored this.updateConnectionProfile(profileFromDialog);
const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this);
connectToSqlDialog.showDialog(connectionProfile);
connectionProfile = await connectToSqlDialog.waitForClose();
}
if (connectionProfile) {
this.updateConnectionProfile(connectionProfile);
} else { } else {
throw new UserCancelledError(); throw new UserCancelledError();
} }
} }
private async updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void> { protected async updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void> {
this._connectionProfile = connectionProfile; this._connectionProfile = connectionProfile;
this._activeConnectionId = connectionProfile.id;
this.info.connectionId = connectionProfile.id; this.info.connectionId = connectionProfile.id;
this._miaaInfo.userName = connectionProfile.userName; this._miaaInfo.userName = connectionProfile.userName;
await this._treeDataProvider.saveControllers(); await this._treeDataProvider.saveControllers();

View File

@@ -3,27 +3,45 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ResourceInfo } from 'arc'; import { PGResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext'; import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
import { ConnectToPGSqlDialog } from '../ui/dialogs/connectPGDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerModel, Registration } from './controllerModel'; import { ControllerModel, Registration } from './controllerModel';
import { parseIpAndPort } from '../common/utils';
import { UserCancelledError } from '../common/api';
import { ResourceModel } from './resourceModel'; import { ResourceModel } from './resourceModel';
import { Deferred } from '../common/promise'; import { Deferred } from '../common/promise';
import { parseIpAndPort } from '../common/utils';
export type EngineSettingsModel = {
parameterName: string | undefined,
value: string | undefined,
description: string | undefined,
min: string | undefined,
max: string | undefined,
options: string | undefined,
type: string | undefined
};
export class PostgresModel extends ResourceModel { export class PostgresModel extends ResourceModel {
private _config?: azdataExt.PostgresServerShowResult; private _config?: azdataExt.PostgresServerShowResult;
public _engineSettings: EngineSettingsModel[] = [];
private readonly _azdataApi: azdataExt.IExtension; private readonly _azdataApi: azdataExt.IExtension;
private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>(); private readonly _onConfigUpdated = new vscode.EventEmitter<azdataExt.PostgresServerShowResult>();
public readonly _onEngineSettingsUpdated = new vscode.EventEmitter<EngineSettingsModel[]>();
public onConfigUpdated = this._onConfigUpdated.event; public onConfigUpdated = this._onConfigUpdated.event;
public onEngineSettingsUpdated = this._onEngineSettingsUpdated.event;
public configLastUpdated?: Date; public configLastUpdated?: Date;
public engineSettingsLastUpdated?: Date;
private _refreshPromise?: Deferred<void>; private _refreshPromise?: Deferred<void>;
constructor(private _controllerModel: ControllerModel, info: ResourceInfo, registration: Registration) { constructor(_controllerModel: ControllerModel, private _pgInfo: PGResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) {
super(info, registration); super(_controllerModel, _pgInfo, registration);
this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports; this._azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
} }
@@ -89,10 +107,10 @@ export class PostgresModel extends ResourceModel {
return this._refreshPromise.promise; return this._refreshPromise.promise;
} }
this._refreshPromise = new Deferred(); this._refreshPromise = new Deferred();
let session: azdataExt.AzdataSession | undefined = undefined;
try { try {
await this._controllerModel.azdataLogin(); session = await this.controllerModel.acquireAzdataSession();
this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name)).result; this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name, this.controllerModel.azdataAdditionalEnvVars, session)).result;
this.configLastUpdated = new Date(); this.configLastUpdated = new Date();
this._onConfigUpdated.fire(this._config); this._onConfigUpdated.fire(this._config);
this._refreshPromise.resolve(); this._refreshPromise.resolve();
@@ -100,7 +118,100 @@ export class PostgresModel extends ResourceModel {
this._refreshPromise.reject(err); this._refreshPromise.reject(err);
throw err; throw err;
} finally { } finally {
session?.dispose();
this._refreshPromise = undefined; this._refreshPromise = undefined;
} }
} }
public async getEngineSettings(): Promise<void> {
if (!this._connectionProfile) {
await this.getConnectionProfile();
}
// We haven't connected yet so do so now and then store the ID for the active connection
if (!this._activeConnectionId) {
const result = await azdata.connection.connect(this._connectionProfile!, false, false);
if (!result.connected) {
throw new Error(result.errorMessage);
}
this._activeConnectionId = result.connectionId;
}
const provider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this._connectionProfile!.providerName, azdata.DataProviderType.QueryProvider);
const ownerUri = await azdata.connection.getUriForConnection(this._activeConnectionId);
const engineSettings = await provider.runQueryAndReturn(ownerUri, 'select name, setting, short_desc,min_val, max_val, enumvals, vartype from pg_settings');
if (!engineSettings) {
throw new Error('Could not fetch engine settings');
}
const skippedEngineSettings: String[] = [
'archive_command', 'archive_timeout', 'log_directory', 'log_file_mode', 'log_filename', 'restore_command',
'shared_preload_libraries', 'synchronous_commit', 'ssl', 'unix_socket_permissions', 'wal_level'
];
this._engineSettings = [];
engineSettings.rows.forEach(row => {
let rowValues = row.map(c => c.displayValue);
let name = rowValues.shift();
if (!skippedEngineSettings.includes(name!)) {
let result: EngineSettingsModel = {
parameterName: name,
value: rowValues.shift(),
description: rowValues.shift(),
min: rowValues.shift(),
max: rowValues.shift(),
options: rowValues.shift(),
type: rowValues.shift()
};
this._engineSettings.push(result);
}
});
this.engineSettingsLastUpdated = new Date();
this._onEngineSettingsUpdated.fire(this._engineSettings);
}
protected createConnectionProfile(): azdata.IConnectionProfile {
const ipAndPort = parseIpAndPort(this.config?.status.externalEndpoint || '');
return {
serverName: `${ipAndPort.ip},${ipAndPort.port}`,
databaseName: '',
authenticationType: 'SqlLogin',
providerName: loc.postgresProviderName,
connectionName: '',
userName: this._pgInfo.userName || '',
password: '',
savePassword: true,
groupFullName: undefined,
saveProfile: true,
id: '',
groupId: undefined,
options: {
host: `${ipAndPort.ip}`,
port: `${ipAndPort.port}`,
}
};
}
protected async promptForConnection(connectionProfile: azdata.IConnectionProfile): Promise<void> {
const connectToSqlDialog = new ConnectToPGSqlDialog(this.controllerModel, this);
connectToSqlDialog.showDialog(loc.connectToPGSql(this.info.name), connectionProfile);
let profileFromDialog = await connectToSqlDialog.waitForClose();
if (profileFromDialog) {
this.updateConnectionProfile(profileFromDialog);
} else {
throw new UserCancelledError();
}
}
protected async updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void> {
this._connectionProfile = connectionProfile;
this.info.connectionId = connectionProfile.id;
this._pgInfo.userName = connectionProfile.userName;
await this._treeDataProvider.saveControllers();
}
} }

View File

@@ -4,15 +4,23 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ResourceInfo } from 'arc'; import { ResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Registration } from './controllerModel'; import { ControllerModel, Registration } from './controllerModel';
import { createCredentialId } from '../common/utils';
import { credentialNamespace } from '../constants';
export abstract class ResourceModel { export abstract class ResourceModel {
private readonly _onRegistrationUpdated = new vscode.EventEmitter<Registration>(); private readonly _onRegistrationUpdated = new vscode.EventEmitter<Registration>();
public onRegistrationUpdated = this._onRegistrationUpdated.event; public onRegistrationUpdated = this._onRegistrationUpdated.event;
constructor(public info: ResourceInfo, private _registration: Registration) { } // The saved connection information
protected _connectionProfile: azdata.IConnectionProfile | undefined = undefined;
// The ID of the active connection used to query the server
protected _activeConnectionId: string | undefined = undefined;
constructor(public readonly controllerModel: ControllerModel, public info: ResourceInfo, private _registration: Registration) { }
public get registration(): Registration { public get registration(): Registration {
return this._registration; return this._registration;
@@ -23,5 +31,57 @@ export abstract class ResourceModel {
this._onRegistrationUpdated.fire(this._registration); this._onRegistrationUpdated.fire(this._registration);
} }
/**
* Loads the saved connection profile associated with this model. Will prompt for one if
* we don't have one or can't find it (it was deleted)
*/
protected async getConnectionProfile(promptForConnection: boolean = true): Promise<void> {
let connectionProfile: azdata.IConnectionProfile | undefined = this.createConnectionProfile();
// If we have the ID stored then try to retrieve the password from previous connections
if (this.info.connectionId) {
try {
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
const credentials = await credentialProvider.readCredential(createCredentialId(this.controllerModel.info.id, this.info.resourceType, this.info.name));
if (credentials.password) {
// Try to connect to verify credentials are still valid
connectionProfile.password = credentials.password;
// If we don't have a username for some reason then just continue on and we'll prompt for the username below
if (connectionProfile.userName) {
const result = await azdata.connection.connect(connectionProfile, false, false);
if (!result.connected) {
if (promptForConnection) {
await this.promptForConnection(connectionProfile);
} else {
throw new Error(result.errorMessage);
}
} else {
this.updateConnectionProfile(connectionProfile);
}
}
}
} catch (err) {
console.warn(`Unexpected error fetching password for instance ${err}`);
// ignore - something happened fetching the password so just reprompt
}
}
if (!connectionProfile?.userName || !connectionProfile?.password) {
if (promptForConnection) {
// Need to prompt user for password since we don't have one stored
await this.promptForConnection(connectionProfile);
} else {
throw new Error('Missing username/password for connection profile');
}
}
}
public abstract refresh(): Promise<void>; public abstract refresh(): Promise<void>;
protected abstract createConnectionProfile(): azdata.IConnectionProfile;
protected abstract promptForConnection(connectionProfile: azdata.IConnectionProfile): Promise<void>;
protected abstract updateConnectionProfile(connectionProfile: azdata.IConnectionProfile): Promise<void>;
} }

View File

@@ -7,7 +7,6 @@ import * as arc from 'arc';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as rd from 'resource-deployment'; import * as rd from 'resource-deployment';
import { getControllerPassword, getRegisteredDataControllers, reacquireControllerPassword } from '../common/api'; import { getControllerPassword, getRegisteredDataControllers, reacquireControllerPassword } from '../common/api';
import { CacheManager } from '../common/cacheManager';
import { throwUnless } from '../common/utils'; import { throwUnless } from '../common/utils';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
@@ -16,11 +15,10 @@ import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
* Class that provides options sources for an Arc Data Controller * Class that provides options sources for an Arc Data Controller
*/ */
export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourceProvider { export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourceProvider {
private _cacheManager = new CacheManager<string, string>(); readonly id = 'arc.controllers';
readonly optionsSourceId = 'arc.controllers';
constructor(private _treeProvider: AzureArcTreeDataProvider) { } constructor(private _treeProvider: AzureArcTreeDataProvider) { }
async getOptions(): Promise<string[] | azdata.CategoryValue[]> { public async getOptions(): Promise<string[] | azdata.CategoryValue[]> {
const controllers = await getRegisteredDataControllers(this._treeProvider); const controllers = await getRegisteredDataControllers(this._treeProvider);
throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected); throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected);
return controllers.map(ci => { return controllers.map(ci => {
@@ -28,24 +26,19 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
}); });
} }
private async retrieveVariable(key: string): Promise<string> { public async getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
const [variableName, controllerLabel] = JSON.parse(key);
const controller = (await getRegisteredDataControllers(this._treeProvider)).find(ci => ci.label === controllerLabel); const controller = (await getRegisteredDataControllers(this._treeProvider)).find(ci => ci.label === controllerLabel);
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel)); throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
switch (variableName) { switch (variableName) {
case 'endpoint': return controller.info.url; case 'endpoint': return controller.info.url;
case 'username': return controller.info.username; case 'username': return controller.info.username;
case 'kubeConfig': return controller.info.kubeConfigFilePath;
case 'clusterContext': return controller.info.kubeClusterContext;
case 'password': return this.getPassword(controller); case 'password': return this.getPassword(controller);
default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName)); default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
} }
} }
getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
// capture 'this' in an arrow function object
const retrieveVariable = (key: string) => this.retrieveVariable(key);
return this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), retrieveVariable);
}
private async getPassword(controller: arc.DataController): Promise<string> { private async getPassword(controller: arc.DataController): Promise<string> {
let password = await getControllerPassword(this._treeProvider, controller.info); let password = await getControllerPassword(this._treeProvider, controller.info);
if (!password) { if (!password) {
@@ -55,10 +48,12 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro
return password; return password;
} }
getIsPassword(variableName: string): boolean { public getIsPassword(variableName: string): boolean {
switch (variableName) { switch (variableName) {
case 'endpoint': return false; case 'endpoint': return false;
case 'username': return false; case 'username': return false;
case 'kubeConfig': return false;
case 'clusterContext': return false;
case 'password': return true; case 'password': return true;
default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName)); default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
} }

View File

@@ -55,7 +55,7 @@ describe('KubeUtils', function (): void {
}); });
it('throws error when unable to load config file', async () => { it('throws error when unable to load config file', async () => {
const error = new Error('unknown error accessing file'); const error = new Error('unknown error accessing file');
sinon.stub(yamljs, 'load').throws(error); //erroring config file load sinon.stub(yamljs, 'load').throws(error); // simulate an error thrown from config file load
((await tryExecuteAction(() => getKubeConfigClusterContexts(configFile))).error).should.equal(error, `test: getKubeConfigClusterContexts failed`); ((await tryExecuteAction(() => getKubeConfigClusterContexts(configFile))).error).should.equal(error, `test: getKubeConfigClusterContexts failed`);
}); });
}); });

View File

@@ -7,7 +7,7 @@ import { Deferred } from '../../common/promise';
describe('Deferred', () => { describe('Deferred', () => {
it('Then should be called upon resolution', function (done): void { it('Then should be called upon resolution', function (done): void {
const deferred = new Deferred(); const deferred = new Deferred<void>();
deferred.then(() => { deferred.then(() => {
done(); done();
}); });

View File

@@ -49,7 +49,9 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
replaceEngineSettings?: boolean, replaceEngineSettings?: boolean,
workers?: number workers?: number
}, },
_additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); } _engineVersion?: string,
_additionalEnvVars?: azdataExt.AdditionalEnvVars
): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
} }
}, },
sql: { sql: {
@@ -73,9 +75,12 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
getPath(): Promise<string> { getPath(): Promise<string> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<any>> { login(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataOutput<void>> {
return <any>undefined; return <any>undefined;
} }
acquireSession(_endpoint: string, _username: string, _password: string): Promise<azdataExt.AzdataSession> {
return Promise.resolve({ dispose: () => { } });
}
version(): Promise<azdataExt.AzdataOutput<string>> { version(): Promise<azdataExt.AzdataOutput<string>> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }

View File

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

View File

@@ -1,91 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
export class FakeRadioButton implements azdata.RadioButtonComponent {
private _onDidClickEmitter = new vscode.EventEmitter<any>();
onDidClick = this._onDidClickEmitter.event;
constructor(props: azdata.RadioButtonProperties) {
this.label = props.label;
this.value = props.value;
this.checked = props.checked;
this.enabled = props.enabled;
}
//#region RadioButtonProperties implementation
label?: string;
value?: string;
checked?: boolean;
//#endregion
click() {
this.checked = true;
this._onDidClickEmitter.fire(this);
}
//#region Component Implementation
id: string = '';
updateProperties(_properties: { [key: string]: any; }): Thenable<void> {
throw new Error('Method not implemented.');
}
updateProperty(_key: string, _value: any): Thenable<void> {
throw new Error('Method not implemented.');
}
updateCssStyles(_cssStyles: { [key: string]: string; }): Thenable<void> {
throw new Error('Method not implemented.');
}
onValidityChanged: vscode.Event<boolean> = <vscode.Event<boolean>>{};
valid: boolean = false;
validate(): Thenable<boolean> {
throw new Error('Method not implemented.');
}
focus(): Thenable<void> {
throw new Error('Method not implemented.');
}
ariaHidden?: boolean | undefined;
//#endregion
//#region ComponentProperties Implementation
height?: number | string;
width?: number | string;
/**
* The position CSS property. Empty by default.
* This is particularly useful if laying out components inside a FlexContainer and
* the size of the component is meant to be a fixed size. In this case the position must be
* set to 'absolute', with the parent FlexContainer having 'relative' position.
* Without this the component will fail to correctly size itself
*/
position?: azdata.PositionType;
/**
* Whether the component is enabled in the DOM
*/
enabled?: boolean;
/**
* Corresponds to the display CSS property for the element
*/
display?: azdata.DisplayType;
/**
* Corresponds to the aria-label accessibility attribute for this component
*/
ariaLabel?: string;
/**
* Corresponds to the role accessibility attribute for this component
*/
ariaRole?: string;
/**
* Corresponds to the aria-selected accessibility attribute for this component
*/
ariaSelected?: boolean;
/**
* Matches the CSS style key and its available values.
*/
CSSStyles?: { [key: string]: string };
//#endregion
}

View File

@@ -11,7 +11,9 @@ import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { UserCancelledError } from '../../common/utils'; import * as loc from '../../localizedConstants';
import * as kubeUtils from '../../common/kubeUtils';
import { UserCancelledError } from '../../common/api';
import { ControllerModel } from '../../models/controllerModel'; import { ControllerModel } from '../../models/controllerModel';
import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog'; import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
@@ -33,13 +35,15 @@ describe('ControllerModel', function (): void {
beforeEach(function (): void { beforeEach(function (): void {
sinon.stub(ConnectToControllerDialog.prototype, 'showDialog'); sinon.stub(ConnectToControllerDialog.prototype, 'showDialog');
sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.yes);
}); });
it('Rejected with expected error when user cancels', async function (): Promise<void> { it('Rejected with expected error when user cancels', async function (): Promise<void> {
// Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel" // Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined)); sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError()); await should(model.acquireAzdataSession()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
}); });
it('Reads password from cred store', async function (): Promise<void> { it('Reads password from cred store', async function (): Promise<void> {
@@ -54,17 +58,17 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>(); const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>(); const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined)); azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin(); await model.acquireAzdataSession();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
}); });
it('Prompt for password when not in cred store', async function (): Promise<void> { it('Prompt for password when not in cred store', async function (): Promise<void> {
const password = 'password123'; const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return empty password // Set up cred store to return empty password
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>(); const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
@@ -75,22 +79,22 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>(); const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>(); const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined)); azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our password // Set up dialog to return new model with our password
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin(); await model.acquireAzdataSession();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
}); });
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> { it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return a password to start with // Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>(); const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' })); credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
@@ -100,23 +104,23 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>(); const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>(); const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined)); azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt // Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin(true); await model.acquireAzdataSession(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called'); should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
}); });
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> { it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
// Set up cred store to return a password to start with // Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>(); const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' })); credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
@@ -126,20 +130,20 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>(); const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>(); const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined)); azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt // Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// Set up original model with a password // Set up original model with a password
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword'); const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword');
await model.azdataLogin(true); await model.acquireAzdataSession(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called'); should(waitForCloseStub.called).be.true('waitForClose should have been called');
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); azdataMock.verify(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once());
}); });
it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> { it('Model values are updated correctly when modified during reconnect', async function (): Promise<void> {
@@ -154,7 +158,7 @@ describe('ControllerModel', function (): void {
const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>(); const azdataExtApiMock = TypeMoq.Mock.ofType<azdataExt.IExtension>();
const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>(); const azdataMock = TypeMoq.Mock.ofType<azdataExt.IAzdataApi>();
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined)); azdataMock.setup(x => x.acquireSession(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
@@ -165,6 +169,8 @@ describe('ControllerModel', function (): void {
{ {
id: uuid(), id: uuid(),
url: '127.0.0.1', url: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'admin', username: 'admin',
name: 'arc', name: 'arc',
rememberPassword: false, rememberPassword: false,
@@ -177,6 +183,8 @@ describe('ControllerModel', function (): void {
const newInfo: ControllerInfo = { const newInfo: ControllerInfo = {
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
url: 'newUrl', url: 'newUrl',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'newUser', username: 'newUser',
name: 'newName', name: 'newName',
rememberPassword: true, rememberPassword: true,
@@ -191,10 +199,11 @@ describe('ControllerModel', function (): void {
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve( const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(
{ controllerModel: newModel, password: newPassword })); { controllerModel: newModel, password: newPassword }));
await model.azdataLogin(true); await model.acquireAzdataSession(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called'); should(waitForCloseStub.called).be.true('waitForClose should have been called');
should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node'); should((await treeDataProvider.getChildren()).length).equal(1, 'Tree Data provider should still only have 1 node');
should(model.info).deepEqual(newInfo, 'Model info should have been updated'); should(model.info).deepEqual(newInfo, 'Model info should have been updated');
}); });
}); });

View File

@@ -3,66 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
export function createModelViewMock() {
const mockModelBuilder = TypeMoq.Mock.ofType<azdata.ModelBuilder>();
const mockTextBuilder = setupMockComponentBuilder<azdata.TextComponent, azdata.TextComponentProperties>();
const mockInputBoxBuilder = setupMockComponentBuilder<azdata.InputBoxComponent, azdata.InputBoxProperties>();
const mockRadioButtonBuilder = setupMockComponentBuilder<azdata.RadioButtonComponent, azdata.RadioButtonProperties>();
const mockDivBuilder = setupMockContainerBuilder<azdata.DivContainer, azdata.DivContainerProperties, azdata.DivBuilder>();
const mockLoadingBuilder = setupMockLoadingBuilder();
mockModelBuilder.setup(b => b.loadingComponent()).returns(() => mockLoadingBuilder.object);
mockModelBuilder.setup(b => b.text()).returns(() => mockTextBuilder.object);
mockModelBuilder.setup(b => b.inputBox()).returns(() => mockInputBoxBuilder.object);
mockModelBuilder.setup(b => b.radioButton()).returns(() => mockRadioButtonBuilder.object);
mockModelBuilder.setup(b => b.divContainer()).returns(() => mockDivBuilder.object);
const mockModelView = TypeMoq.Mock.ofType<azdata.ModelView>();
mockModelView.setup(mv => mv.modelBuilder).returns(() => mockModelBuilder.object);
return { mockModelView, mockModelBuilder, mockTextBuilder, mockInputBoxBuilder, mockRadioButtonBuilder, mockDivBuilder };
}
function setupMockLoadingBuilder(
loadingBuilderGetter?: (item: azdata.Component) => azdata.LoadingComponentBuilder,
mockLoadingBuilder?: TypeMoq.IMock<azdata.LoadingComponentBuilder>
): TypeMoq.IMock<azdata.LoadingComponentBuilder> {
mockLoadingBuilder = mockLoadingBuilder ?? setupMockComponentBuilder<azdata.LoadingComponent, azdata.LoadingComponentProperties, azdata.LoadingComponentBuilder>();
let item: azdata.Component;
mockLoadingBuilder.setup(b => b.withItem(TypeMoq.It.isAny())).callback((_item) => item = _item).returns(() => loadingBuilderGetter ? loadingBuilderGetter(item) : mockLoadingBuilder!.object);
return mockLoadingBuilder;
}
export function setupMockComponentBuilder<T extends azdata.Component, P extends azdata.ComponentProperties, B extends azdata.ComponentBuilder<T, P> = azdata.ComponentBuilder<T, P>>(
componentGetter?: (props: P) => T,
mockComponentBuilder?: TypeMoq.IMock<B>,
): TypeMoq.IMock<B> {
mockComponentBuilder = mockComponentBuilder ?? TypeMoq.Mock.ofType<B>();
const returnComponent = TypeMoq.Mock.ofType<T>();
// Need to setup 'then' for when a mocked object is resolved otherwise the test will hang : https://github.com/florinn/typemoq/issues/66
returnComponent.setup((x: any) => x.then).returns(() => { });
let compProps: P;
mockComponentBuilder.setup(b => b.withProperties(TypeMoq.It.isAny())).callback((props: P) => compProps = props).returns(() => mockComponentBuilder!.object);
mockComponentBuilder.setup(b => b.component()).returns(() => {
return componentGetter ? componentGetter(compProps) : Object.assign<T, P>(Object.assign({}, returnComponent.object), compProps);
});
// For now just have these be passthrough - can hook up additional functionality later if needed
mockComponentBuilder.setup(b => b.withValidation(TypeMoq.It.isAny())).returns(() => mockComponentBuilder!.object);
return mockComponentBuilder;
}
export function setupMockContainerBuilder<T extends azdata.Container<any, any>, P extends azdata.ComponentProperties, B extends azdata.ContainerBuilder<T, any, any, any> = azdata.ContainerBuilder<T, any, any, any>>(
mockContainerBuilder?: TypeMoq.IMock<B>
): TypeMoq.IMock<B> {
mockContainerBuilder = mockContainerBuilder ?? setupMockComponentBuilder<T, P, B>();
// For now just have these be passthrough - can hook up additional functionality later if needed
mockContainerBuilder.setup(b => b.withItems(TypeMoq.It.isAny(), undefined)).returns(() => mockContainerBuilder!.object);
mockContainerBuilder.setup(b => b.withLayout(TypeMoq.It.isAny())).returns(() => mockContainerBuilder!.object);
return mockContainerBuilder;
}
export class MockInputBox implements vscode.InputBox { export class MockInputBox implements vscode.InputBox {
private _value: string = ''; private _value: string = '';
public get value(): string { public get value(): string {
@@ -77,17 +19,17 @@ export class MockInputBox implements vscode.InputBox {
placeholder: string | undefined; placeholder: string | undefined;
password: boolean = false; password: boolean = false;
private _onDidChangeValueCallback: ((e: string) => any) | undefined = undefined; private _onDidChangeValueCallback: ((e: string) => any) | undefined = undefined;
onDidChangeValue: vscode.Event<string> = (listener) => { onDidChangeValue: vscode.Event<string> = (listener: (value: string) => void) => {
this._onDidChangeValueCallback = listener; this._onDidChangeValueCallback = listener;
return new vscode.Disposable(() => { }); return new vscode.Disposable(() => { });
}; };
private _onDidAcceptCallback: ((e: void) => any) | undefined = undefined; private _onDidAcceptCallback: ((e: void) => any) | undefined = undefined;
public onDidAccept: vscode.Event<void> = (listener) => { public onDidAccept: vscode.Event<void> = (listener: () => void) => {
this._onDidAcceptCallback = listener; this._onDidAcceptCallback = listener;
return new vscode.Disposable(() => { }); return new vscode.Disposable(() => { });
}; };
buttons: readonly vscode.QuickInputButton[] = []; buttons: readonly vscode.QuickInputButton[] = [];
onDidTriggerButton: vscode.Event<vscode.QuickInputButton> = (_) => { return new vscode.Disposable(() => { }); }; onDidTriggerButton: vscode.Event<vscode.QuickInputButton> = () => { return new vscode.Disposable(() => { }); };
prompt: string | undefined; prompt: string | undefined;
validationMessage: string | undefined; validationMessage: string | undefined;
title: string | undefined; title: string | undefined;
@@ -104,7 +46,7 @@ export class MockInputBox implements vscode.InputBox {
} }
} }
private _onDidHideCallback: ((e: void) => any) | undefined = undefined; private _onDidHideCallback: ((e: void) => any) | undefined = undefined;
onDidHide: vscode.Event<void> = (listener) => { onDidHide: vscode.Event<void> = (listener: () => void) => {
this._onDidHideCallback = listener; this._onDidHideCallback = listener;
return new vscode.Disposable(() => { }); return new vscode.Disposable(() => { });
}; };

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as should from 'should';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { Deferred } from '../../../common/promise';
import { FilePicker } from '../../../ui/components/filePicker';
import { createModelViewMock } from 'azdata-test/out/mocks/modelView/modelViewMock';
import { StubButton } from 'azdata-test/out/stubs/modelView/stubButton';
let filePicker: FilePicker;
const initialPath = path.join('path', 'to', '.kube','config');
const newFileUri = vscode.Uri.file(path.join('path', 'to', 'new', '.kube', 'config'));
describe('filePicker', function (): void {
beforeEach(async () => {
const { modelBuilderMock } = createModelViewMock();
filePicker = new FilePicker(modelBuilderMock.object, initialPath, (_disposable) => { });
});
afterEach(() => {
sinon.restore();
});
it('browse Button chooses new FilePath', async () => {
should(filePicker.filePathInputBox.value).should.not.be.undefined();
filePicker.value!.should.equal(initialPath);
filePicker.component().items.length.should.equal(2, 'Filepicker container should have two components');
const deferred = new Deferred<void>();
sinon.stub(vscode.window, 'showOpenDialog').callsFake(async (_options) => {
deferred.resolve();
return [newFileUri];
});
(filePicker.filePickerButton as StubButton).click();
await deferred;
filePicker.value!.should.equal(newFileUri.fsPath);
});
describe('getters and setters', async () => {
it('component getter', () => {
should(filePicker.component()).not.be.undefined();
});
});
});

View File

@@ -7,41 +7,25 @@ import * as azdata from 'azdata';
import * as should from 'should'; import * as should from 'should';
import { getErrorMessage } from '../../../common/utils'; import { getErrorMessage } from '../../../common/utils';
import { RadioOptionsGroup, RadioOptionsInfo } from '../../../ui/components/radioOptionsGroup'; import { RadioOptionsGroup, RadioOptionsInfo } from '../../../ui/components/radioOptionsGroup';
import { FakeRadioButton } from '../../mocks/fakeRadioButton'; import { createModelViewMock } from 'azdata-test/out/mocks/modelView/modelViewMock';
import { setupMockComponentBuilder, createModelViewMock } from '../../stubs'; import { StubRadioButton } from 'azdata-test/out/stubs/modelView/stubRadioButton';
const loadingError = new Error('Error loading options'); const loadingError = new Error('Error loading options');
const radioOptionsInfo = <RadioOptionsInfo>{ const radioOptionsInfo: RadioOptionsInfo = {
values: [ values: [
'value1', 'value1',
'value2' 'value2'
], ],
defaultValue: 'value2' defaultValue: 'value2'
}; };
const divItems: azdata.Component[] = [];
let radioOptionsGroup: RadioOptionsGroup;
let radioOptionsGroup: RadioOptionsGroup;
describe('radioOptionsGroup', function (): void { describe('radioOptionsGroup', function (): void {
beforeEach(async () => { beforeEach(async () => {
const { mockModelView, mockRadioButtonBuilder, mockDivBuilder } = createModelViewMock(); const { modelBuilderMock } = createModelViewMock();
mockRadioButtonBuilder.reset(); // reset any previous mock so that we can set our own. radioOptionsGroup = new RadioOptionsGroup(modelBuilderMock.object, (_disposable) => { });
setupMockComponentBuilder<azdata.RadioButtonComponent, azdata.RadioButtonProperties>(
(props) => new FakeRadioButton(props),
mockRadioButtonBuilder,
);
mockDivBuilder.reset(); // reset previous setups so new setups we are about to create will replace the setups instead creating a recording chain
// create new setups for the DivContainer with custom behavior
setupMockComponentBuilder<azdata.DivContainer, azdata.DivContainerProperties, azdata.DivBuilder>(
() => <azdata.DivContainer>{
addItem: (item) => { divItems.push(item); },
clearItems: () => { divItems.length = 0; },
get items() { return divItems; },
},
mockDivBuilder
);
radioOptionsGroup = new RadioOptionsGroup(mockModelView.object, (_disposable) => { });
await radioOptionsGroup.load(async () => radioOptionsInfo); await radioOptionsGroup.load(async () => radioOptionsInfo);
}); });
@@ -55,34 +39,40 @@ describe('radioOptionsGroup', function (): void {
it('onClick', async () => { it('onClick', async () => {
// click the radioButton corresponding to 'value1' // click the radioButton corresponding to 'value1'
(divItems as FakeRadioButton[]).filter(r => r.value === 'value1').pop()!.click(); ((radioOptionsGroup.items as azdata.RadioButtonComponent[]).find(r => r.value === 'value1') as StubRadioButton).click();
radioOptionsGroup.value!.should.equal('value1', 'radio options group should correspond to the radioButton that we clicked'); radioOptionsGroup.value!.should.equal('value1', 'radio options group should correspond to the radioButton that we clicked');
// verify all the radioButtons created in the group // verify all the radioButtons created in the group
verifyRadioGroup(); verifyRadioGroup();
}); });
it('load throws', async () => { it('load throws', async () => {
radioOptionsGroup.load(() => { throw loadingError; }); await radioOptionsGroup.load(() => { throw loadingError; });
//in error case radioButtons array wont hold radioButtons but holds a TextComponent with value equal to error string //in error case radioButtons array wont hold radioButtons but holds a TextComponent with value equal to error string
divItems.length.should.equal(1, 'There is should be only one element in the divContainer when loading error happens'); radioOptionsGroup.items.length.should.equal(1, 'There is should be only one element in the divContainer when loading error happens');
const label = divItems[0] as azdata.TextComponent; const label = radioOptionsGroup.items[0] as azdata.TextComponent;
should(label.value).not.be.undefined(); should(label.value).not.be.undefined();
label.value!.should.deepEqual(getErrorMessage(loadingError)); label.value!.should.deepEqual(getErrorMessage(loadingError));
should(label.CSSStyles).not.be.undefined(); should(label.CSSStyles).not.be.undefined();
should(label.CSSStyles!.color).not.be.undefined(); should(label.CSSStyles!.color).not.be.undefined();
label.CSSStyles!.color.should.equal('Red'); label.CSSStyles!.color.should.equal('Red');
}); });
describe('getters and setters', async () => {
it(`component getter`, () => {
should(radioOptionsGroup.component()).not.be.undefined();
});
});
}); });
function verifyRadioGroup() { function verifyRadioGroup() {
const radioButtons = divItems as FakeRadioButton[]; const radioButtons = radioOptionsGroup.items as azdata.RadioButtonComponent[];
radioButtons.length.should.equal(radioOptionsInfo.values!.length); radioButtons.length.should.equal(radioOptionsInfo.values!.length, 'Unexpected number of radio buttons');
radioButtons.forEach(rb => { radioButtons.forEach(rb => {
should(rb.label).not.be.undefined(); should(rb.label).not.equal(undefined, 'Radio Button label should not be undefined');
should(rb.value).not.be.undefined(); should(rb.value).not.equal(undefined, 'Radio button value should not be undefined');
should(rb.enabled).not.be.undefined(); should(rb.enabled).not.equal(undefined, 'Enabled should not be undefined');
rb.label!.should.equal(rb.value); rb.label!.should.equal(rb.value, 'Radio button label did not match');
rb.enabled!.should.be.true(); rb.enabled!.should.be.true('Radio button should be enabled');
}); });
} }

View File

@@ -32,7 +32,7 @@ describe('ConnectControllerDialog', function (): void {
it('validate returns false if controller refresh fails', async function (): Promise<void> { it('validate returns false if controller refresh fails', async function (): Promise<void> {
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed')); sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
const connectControllerDialog = new ConnectToControllerDialog(undefined!); const connectControllerDialog = new ConnectToControllerDialog(undefined!);
const info = { id: uuid(), url: 'https://127.0.0.1:30080', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }; const info = { id: uuid(), url: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
connectControllerDialog.showDialog(info, 'pwd'); connectControllerDialog.showDialog(info, 'pwd');
await connectControllerDialog.isInitialized; await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate(); const validateResult = await connectControllerDialog.validate();
@@ -41,36 +41,36 @@ describe('ConnectControllerDialog', function (): void {
it('validate replaces http with https', async function (): Promise<void> { it('validate replaces http with https', async function (): Promise<void> {
await validateConnectControllerDialog( await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, { id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081'); 'https://127.0.0.1:30081');
}); });
it('validate appends https if missing', async function (): Promise<void> { it('validate appends https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080'); 'https://127.0.0.1:30080');
}); });
it('validate appends default port if missing', async function (): Promise<void> { it('validate appends default port if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080'); 'https://127.0.0.1:30080');
}); });
it('validate appends both port and https if missing', async function (): Promise<void> { it('validate appends both port and https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080'); 'https://127.0.0.1:30080');
}); });
for (const name of ['', undefined]) { for (const name of ['', undefined]) {
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> { it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
await validateConnectControllerDialog( await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', name: name!, username: 'sa', rememberPassword: true, resources: [] }, { id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081'); 'https://127.0.0.1:30081');
}); });
} }
it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> { it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
await validateConnectControllerDialog( await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', name: '', username: 'sa', rememberPassword: true, resources: [] }, { id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081', 'https://127.0.0.1:30081',
undefined); undefined);
}); });

View File

@@ -11,6 +11,7 @@ import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdataExt from 'azdata-ext'; import * as azdataExt from 'azdata-ext';
import * as kubeUtils from '../../../common/kubeUtils';
import { ControllerModel } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { MiaaModel } from '../../../models/miaaModel'; import { MiaaModel } from '../../../models/miaaModel';
import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider';
@@ -53,7 +54,7 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false; treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren(); let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children'); should(children.length).equal(0, 'There initially shouldn\'t be any children');
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly'); should(children.length).equal(1, 'Controller node should be added correctly');
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
@@ -64,12 +65,12 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false; treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren(); let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children'); should(children.length).equal(0, 'There initially shouldn\'t be any children');
const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }; const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const controllerModel = new ControllerModel(treeDataProvider, originalInfo); const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly'); should(children.length).equal(1, 'Controller node should be added correctly');
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo); should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
const newInfo = { id: originalInfo.id, url: '1.1.1.1', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] }; const newInfo = { id: originalInfo.id, url: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] };
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo); const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
await treeDataProvider.addOrUpdateController(controllerModel2, ''); await treeDataProvider.addOrUpdateController(controllerModel2, '');
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node'); should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
@@ -102,21 +103,22 @@ describe('AzureArcTreeDataProvider tests', function (): void {
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi); mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object); sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword'); sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]);
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel); const controllerNode = treeDataProvider.getControllerNode(controllerModel);
const children = await treeDataProvider.getChildren(controllerNode); const children = await treeDataProvider.getChildren(controllerNode);
should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child'); should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child');
should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child'); should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child');
should(children.length).equal(2, 'Should have excatly 2 children'); should(children.length).equal(2, 'Should have exactly 2 children');
}); });
}); });
describe('removeController', function (): void { describe('removeController', function (): void {
it('removing a controller should work as expected', async function (): Promise<void> { it('removing a controller should work as expected', async function (): Promise<void> {
treeDataProvider['_loading'] = false; treeDataProvider['_loading'] = false;
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] }); const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
await treeDataProvider.addOrUpdateController(controllerModel2, ''); await treeDataProvider.addOrUpdateController(controllerModel2, '');
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren()); const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
@@ -133,20 +135,20 @@ describe('AzureArcTreeDataProvider tests', function (): void {
describe('openResourceDashboard', function (): void { describe('openResourceDashboard', function (): void {
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> { it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, ''); const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected(); await should(openDashboardPromise).be.rejected();
}); });
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> { it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, ''); const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected(); await should(openDashboardPromise).be.rejected();
}); });
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> { it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider); const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
await treeDataProvider.addOrUpdateController(controllerModel, ''); await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!; const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;

View File

@@ -23,6 +23,10 @@ declare module 'arc' {
userName?: string userName?: string
}; };
export type PGResourceInfo = ResourceInfo & {
userName?: string
};
export type ResourceInfo = { export type ResourceInfo = {
name: string, name: string,
resourceType: ResourceType | string, resourceType: ResourceType | string,
@@ -31,6 +35,8 @@ declare module 'arc' {
export type ControllerInfo = { export type ControllerInfo = {
id: string, id: string,
kubeConfigFilePath: string,
kubeClusterContext: string
url: string, url: string,
name: string, name: string,
username: string, username: string,

View File

@@ -24,5 +24,5 @@ export abstract class Dashboard {
return dashboard; return dashboard;
} }
protected abstract async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>; protected abstract registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>;
} }

View File

@@ -26,8 +26,9 @@ export abstract class DashboardPage extends InitializingComponent {
title: this.title, title: this.title,
id: this.id, id: this.id,
icon: this.icon, icon: this.icon,
content: this.container, // Get toolbar first since things in the container might depend on these being created
toolbar: this.toolbarContainer toolbar: this.toolbarContainer,
content: this.container
}; };
} }

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as path from 'path';
import * as vscode from 'vscode';
import * as loc from '../../localizedConstants';
export interface RadioOptionsInfo {
values?: string[],
defaultValue: string
}
export class FilePicker {
private _flexContainer: azdata.FlexContainer;
public readonly filePathInputBox: azdata.InputBoxComponent;
public readonly filePickerButton: azdata.ButtonComponent;
constructor(
modelBuilder: azdata.ModelBuilder,
initialPath: string, onNewDisposableCreated: (disposable: vscode.Disposable) => void
) {
const buttonWidth = 80;
this.filePathInputBox = modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
value: initialPath,
width: 350
}).component();
this.filePickerButton = modelBuilder.button()
.withProperties<azdata.ButtonProperties>({
label: loc.browse,
width: buttonWidth
}).component();
onNewDisposableCreated(this.filePickerButton.onDidClick(async () => {
const fileUris = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: this.filePathInputBox.value ? vscode.Uri.file(path.dirname(this.filePathInputBox.value)) : undefined,
openLabel: loc.select,
filters: undefined /* file type filters */
});
if (!fileUris || fileUris.length === 0) {
return; // This can happen when a user cancels out. We don't throw and the user just won't be able to move on until they select something.
}
const fileUri = fileUris[0]; //we allow the user to select only one file in the dialog
this.filePathInputBox.value = fileUri.fsPath;
}));
this._flexContainer = createFlexContainer(modelBuilder, [this.filePathInputBox, this.filePickerButton]);
}
component(): azdata.FlexContainer {
return this._flexContainer;
}
get onTextChanged() {
return this.filePathInputBox.onTextChanged;
}
get value(): string | undefined {
return this.filePathInputBox?.value;
}
get items(): azdata.Component[] {
return this._flexContainer.items;
}
}
function createFlexContainer(modelBuilder: azdata.ModelBuilder, items: azdata.Component[], rowLayout: boolean = true, width?: string | number, height?: string | number, alignItems?: azdata.AlignItemsType, cssStyles?: { [key: string]: string }): azdata.FlexContainer {
const flexFlow = rowLayout ? 'row' : 'column';
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();
}

View File

@@ -17,12 +17,9 @@ export class RadioOptionsGroup {
private _loadingBuilder: azdata.LoadingComponentBuilder; private _loadingBuilder: azdata.LoadingComponentBuilder;
private _currentRadioOption!: azdata.RadioButtonComponent; private _currentRadioOption!: azdata.RadioButtonComponent;
constructor(private _view: azdata.ModelView, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) { constructor(private _modelBuilder: azdata.ModelBuilder, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) {
const divBuilder = this._view.modelBuilder.divContainer(); this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
const divBuilderWithProperties = divBuilder.withProperties<azdata.DivContainerProperties>({ clickable: false }); this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
this._divContainer = divBuilderWithProperties.component();
const loadingComponentBuilder = this._view.modelBuilder.loadingComponent();
this._loadingBuilder = loadingComponentBuilder.withItem(this._divContainer);
} }
public component(): azdata.LoadingComponent { public component(): azdata.LoadingComponent {
@@ -37,7 +34,7 @@ export class RadioOptionsGroup {
const options = optionsInfo.values!; const options = optionsInfo.values!;
let defaultValue: string = optionsInfo.defaultValue!; let defaultValue: string = optionsInfo.defaultValue!;
options.forEach((option: string) => { options.forEach((option: string) => {
const radioOption = this._view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ const radioOption = this._modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: option, label: option,
checked: option === defaultValue, checked: option === defaultValue,
name: this._groupName, name: this._groupName,
@@ -60,7 +57,7 @@ export class RadioOptionsGroup {
}); });
} }
catch (e) { catch (e) {
const errorLabel = this._view!.modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component(); const errorLabel = this._modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component();
this._divContainer.addItem(errorLabel); this._divContainer.addItem(errorLabel);
} }
this.component().loading = false; this.component().loading = false;
@@ -69,4 +66,8 @@ export class RadioOptionsGroup {
get value(): string | undefined { get value(): string | undefined {
return this._currentRadioOption?.value; return this._currentRadioOption?.value;
} }
get items(): azdata.Component[] {
return this._divContainer.items;
}
} }

View File

@@ -147,7 +147,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
this.disposables.push( this.disposables.push(
newInstance.onDidClick(async () => { newInstance.onDidClick(async () => {
await vscode.commands.executeCommand('azdata.resource.deploy', 'arc.sql', ['arc.sql', 'arc.postgres']); await vscode.commands.executeCommand('azdata.resource.deploy', 'azure-sql-mi', ['azure-sql-mi', 'arc.postgres'], { 'azure-sql-mi': { 'mi-type': ['arc-mi'] } });
})); }));
// Refresh // Refresh
@@ -179,7 +179,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
const config = this._controllerModel.controllerConfig; const config = this._controllerModel.controllerConfig;
if (config) { if (config) {
await vscode.env.openExternal(vscode.Uri.parse( await vscode.env.openExternal(vscode.Uri.parse(
`https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.dataControllers}/${config.metadata.name}`)); `https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureArcData/${ResourceType.dataControllers}/${config.metadata.name}`));
} else { } else {
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration); vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
} }

View File

@@ -129,12 +129,16 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
cancellable: false cancellable: false
}, },
async (_progress, _token): Promise<void> => { async (_progress, _token): Promise<void> => {
let session: azdataExt.AzdataSession | undefined = undefined;
try { try {
session = await this._miaaModel.controllerModel.acquireAzdataSession();
await this._azdataApi.azdata.arc.sql.mi.edit( await this._azdataApi.azdata.arc.sql.mi.edit(
this._miaaModel.info.name, this.saveArgs); this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.azdataAdditionalEnvVars, session);
} catch (err) { } catch (err) {
this.saveButton!.enabled = true; this.saveButton!.enabled = true;
throw err; throw err;
} finally {
session?.dispose();
} }
await this._miaaModel.refresh(); await this._miaaModel.refresh();
@@ -179,7 +183,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false, readOnly: false,
min: 1, min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number', inputType: 'number',
placeHolder: loc.loading placeHolder: loc.loading
}).component(); }).component();
@@ -197,7 +200,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false, readOnly: false,
min: 1, min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number', inputType: 'number',
placeHolder: loc.loading placeHolder: loc.loading
}).component(); }).component();
@@ -318,6 +320,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
currentCPUSize = ''; currentCPUSize = '';
} }
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
this.coresRequestBox!.placeHolder = currentCPUSize; this.coresRequestBox!.placeHolder = currentCPUSize;
this.coresRequestBox!.value = ''; this.coresRequestBox!.value = '';
this.saveArgs.coresRequest = undefined; this.saveArgs.coresRequest = undefined;
@@ -328,6 +331,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
currentCPUSize = ''; currentCPUSize = '';
} }
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
this.coresLimitBox!.placeHolder = currentCPUSize; this.coresLimitBox!.placeHolder = currentCPUSize;
this.coresLimitBox!.value = ''; this.coresLimitBox!.value = '';
this.saveArgs.coresLimit = undefined; this.saveArgs.coresLimit = undefined;

View File

@@ -14,13 +14,13 @@ import { ControllerModel } from '../../../models/controllerModel';
import { MiaaModel } from '../../../models/miaaModel'; import { MiaaModel } from '../../../models/miaaModel';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { ResourceType } from 'arc'; import { ResourceType } from 'arc';
import { UserCancelledError } from '../../../common/api';
export class MiaaDashboardOverviewPage extends DashboardPage { export class MiaaDashboardOverviewPage extends DashboardPage {
private _propertiesLoading!: azdata.LoadingComponent; private _propertiesLoading!: azdata.LoadingComponent;
private _kibanaLoading!: azdata.LoadingComponent; private _kibanaLoading!: azdata.LoadingComponent;
private _grafanaLoading!: azdata.LoadingComponent; private _grafanaLoading!: azdata.LoadingComponent;
private _databasesTableLoading!: azdata.LoadingComponent;
private _propertiesContainer!: azdata.PropertiesContainerComponent; private _propertiesContainer!: azdata.PropertiesContainerComponent;
private _kibanaLink!: azdata.HyperlinkComponent; private _kibanaLink!: azdata.HyperlinkComponent;
@@ -29,6 +29,11 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
private _databasesMessage!: azdata.TextComponent; private _databasesMessage!: azdata.TextComponent;
private _openInAzurePortalButton!: azdata.ButtonComponent; private _openInAzurePortalButton!: azdata.ButtonComponent;
private _databasesContainer!: azdata.DivContainer;
private _connectToServerLoading!: azdata.LoadingComponent;
private _connectToServerButton!: azdata.ButtonComponent;
private _databasesTableLoading!: azdata.LoadingComponent;
private readonly _azdataApi: azdataExt.IExtension; private readonly _azdataApi: azdataExt.IExtension;
private readonly _azurecoreApi: azurecore.IExtension; private readonly _azurecoreApi: azurecore.IExtension;
@@ -84,6 +89,28 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
this._grafanaLink = this.modelView.modelBuilder.hyperlink().component(); this._grafanaLink = this.modelView.modelBuilder.hyperlink().component();
this._grafanaLoading = this.modelView.modelBuilder.loadingComponent().component(); this._grafanaLoading = this.modelView.modelBuilder.loadingComponent().component();
this._databasesContainer = this.modelView.modelBuilder.divContainer().component();
const connectToServerText = this.modelView.modelBuilder.text().withProps({
value: loc.miaaConnectionRequired
}).component();
this._connectToServerButton = this.modelView.modelBuilder.button().withProps({
label: loc.connectToServer,
enabled: false,
CSSStyles: { 'max-width': '125px', 'margin-left': '40%' }
}).component();
const connectToServerContainer = this.modelView.modelBuilder.divContainer().component();
connectToServerContainer.addItem(connectToServerText, { CSSStyles: { 'text-align': 'center', 'margin-top': '20px' } });
connectToServerContainer.addItem(this._connectToServerButton);
this._connectToServerLoading = this.modelView.modelBuilder.loadingComponent().withItem(connectToServerContainer).component();
this._databasesContainer.addItem(this._connectToServerLoading, { CSSStyles: { 'margin-top': '20px' } });
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component(); this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component();
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({ this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%', width: '100%',
@@ -180,7 +207,18 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
// Databases // Databases
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component()); rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component());
rootContainer.addItem(this._databasesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } }); this.disposables.push(
this._connectToServerButton!.onDidClick(async () => {
this._connectToServerButton!.enabled = false;
this._databasesTableLoading!.loading = true;
try {
await this.callGetDatabases();
} catch {
this._connectToServerButton!.enabled = true;
}
})
);
rootContainer.addItem(this._databasesContainer);
rootContainer.addItem(this._databasesMessage); rootContainer.addItem(this._databasesMessage);
this.initialized = true; this.initialized = true;
@@ -205,8 +243,14 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
title: loc.deletingInstance(this._miaaModel.info.name), title: loc.deletingInstance(this._miaaModel.info.name),
cancellable: false cancellable: false
}, },
(_progress, _token) => { async (_progress, _token) => {
return this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name); const session = await this._controllerModel.acquireAzdataSession();
try {
return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name, this._controllerModel.azdataAdditionalEnvVars, session);
} finally {
session.dispose();
}
} }
); );
await this._controllerModel.refreshTreeNode(); await this._controllerModel.refreshTreeNode();
@@ -251,7 +295,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
const config = this._controllerModel.controllerConfig; const config = this._controllerModel.controllerConfig;
if (config) { if (config) {
vscode.env.openExternal(vscode.Uri.parse( vscode.env.openExternal(vscode.Uri.parse(
`https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.sqlManagedInstances}/${this._miaaModel.info.name}`)); `https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureArcData/${ResourceType.sqlManagedInstances}/${this._miaaModel.info.name}`));
} else { } else {
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration); vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
} }
@@ -277,6 +321,19 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
).component(); ).component();
} }
private async callGetDatabases(): Promise<void> {
try {
await this._miaaModel.getDatabases();
} catch (error) {
if (error instanceof UserCancelledError) {
vscode.window.showWarningMessage(loc.miaaConnectionRequired);
} else {
vscode.window.showErrorMessage(loc.fetchDatabasesFailed(this._miaaModel.info.name, error));
}
throw error;
}
}
private handleRegistrationsUpdated(): void { private handleRegistrationsUpdated(): void {
const config = this._controllerModel.controllerConfig; const config = this._controllerModel.controllerConfig;
if (this._openInAzurePortalButton) { if (this._openInAzurePortalButton) {
@@ -295,6 +352,9 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
this._instanceProperties.externalEndpoint = this._miaaModel.config.status.externalEndpoint || loc.notConfigured; this._instanceProperties.externalEndpoint = this._miaaModel.config.status.externalEndpoint || loc.notConfigured;
this._instanceProperties.vCores = this._miaaModel.config.spec.limits?.vcores?.toString() || ''; this._instanceProperties.vCores = this._miaaModel.config.spec.limits?.vcores?.toString() || '';
this._databasesMessage.value = !this._miaaModel.config.status.externalEndpoint ? loc.noExternalEndpoint : ''; this._databasesMessage.value = !this._miaaModel.config.status.externalEndpoint ? loc.noExternalEndpoint : '';
if (!this._miaaModel.config.status.externalEndpoint) {
this._databasesContainer.removeItem(this._connectToServerLoading);
}
} }
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
@@ -306,7 +366,20 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin; this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
this._databasesTable.data = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]); this._databasesTable.data = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
this._databasesTableLoading.loading = !this._miaaModel.databasesLastUpdated; this._databasesTableLoading.loading = false;
if (this._miaaModel.databasesLastUpdated) {
// We successfully connected so now can remove the button and replace it with the actual databases table
this._databasesContainer.removeItem(this._connectToServerLoading);
this._databasesContainer.addItem(this._databasesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
} else {
// If we don't have an endpoint then there's no point in showing the connect button - but the logic
// to display text informing the user of this is already handled by the handleMiaaConfigUpdated
if (this._miaaModel?.config?.status.externalEndpoint) {
this._connectToServerLoading.loading = false;
this._connectToServerButton.enabled = true;
}
}
} }
private refreshDisplayedProperties(): void { private refreshDisplayedProperties(): void {

View File

@@ -76,7 +76,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
}).component(); }).component();
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
label: loc.addingWokerNodes, label: loc.addingWorkerNodes,
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli', 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' } CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component(); }).component();
@@ -155,14 +155,23 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
cancellable: false cancellable: false
}, },
async (_progress, _token): Promise<void> => { async (_progress, _token): Promise<void> => {
let session: azdataExt.AzdataSession | undefined = undefined;
try { try {
session = await this._postgresModel.controllerModel.acquireAzdataSession();
await this._azdataApi.azdata.arc.postgres.server.edit( await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name, this.saveArgs); this._postgresModel.info.name,
this.saveArgs,
this._postgresModel.engineVersion,
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
session
);
} catch (err) { } catch (err) {
// If an error occurs while editing the instance then re-enable the save button since // If an error occurs while editing the instance then re-enable the save button since
// the edit wasn't successfully applied // the edit wasn't successfully applied
this.saveButton!.enabled = true; this.saveButton!.enabled = true;
throw err; throw err;
} finally {
session?.dispose();
} }
await this._postgresModel.refresh(); await this._postgresModel.refresh();
} }
@@ -225,7 +234,6 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false, readOnly: false,
min: 1, min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number', inputType: 'number',
placeHolder: loc.loading placeHolder: loc.loading
}).component(); }).component();
@@ -243,7 +251,6 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false, readOnly: false,
min: 1, min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number', inputType: 'number',
placeHolder: loc.loading placeHolder: loc.loading
}).component(); }).component();
@@ -334,8 +341,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
iconPath: IconPathHelper.information, iconPath: IconPathHelper.information,
title: loc.workerNodesInformation, title: loc.workerNodesInformation,
width: '12px', width: '15px',
height: '12px', height: '15px',
enabled: false enabled: false
}).component(); }).component();
@@ -427,8 +434,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
iconPath: IconPathHelper.information, iconPath: IconPathHelper.information,
title: loc.postgresConfigurationInformation, title: loc.postgresConfigurationInformation,
width: '12px', width: '15px',
height: '12px', height: '15px',
enabled: false enabled: false
}).component(); }).component();
@@ -448,6 +455,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
currentCPUSize = ''; currentCPUSize = '';
} }
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
this.coresRequestBox!.placeHolder = currentCPUSize; this.coresRequestBox!.placeHolder = currentCPUSize;
this.coresRequestBox!.value = ''; this.coresRequestBox!.value = '';
this.saveArgs.coresRequest = undefined; this.saveArgs.coresRequest = undefined;
@@ -458,6 +466,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
currentCPUSize = ''; currentCPUSize = '';
} }
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
this.coresLimitBox!.placeHolder = currentCPUSize; this.coresLimitBox!.placeHolder = currentCPUSize;
this.coresLimitBox!.value = ''; this.coresLimitBox!.value = '';
this.saveArgs.coresLimit = undefined; this.saveArgs.coresLimit = undefined;

View File

@@ -14,6 +14,8 @@ import { Dashboard } from '../../components/dashboard';
import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage'; import { PostgresDiagnoseAndSolveProblemsPage } from './postgresDiagnoseAndSolveProblemsPage';
import { PostgresSupportRequestPage } from './postgresSupportRequestPage'; import { PostgresSupportRequestPage } from './postgresSupportRequestPage';
import { PostgresComputeAndStoragePage } from './postgresComputeAndStoragePage'; import { PostgresComputeAndStoragePage } from './postgresComputeAndStoragePage';
import { PostgresParametersPage } from './postgresParametersPage';
import { PostgresPropertiesPage } from './postgresPropertiesPage';
export class PostgresDashboard extends Dashboard { export class PostgresDashboard extends Dashboard {
constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) { constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
@@ -32,8 +34,8 @@ export class PostgresDashboard extends Dashboard {
const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel); const overviewPage = new PostgresOverviewPage(modelView, this._controllerModel, this._postgresModel);
const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel); const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this._postgresModel);
const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this._postgresModel); const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this._postgresModel);
// TODO: Removed properties page while investigating bug where refreshed values don't appear in UI const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
// const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel); const parametersPage = new PostgresParametersPage(modelView, this._postgresModel);
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel); const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel); const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);
@@ -42,8 +44,10 @@ export class PostgresDashboard extends Dashboard {
{ {
title: loc.settings, title: loc.settings,
tabs: [ tabs: [
propertiesPage.tab,
connectionStringsPage.tab, connectionStringsPage.tab,
computeAndStoragePage.tab computeAndStoragePage.tab,
parametersPage.tab
] ]
}, },
{ {

View File

@@ -7,16 +7,23 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext'; import * as azdataExt from 'azdata-ext';
import * as loc from '../../../localizedConstants'; import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants'; import { IconPathHelper, cssStyles, iconSize } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel'; import { PostgresModel } from '../../../models/postgresModel';
import { promptAndConfirmPassword, promptForInstanceDeletion } from '../../../common/utils'; import { promptAndConfirmPassword, promptForInstanceDeletion } from '../../../common/utils';
import { ResourceType } from 'arc'; import { ResourceType } from 'arc';
export type PodStatusModel = {
podName: azdata.Component,
type: string,
status: string
};
export class PostgresOverviewPage extends DashboardPage { export class PostgresOverviewPage extends DashboardPage {
private propertiesLoading!: azdata.LoadingComponent; private propertiesLoading!: azdata.LoadingComponent;
private serverGroupNodesLoading!: azdata.LoadingComponent;
private kibanaLoading!: azdata.LoadingComponent; private kibanaLoading!: azdata.LoadingComponent;
private grafanaLoading!: azdata.LoadingComponent; private grafanaLoading!: azdata.LoadingComponent;
@@ -24,6 +31,9 @@ export class PostgresOverviewPage extends DashboardPage {
private kibanaLink!: azdata.HyperlinkComponent; private kibanaLink!: azdata.HyperlinkComponent;
private grafanaLink!: azdata.HyperlinkComponent; private grafanaLink!: azdata.HyperlinkComponent;
private podStatusTable!: azdata.DeclarativeTableComponent;
private podStatusData: PodStatusModel[] = [];
private readonly _azdataApi: azdataExt.IExtension; private readonly _azdataApi: azdataExt.IExtension;
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) { constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
@@ -132,8 +142,63 @@ export class PostgresOverviewPage extends DashboardPage {
[loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription], [loc.kibanaDashboard, this.kibanaLoading, loc.kibanaDashboardDescription],
[loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]] [loc.grafanaDashboard, this.grafanaLoading, loc.grafanaDashboardDescription]]
}).component(); }).component();
content.addItem(endpointsTable); content.addItem(endpointsTable);
// Server Group Nodes
content.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: loc.serverGroupNodes,
CSSStyles: titleCSS
}).component());
this.podStatusTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
columns: [
{
displayName: loc.name,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: '35%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: {
...cssStyles.tableRow,
'overflow': 'hidden',
'text-overflow': 'ellipsis',
'white-space': 'nowrap',
'max-width': '0'
}
},
{
displayName: loc.type,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '35%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.status,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '30%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: [this.podStatusData.map(p => [p.podName, p.type, p.status])]
}).component();
this.serverGroupNodesLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.podStatusTable)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.configLastUpdated
}).component();
this.refreshServerNodes();
content.addItem(this.serverGroupNodesLoading, { CSSStyles: cssStyles.text });
this.initialized = true; this.initialized = true;
return root; return root;
} }
@@ -151,13 +216,21 @@ export class PostgresOverviewPage extends DashboardPage {
try { try {
const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : ''); const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : '');
if (password) { if (password) {
await this._azdataApi.azdata.arc.postgres.server.edit( const session = await this._postgresModel.controllerModel.acquireAzdataSession();
this._postgresModel.info.name, try {
{ await this._azdataApi.azdata.arc.postgres.server.edit(
adminPassword: true, this._postgresModel.info.name,
noWait: true {
}, adminPassword: true,
{ 'AZDATA_PASSWORD': password }); noWait: true
},
this._postgresModel.engineVersion,
Object.assign({ 'AZDATA_PASSWORD': password }, this._controllerModel.azdataAdditionalEnvVars),
session
);
} finally {
session.dispose();
}
vscode.window.showInformationMessage(loc.passwordReset); vscode.window.showInformationMessage(loc.passwordReset);
} }
} catch (error) { } catch (error) {
@@ -184,8 +257,14 @@ export class PostgresOverviewPage extends DashboardPage {
title: loc.deletingInstance(this._postgresModel.info.name), title: loc.deletingInstance(this._postgresModel.info.name),
cancellable: false cancellable: false
}, },
(_progress, _token) => { async (_progress, _token) => {
return this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name); const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name, this._controllerModel.azdataAdditionalEnvVars, session);
} finally {
session.dispose();
}
} }
); );
await this._controllerModel.refreshTreeNode(); await this._controllerModel.refreshTreeNode();
@@ -209,6 +288,7 @@ export class PostgresOverviewPage extends DashboardPage {
refreshButton.enabled = false; refreshButton.enabled = false;
try { try {
this.propertiesLoading!.loading = true; this.propertiesLoading!.loading = true;
this.serverGroupNodesLoading!.loading = true;
this.kibanaLoading!.loading = true; this.kibanaLoading!.loading = true;
this.grafanaLoading!.loading = true; this.grafanaLoading!.loading = true;
@@ -235,7 +315,7 @@ export class PostgresOverviewPage extends DashboardPage {
const azure = this._controllerModel.controllerConfig?.spec.settings.azure; const azure = this._controllerModel.controllerConfig?.spec.settings.azure;
if (azure) { if (azure) {
vscode.env.openExternal(vscode.Uri.parse( vscode.env.openExternal(vscode.Uri.parse(
`https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}`)); `https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureArcData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}`));
} else { } else {
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration); vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
} }
@@ -267,6 +347,54 @@ export class PostgresOverviewPage extends DashboardPage {
]; ];
} }
private getPodStatus(): PodStatusModel[] {
let podModels: PodStatusModel[] = [];
const podStatus = this._postgresModel.config?.status.podsStatus;
podStatus?.forEach(p => {
// If a condition of the pod has a status of False, pod is not Ready
const status = p.conditions.find(c => c.status === 'False') ? loc.notReady : loc.ready;
const podLabelContainer = this.modelView.modelBuilder.flexContainer().withProps({
CSSStyles: { 'alignItems': 'center', 'height': '15px' }
}).component();
const imageComponent = this.modelView.modelBuilder.image().withProps({
iconPath: IconPathHelper.postgres,
width: iconSize,
height: iconSize,
iconHeight: '15px',
iconWidth: '15px'
}).component();
let podLabel = this.modelView.modelBuilder.text().withProps({
value: p.name,
}).component();
if (p.role.toUpperCase() === loc.worker.toUpperCase()) {
podLabelContainer.addItem(imageComponent, { CSSStyles: { 'margin-left': '15px', 'margin-right': '0px' } });
podLabelContainer.addItem(podLabel);
let pod: PodStatusModel = {
podName: podLabelContainer,
type: loc.worker,
status: status
};
podModels.push(pod);
} else {
podLabelContainer.addItem(imageComponent, { CSSStyles: { 'margin-right': '0px' } });
podLabelContainer.addItem(podLabel);
let pod: PodStatusModel = {
podName: podLabelContainer,
type: loc.coordinator,
status: status
};
podModels.unshift(pod);
}
});
return podModels;
}
private refreshDashboardLinks(): void { private refreshDashboardLinks(): void {
if (this._postgresModel.config) { if (this._postgresModel.config) {
const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? ''; const kibanaUrl = this._postgresModel.config.status.logSearchDashboard ?? '';
@@ -281,6 +409,14 @@ export class PostgresOverviewPage extends DashboardPage {
} }
} }
private refreshServerNodes(): void {
if (this._postgresModel.config) {
this.podStatusData = this.getPodStatus();
this.podStatusTable.data = this.podStatusData.map(p => [p.podName, p.type, p.status]);
this.serverGroupNodesLoading.loading = false;
}
}
private handleRegistrationsUpdated() { private handleRegistrationsUpdated() {
this.properties!.propertyItems = this.getProperties(); this.properties!.propertyItems = this.getProperties();
this.propertiesLoading!.loading = false; this.propertiesLoading!.loading = false;
@@ -290,5 +426,6 @@ export class PostgresOverviewPage extends DashboardPage {
this.properties!.propertyItems = this.getProperties(); this.properties!.propertyItems = this.getProperties();
this.propertiesLoading!.loading = false; this.propertiesLoading!.loading = false;
this.refreshDashboardLinks(); this.refreshDashboardLinks();
this.refreshServerNodes();
} }
} }

View File

@@ -0,0 +1,588 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as loc from '../../../localizedConstants';
import { UserCancelledError } from '../../../common/api';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
import { EngineSettingsModel, PostgresModel } from '../../../models/postgresModel';
import { debounce } from '../../../common/utils';
export type ParametersModel = {
parameterName: string,
valueContainer: azdata.FlexContainer,
description: string,
resetButton: azdata.ButtonComponent
};
export class PostgresParametersPage extends DashboardPage {
private searchBox!: azdata.InputBoxComponent;
private parametersTable!: azdata.DeclarativeTableComponent;
private parameterContainer?: azdata.DivContainer;
private _parametersTableLoading!: azdata.LoadingComponent;
private discardButton!: azdata.ButtonComponent;
private saveButton!: azdata.ButtonComponent;
private resetAllButton!: azdata.ButtonComponent;
private connectToServerButton?: azdata.ButtonComponent;
private _parameters: ParametersModel[] = [];
private parameterUpdates: Map<string, string> = new Map();
private readonly _azdataApi: azdataExt.IExtension;
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
super(modelView);
this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
this.initializeConnectButton();
this.initializeSearchBox();
this.disposables.push(
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshParametersTable()))
);
}
protected get title(): string {
return loc.nodeParameters;
}
protected get id(): string {
return 'postgres-node-parameters';
}
protected get icon(): { dark: string; light: string; } {
return IconPathHelper.gear;
}
protected get container(): azdata.Component {
const root = this.modelView.modelBuilder.divContainer().component();
const content = this.modelView.modelBuilder.divContainer().component();
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.nodeParameters,
CSSStyles: { ...cssStyles.title }
}).component());
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.nodeParametersDescription,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component());
content.addItem(this.modelView.modelBuilder.hyperlink().withProps({
label: loc.learnAboutNodeParameters,
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale'
}).component(), { CSSStyles: { 'margin-bottom': '20px' } });
content.addItem(this.searchBox!, { CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-bottom': '20px' } });
this.parametersTable = this.modelView.modelBuilder.declarativeTable().withProps({
width: '100%',
columns: [
{
displayName: loc.parameterName,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.value,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: false,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.description,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '50%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: {
...cssStyles.tableRow
}
},
{
displayName: loc.resetToDefault,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: false,
width: '10%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: []
}).component();
this._parametersTableLoading = this.modelView.modelBuilder.loadingComponent().component();
this.parameterContainer = this.modelView.modelBuilder.divContainer().component();
this.selectComponent();
content.addItem(this.parameterContainer);
this.initialized = true;
return root;
}
protected get toolbarContainer(): azdata.ToolbarContainer {
// Save Edits
this.saveButton = this.modelView.modelBuilder.button().withProps({
label: loc.saveText,
iconPath: IconPathHelper.save,
enabled: false
}).component();
let engineSettings: string[] = [];
this.disposables.push(
this.saveButton.onDidClick(async () => {
this.saveButton!.enabled = false;
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
try {
this.parameterUpdates!.forEach((value, key) => {
engineSettings.push(`${key}="${value}"`);
});
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
{ engineSettings: engineSettings.toString() },
this._postgresModel.engineVersion,
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
session);
} finally {
session.dispose();
}
} catch (err) {
// If an error occurs while editing the instance then re-enable the save button since
// the edit wasn't successfully applied
this.saveButton!.enabled = true;
throw err;
}
await this._postgresModel.refresh();
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
engineSettings = [];
this.parameterUpdates!.clear();
this.discardButton!.enabled = false;
this.resetAllButton!.enabled = true;
} catch (error) {
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
}
})
);
// Discard
this.discardButton = this.modelView.modelBuilder.button().withProps({
label: loc.discardText,
iconPath: IconPathHelper.discard,
enabled: false
}).component();
this.disposables.push(
this.discardButton.onDidClick(async () => {
this.discardButton!.enabled = false;
try {
this.refreshParametersTable();
} catch (error) {
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
} finally {
this.saveButton!.enabled = false;
}
})
);
// Reset all
this.resetAllButton = this.modelView.modelBuilder.button().withProps({
label: loc.resetAllToDefault,
iconPath: IconPathHelper.reset,
enabled: false
}).component();
this.disposables.push(
this.resetAllButton.onDidClick(async () => {
this.resetAllButton!.enabled = false;
this.discardButton!.enabled = false;
this.saveButton!.enabled = false;
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
//all
// azdata arc postgres server edit -n <server group name> -e '' -re
let session: azdataExt.AzdataSession | undefined = undefined;
try {
session = await this._postgresModel.controllerModel.acquireAzdataSession();
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
{ engineSettings: `''`, replaceEngineSettings: true },
this._postgresModel.engineVersion,
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
session);
} catch (err) {
// If an error occurs while resetting the instance then re-enable the reset button since
// the edit wasn't successfully applied
if (this.parameterUpdates.size > 0) {
this.discardButton!.enabled = true;
this.saveButton!.enabled = true;
}
this.resetAllButton!.enabled = true;
throw err;
} finally {
session?.dispose();
}
await this._postgresModel.refresh();
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
this.parameterUpdates!.clear();
} catch (error) {
vscode.window.showErrorMessage(loc.resetFailed(error));
}
})
);
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
{ component: this.saveButton },
{ component: this.discardButton },
{ component: this.resetAllButton }
]).component();
}
private initializeConnectButton(): void {
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
label: loc.connectToServer,
enabled: false,
CSSStyles: { 'max-width': '125px' }
}).component();
this.disposables.push(
this.connectToServerButton!.onDidClick(async () => {
this.connectToServerButton!.enabled = false;
if (!vscode.extensions.getExtension(loc.postgresExtension)) {
const response = await vscode.window.showErrorMessage(loc.missingExtension('PostgreSQL'), loc.yes, loc.no);
if (response !== loc.yes) {
this.connectToServerButton!.enabled = true;
return;
}
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.installingExtension(loc.postgresExtension),
cancellable: false
},
async (_progress, _token): Promise<void> => {
try {
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
} catch (err) {
vscode.window.showErrorMessage(loc.extensionInstallationFailed(loc.postgresExtension));
this.connectToServerButton!.enabled = true;
throw err;
}
}
);
vscode.window.showInformationMessage(loc.extensionInstalled(loc.postgresExtension));
}
this._parametersTableLoading!.loading = true;
await this.callGetEngineSettings().finally(() => this._parametersTableLoading!.loading = false);
this.searchBox!.enabled = true;
this.resetAllButton!.enabled = true;
this.parameterContainer!.clearItems();
this.parameterContainer!.addItem(this.parametersTable);
})
);
}
private selectComponent(): void {
if (!this._postgresModel.engineSettingsLastUpdated) {
this.parameterContainer!.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.connectToPostgresDescription,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component());
this.parameterContainer!.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
this.parameterContainer!.addItem(this._parametersTableLoading!);
} else {
this.searchBox!.enabled = true;
this.resetAllButton!.enabled = true;
this.parameterContainer!.addItem(this.parametersTable!);
this.refreshParametersTable();
}
}
private async callGetEngineSettings(): Promise<void> {
try {
await this._postgresModel.getEngineSettings();
} catch (error) {
if (error instanceof UserCancelledError) {
vscode.window.showWarningMessage(loc.pgConnectionRequired);
} else {
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
}
this.connectToServerButton!.enabled = true;
throw error;
}
}
private initializeSearchBox(): void {
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
enabled: false,
placeHolder: loc.searchToFilter
}).component();
this.disposables.push(
this.searchBox.onTextChanged(() => {
this.onSearchFilter();
})
);
}
@debounce(500)
private onSearchFilter(): void {
if (!this.searchBox!.value) {
this.parametersTable.setFilter(undefined);
} else {
this.filterParameters(this.searchBox!.value);
}
}
private filterParameters(search: string): void {
const filteredRowIndexes: number[] = [];
this.parametersTable.data?.forEach((row, index) => {
if (row[0].toUpperCase()?.search(search.toUpperCase()) !== -1 || row[2].toUpperCase()?.search(search.toUpperCase()) !== -1) {
filteredRowIndexes.push(index);
}
});
this.parametersTable.setFilter(filteredRowIndexes);
}
private handleOnTextChanged(component: azdata.InputBoxComponent, currentValue: string | undefined): boolean {
if (!component.valid) {
// If invalid value return false and enable discard button
this.discardButton!.enabled = true;
return false;
} else if (component.value === currentValue) {
return false;
} else {
/* If a valid value has been entered into the input box, enable save and discard buttons
so that user could choose to either edit instance or clear all inputs
return true */
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
return true;
}
}
private createParameterComponents(engineSetting: EngineSettingsModel): ParametersModel {
// Container to hold input component and information bubble
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
if (engineSetting.type === 'enum') {
// If type is enum, component should be drop down menu
let options = engineSetting.options?.slice(1, -1).split(',');
let values: string[] = [];
options!.forEach(option => {
values.push(option.slice(option.indexOf('"') + 1, -1));
});
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
values: values,
value: engineSetting.value,
width: '150px'
}).component();
valueContainer.addItem(valueBox);
this.disposables.push(
valueBox.onValueChanged(() => {
if (engineSetting.value !== String(valueBox.value)) {
this.parameterUpdates!.set(engineSetting.parameterName!, String(valueBox.value));
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else if (engineSetting.type === 'bool') {
// If type is bool, component should be checkbox to turn on or off
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
label: loc.on,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
valueContainer.addItem(valueBox);
if (engineSetting.value === 'on') {
valueBox.checked = true;
} else {
valueBox.checked = false;
}
this.disposables.push(
valueBox.onChanged(() => {
if (valueBox.checked && engineSetting.value === 'off') {
this.parameterUpdates!.set(engineSetting.parameterName!, loc.on);
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (!valueBox.checked && engineSetting.value === 'on') {
this.parameterUpdates!.set(engineSetting.parameterName!, loc.off);
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else if (engineSetting.type === 'string') {
// If type is string, component should be text inputbox
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
required: true,
readOnly: false,
value: engineSetting.value,
width: '150px'
}).component();
valueContainer.addItem(valueBox);
this.disposables.push(
valueBox.onTextChanged(() => {
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
this.parameterUpdates!.set(engineSetting.parameterName!, `"${valueBox.value!}"`);
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else {
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
required: true,
readOnly: false,
min: parseInt(engineSetting.min!),
max: parseInt(engineSetting.max!),
validationErrorMessage: loc.outOfRange(engineSetting.min!, engineSetting.max!),
inputType: 'number',
value: engineSetting.value,
width: '150px'
}).component();
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px' } });
this.disposables.push(
valueBox.onTextChanged(() => {
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
this.parameterUpdates!.set(engineSetting.parameterName!, valueBox.value!);
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
// Information bubble title to show allowed values
let information = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.information,
width: '15px',
height: '15px',
enabled: false,
title: loc.allowedValue(loc.rangeSetting(engineSetting.min!, engineSetting.max!))
}).component();
valueContainer.addItem(information, { CSSStyles: { 'margin-left': '5px' } });
}
// Can reset individual parameter
const resetParameterButton = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.reset,
title: loc.resetToDefault,
width: '20px',
height: '20px',
enabled: true
}).component();
this.disposables.push(
resetParameterButton.onDidClick(async () => {
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
{ engineSettings: engineSetting.parameterName + '=' },
this._postgresModel.engineVersion,
this._postgresModel.controllerModel.azdataAdditionalEnvVars,
session);
} finally {
session.dispose();
}
await this._postgresModel.refresh();
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
} catch (error) {
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
}
})
);
let parameter: ParametersModel = {
parameterName: engineSetting.parameterName!,
valueContainer: valueContainer,
description: engineSetting.description!,
resetButton: resetParameterButton
};
return parameter;
}
private refreshParametersTable(): void {
this._parameters = this._postgresModel._engineSettings.map(engineSetting => this.createParameterComponents(engineSetting));
this.parametersTable.data = this._parameters.map(p => [p.parameterName, p.valueContainer, p.description, p.resetButton]);
}
private async handleServiceUpdated(): Promise<void> {
if (this._postgresModel.configLastUpdated && !this._postgresModel.engineSettingsLastUpdated) {
this.connectToServerButton!.enabled = true;
this._parametersTableLoading!.loading = false;
} else if (this._postgresModel.engineSettingsLastUpdated) {
await this.callGetEngineSettings();
this.discardButton!.enabled = false;
this.saveButton!.enabled = false;
}
}
}

View File

@@ -7,10 +7,11 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants'; import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants'; import { IconPathHelper, cssStyles } from '../../../constants';
import { KeyValueContainer, KeyValue, InputKeyValue, TextKeyValue } from '../../components/keyValueContainer'; import { KeyValueContainer, KeyValue, InputKeyValue, TextKeyValue, LinkKeyValue } from '../../components/keyValueContainer';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel'; import { PostgresModel } from '../../../models/postgresModel';
import { ControllerDashboard } from '../controller/controllerDashboard';
export class PostgresPropertiesPage extends DashboardPage { export class PostgresPropertiesPage extends DashboardPage {
private loading?: azdata.LoadingComponent; private loading?: azdata.LoadingComponent;
@@ -49,6 +50,7 @@ export class PostgresPropertiesPage extends DashboardPage {
}).component()); }).component());
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getProperties()); this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getProperties());
this.keyValueContainer.container.updateCssStyles({ 'max-width': '750px' });
this.disposables.push(this.keyValueContainer); this.disposables.push(this.keyValueContainer);
this.loading = this.modelView.modelBuilder.loadingComponent() this.loading = this.modelView.modelBuilder.loadingComponent()
@@ -93,12 +95,13 @@ export class PostgresPropertiesPage extends DashboardPage {
private getProperties(): KeyValue[] { private getProperties(): KeyValue[] {
const endpoint = this._postgresModel.endpoint; const endpoint = this._postgresModel.endpoint;
const status = this._postgresModel.config?.status; const status = this._postgresModel.config?.status;
const controllerDashboard = new ControllerDashboard(this._controllerModel);
return [ return [
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, endpoint ? `postgresql://postgres@${endpoint.ip}:${endpoint.port}` : ''), new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, endpoint ? `postgresql://postgres@${endpoint.ip}:${endpoint.port}` : ''),
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'), new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
new TextKeyValue(this.modelView.modelBuilder, loc.status, status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : loc.unknown), new TextKeyValue(this.modelView.modelBuilder, loc.status, status ? `${status.state} (${status.readyPods} ${loc.podsReady})` : loc.unknown),
// TODO: Make this a LinkKeyValue that opens the controller dashboard new LinkKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? '', () => controllerDashboard.showDashboard()),
new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? ''), new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.controllerConfig?.metadata.namespace ?? ''),
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.scaleConfiguration ?? ''), new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.scaleConfiguration ?? ''),
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.engineVersion ?? ''), new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.engineVersion ?? ''),

View File

@@ -55,7 +55,7 @@ export class PostgresSupportRequestPage extends DashboardPage {
const azure = this._controllerModel.controllerConfig?.spec.settings.azure; const azure = this._controllerModel.controllerConfig?.spec.settings.azure;
if (azure) { if (azure) {
vscode.env.openExternal(vscode.Uri.parse( vscode.env.openExternal(vscode.Uri.parse(
`https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}/supportrequest`)); `https://portal.azure.com/#resource/subscriptions/${azure.subscription}/resourceGroups/${azure.resourceGroup}/providers/Microsoft.AzureArcData/${ResourceType.postgresInstances}/${this._postgresModel.info.name}/supportrequest`));
} else { } else {
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration); vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
} }

View File

@@ -14,23 +14,43 @@ import { ControllerModel } from '../../models/controllerModel';
import { InitializingComponent } from '../components/initializingComponent'; import { InitializingComponent } from '../components/initializingComponent';
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
import { getErrorMessage } from '../../common/utils'; import { getErrorMessage } from '../../common/utils';
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../../common/kubeUtils';
import { FilePicker } from '../components/filePicker';
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string }; export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
abstract class ControllerDialogBase extends InitializingComponent { abstract class ControllerDialogBase extends InitializingComponent {
protected _toDispose: vscode.Disposable[] = [];
protected modelBuilder!: azdata.ModelBuilder; protected modelBuilder!: azdata.ModelBuilder;
protected dialog: azdata.window.Dialog; protected dialog: azdata.window.Dialog;
protected urlInputBox!: azdata.InputBoxComponent; protected urlInputBox!: azdata.InputBoxComponent;
protected kubeConfigInputBox!: FilePicker;
protected clusterContextRadioGroup!: RadioOptionsGroup;
protected nameInputBox!: azdata.InputBoxComponent; protected nameInputBox!: azdata.InputBoxComponent;
protected usernameInputBox!: azdata.InputBoxComponent; protected usernameInputBox!: azdata.InputBoxComponent;
protected passwordInputBox!: azdata.InputBoxComponent; protected passwordInputBox!: azdata.InputBoxComponent;
protected dispose(): void {
this._toDispose.forEach(disposable => disposable.dispose());
this._toDispose.length = 0; // clear the _toDispose array
}
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] { protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
return [ return [
{ {
component: this.urlInputBox, component: this.urlInputBox,
title: loc.controllerUrl, title: loc.controllerUrl,
required: true required: true
}, {
component: this.kubeConfigInputBox.component(),
title: loc.controllerKubeConfig,
required: true
}, {
component: this.clusterContextRadioGroup.component(),
title: loc.controllerClusterContext,
required: true
}, { }, {
component: this.nameInputBox, component: this.nameInputBox,
title: loc.controllerName, title: loc.controllerName,
@@ -48,7 +68,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
} }
protected abstract fieldToFocusOn(): azdata.Component; protected abstract fieldToFocusOn(): azdata.Component;
protected readonlyFields(): azdata.InputBoxComponent[] { return []; } protected readonlyFields(): azdata.Component[] { return []; }
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) { protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
this.urlInputBox = this.modelBuilder.inputBox() this.urlInputBox = this.modelBuilder.inputBox()
@@ -57,6 +77,18 @@ abstract class ControllerDialogBase extends InitializingComponent {
// If we have a model then we're editing an existing connection so don't let them modify the URL // If we have a model then we're editing an existing connection so don't let them modify the URL
readOnly: !!controllerInfo readOnly: !!controllerInfo
}).component(); }).component();
this.kubeConfigInputBox = new FilePicker(
this.modelBuilder,
controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(),
(disposable) => this._toDispose.push(disposable)
);
this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath()
}).component();
this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable));
this.loadRadioGroup(controllerInfo?.kubeClusterContext);
this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));
this.nameInputBox = this.modelBuilder.inputBox() this.nameInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({ .withProperties<azdata.InputBoxProperties>({
value: controllerInfo?.name value: controllerInfo?.name
@@ -81,10 +113,20 @@ abstract class ControllerDialogBase extends InitializingComponent {
this.dialog = azdata.window.createModelViewDialog(title); this.dialog = azdata.window.createModelViewDialog(title);
} }
private loadRadioGroup(previousClusterContext?: string): void {
this.clusterContextRadioGroup.load(async () => {
const clusters = await getKubeConfigClusterContexts(this.kubeConfigInputBox.value!);
return {
values: clusters.map(c => c.name),
defaultValue: getCurrentClusterContext(clusters, previousClusterContext, false),
};
});
}
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog { public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
this.id = controllerInfo?.id ?? uuid(); this.id = controllerInfo?.id ?? uuid();
this.resources = controllerInfo?.resources ?? []; this.resources = controllerInfo?.resources ?? [];
this.dialog.cancelButton.onClick(() => this.handleCancel()); this._toDispose.push(this.dialog.cancelButton.onClick(() => this.handleCancel()));
this.dialog.registerContent(async (view) => { this.dialog.registerContent(async (view) => {
this.modelBuilder = view.modelBuilder; this.modelBuilder = view.modelBuilder;
this.initializeFields(controllerInfo, password); this.initializeFields(controllerInfo, password);
@@ -96,18 +138,24 @@ abstract class ControllerDialogBase extends InitializingComponent {
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
await this.fieldToFocusOn().focus(); await this.fieldToFocusOn().focus();
this.readonlyFields().forEach(f => f.readOnly = true); this.readonlyFields().forEach(f => f.enabled = false);
this.initialized = true; this.initialized = true;
}); });
this.dialog.registerCloseValidator(async () => await this.validate()); this.dialog.registerCloseValidator(async () => {
const isValidated = await this.validate();
if (isValidated) {
this.dispose();
}
return isValidated;
});
this.dialog.okButton.label = loc.connect; this.dialog.okButton.label = loc.connect;
this.dialog.cancelButton.label = loc.cancel; this.dialog.cancelButton.label = loc.cancel;
azdata.window.openDialog(this.dialog); azdata.window.openDialog(this.dialog);
return this.dialog; return this.dialog;
} }
public abstract async validate(): Promise<boolean>; public abstract validate(): Promise<boolean>;
private handleCancel(): void { private handleCancel(): void {
this.completionPromise.resolve(undefined); this.completionPromise.resolve(undefined);
@@ -116,6 +164,19 @@ abstract class ControllerDialogBase extends InitializingComponent {
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> { public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
return this.completionPromise.promise; return this.completionPromise.promise;
} }
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
return {
id: this.id,
url: url,
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 { export class ConnectToControllerDialog extends ControllerDialogBase {
@@ -164,14 +225,7 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
if (!/.*:\d*$/.test(url)) { if (!/.*:\d*$/.test(url)) {
url = `${url}:30080`; url = `${url}:30080`;
} }
const controllerInfo: ControllerInfo = { const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
id: this.id,
url: url,
name: this.nameInputBox.value ?? '',
username: this.usernameInputBox.value,
rememberPassword: this.rememberPwCheckBox.checked ?? false,
resources: this.resources
};
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value); const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
try { try {
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response. // Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
@@ -199,9 +253,11 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
return this.passwordInputBox; return this.passwordInputBox;
} }
protected readonlyFields() { protected readonlyFields(): azdata.Component[] {
return [ return [
this.urlInputBox, this.urlInputBox,
...this.kubeConfigInputBox.items,
...this.clusterContextRadioGroup.items,
this.nameInputBox, this.nameInputBox,
this.usernameInputBox this.usernameInputBox
]; ];
@@ -213,11 +269,19 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
} }
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports; const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
try { try {
await azdataApi.azdata.login(this.urlInputBox.value!, this.usernameInputBox.value!, this.passwordInputBox.value); await azdataApi.azdata.login(
this.urlInputBox.value!,
this.usernameInputBox.value!,
this.passwordInputBox.value,
{
'KUBECONFIG': this.kubeConfigInputBox.value!,
'KUBECTL_CONTEXT': this.clusterContextRadioGroup.value!
}
);
} catch (e) { } catch (e) {
if (getErrorMessage(e).match(/Wrong username or password/i)) { if (getErrorMessage(e).match(/Wrong username or password/i)) {
this.dialog.message = { this.dialog.message = {
text: loc.invalidPassword, text: loc.loginFailed,
level: azdata.window.MessageLevel.Error level: azdata.window.MessageLevel.Error
}; };
return false; return false;
@@ -229,14 +293,7 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
return false; return false;
} }
} }
const controllerInfo: ControllerInfo = { const controllerInfo: ControllerInfo = this.getControllerInfo(this.urlInputBox.value!, false);
id: this.id,
url: this.urlInputBox.value!,
name: this.nameInputBox.value!,
username: this.usernameInputBox.value!,
rememberPassword: false,
resources: []
};
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value); const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value }); this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
return true; return true;
@@ -248,3 +305,5 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
return dialog; return dialog;
} }
} }

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ControllerModel } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel';
import { ConnectToSqlDialog } from './connectSqlDialog';
import * as loc from '../../localizedConstants';
export class ConnectToMiaaSqlDialog extends ConnectToSqlDialog {
constructor(_controllerModel: ControllerModel, _miaaModel: MiaaModel) {
super(_controllerModel, _miaaModel);
}
protected get providerName(): string {
return 'MSSQL';
}
protected connectionFailedMessage(error: any): string {
return loc.connectToMSSqlFailed(this.serverNameInputBox.value!, error);
}
}

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ControllerModel } from '../../models/controllerModel';
import { PostgresModel } from '../../models/postgresModel';
import { ConnectToSqlDialog } from './connectSqlDialog';
import * as loc from '../../localizedConstants';
export class ConnectToPGSqlDialog extends ConnectToSqlDialog {
constructor(_controllerModel: ControllerModel, _postgresModel: PostgresModel) {
super(_controllerModel, _postgresModel);
}
protected get providerName(): string {
return 'PGSQL';
}
protected connectionFailedMessage(error: any): string {
return loc.connectToPGSqlFailed(this.serverNameInputBox.value!, error);
}
}

View File

@@ -6,29 +6,30 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Deferred } from '../../common/promise'; import { Deferred } from '../../common/promise';
import * as loc from '../../localizedConstants';
import { createCredentialId } from '../../common/utils'; import { createCredentialId } from '../../common/utils';
import { credentialNamespace } from '../../constants'; import { credentialNamespace } from '../../constants';
import * as loc from '../../localizedConstants';
import { ControllerModel } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel';
import { InitializingComponent } from '../components/initializingComponent'; import { InitializingComponent } from '../components/initializingComponent';
import { ResourceModel } from '../../models/resourceModel';
import { ControllerModel } from '../../models/controllerModel';
export class ConnectToSqlDialog extends InitializingComponent { export abstract class ConnectToSqlDialog extends InitializingComponent {
private modelBuilder!: azdata.ModelBuilder; protected modelBuilder!: azdata.ModelBuilder;
private serverNameInputBox!: azdata.InputBoxComponent; protected serverNameInputBox!: azdata.InputBoxComponent;
private usernameInputBox!: azdata.InputBoxComponent; protected usernameInputBox!: azdata.InputBoxComponent;
private passwordInputBox!: azdata.InputBoxComponent; protected passwordInputBox!: azdata.InputBoxComponent;
private rememberPwCheckBox!: azdata.CheckBoxComponent; protected rememberPwCheckBox!: azdata.CheckBoxComponent;
private options: { [name: string]: any } = {};
private _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>(); protected _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
constructor(private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) { constructor(private _controllerModel: ControllerModel, protected _model: ResourceModel) {
super(); super();
} }
public showDialog(connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog { public showDialog(dialogTitle: string, connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
const dialog = azdata.window.createModelViewDialog(loc.connectToSql(this._miaaModel.info.name)); const dialog = azdata.window.createModelViewDialog(dialogTitle);
dialog.cancelButton.onClick(() => this.handleCancel()); dialog.cancelButton.onClick(() => this.handleCancel());
dialog.registerContent(async view => { dialog.registerContent(async view => {
this.modelBuilder = view.modelBuilder; this.modelBuilder = view.modelBuilder;
@@ -84,6 +85,7 @@ export class ConnectToSqlDialog extends InitializingComponent {
dialog.registerCloseValidator(async () => await this.validate()); dialog.registerCloseValidator(async () => await this.validate());
dialog.okButton.label = loc.connect; dialog.okButton.label = loc.connect;
dialog.cancelButton.label = loc.cancel; dialog.cancelButton.label = loc.cancel;
this.options = connectionProfile?.options!;
azdata.window.openDialog(dialog); azdata.window.openDialog(dialog);
return dialog; return dialog;
} }
@@ -96,7 +98,7 @@ export class ConnectToSqlDialog extends InitializingComponent {
serverName: this.serverNameInputBox.value, serverName: this.serverNameInputBox.value,
databaseName: '', databaseName: '',
authenticationType: 'SqlLogin', authenticationType: 'SqlLogin',
providerName: 'MSSQL', providerName: this.providerName,
connectionName: '', connectionName: '',
userName: this.usernameInputBox.value, userName: this.usernameInputBox.value,
password: this.passwordInputBox.value, password: this.passwordInputBox.value,
@@ -105,26 +107,30 @@ export class ConnectToSqlDialog extends InitializingComponent {
saveProfile: true, saveProfile: true,
id: '', id: '',
groupId: undefined, groupId: undefined,
options: {} options: this.options
}; };
const result = await azdata.connection.connect(connectionProfile, false, false); const result = await azdata.connection.connect(connectionProfile, false, false);
if (result.connected) { if (result.connected) {
connectionProfile.id = result.connectionId; connectionProfile.id = result.connectionId;
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace); const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
if (connectionProfile.savePassword) { if (connectionProfile.savePassword) {
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name), connectionProfile.password); await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._model.info.resourceType, this._model.info.name), connectionProfile.password);
} else { } else {
await credentialProvider.deleteCredential(createCredentialId(this._controllerModel.info.id, this._miaaModel.info.resourceType, this._miaaModel.info.name)); await credentialProvider.deleteCredential(createCredentialId(this._controllerModel.info.id, this._model.info.resourceType, this._model.info.name));
} }
this._completionPromise.resolve(connectionProfile); this._completionPromise.resolve(connectionProfile);
return true; return true;
} }
else { else {
vscode.window.showErrorMessage(loc.connectToSqlFailed(this.serverNameInputBox.value, result.errorMessage)); vscode.window.showErrorMessage(this.connectionFailedMessage(result.errorMessage));
return false; return false;
} }
} }
protected abstract get providerName(): string;
protected abstract connectionFailedMessage(error: any): string;
private handleCancel(): void { private handleCancel(): void {
this._completionPromise.resolve(undefined); this._completionPromise.resolve(undefined);
} }

View File

@@ -10,7 +10,7 @@ import { ControllerModel } from '../../models/controllerModel';
import { ControllerTreeNode } from './controllerTreeNode'; import { ControllerTreeNode } from './controllerTreeNode';
import { TreeNode } from './treeNode'; import { TreeNode } from './treeNode';
const mementoToken = 'arcControllers'; const mementoToken = 'arcDataControllers';
/** /**
* The TreeDataProvider for the Azure Arc view, which displays a list of registered * The TreeDataProvider for the Azure Arc view, which displays a list of registered

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { MiaaResourceInfo, ResourceInfo, ResourceType } from 'arc'; import { MiaaResourceInfo, PGResourceInfo, ResourceInfo, ResourceType } from 'arc';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { UserCancelledError } from '../../common/utils'; import { UserCancelledError } from '../../common/api';
import * as loc from '../../localizedConstants'; import * as loc from '../../localizedConstants';
import { ControllerModel, Registration } from '../../models/controllerModel'; import { ControllerModel, Registration } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel'; import { MiaaModel } from '../../models/miaaModel';
@@ -102,7 +102,11 @@ export class ControllerTreeNode extends TreeNode {
switch (registration.instanceType) { switch (registration.instanceType) {
case ResourceType.postgresInstances: case ResourceType.postgresInstances:
const postgresModel = new PostgresModel(this.model, resourceInfo, registration); // Fill in the username too if we already have it
(resourceInfo as PGResourceInfo).userName = (this.model.info.resources.find(info =>
info.name === resourceInfo.name &&
info.resourceType === resourceInfo.resourceType) as PGResourceInfo)?.userName;
const postgresModel = new PostgresModel(this.model, resourceInfo, registration, this._treeDataProvider);
node = new PostgresTreeNode(postgresModel, this.model, this._context); node = new PostgresTreeNode(postgresModel, this.model, this._context);
break; break;
case ResourceType.sqlManagedInstances: case ResourceType.sqlManagedInstances:

View File

@@ -2,6 +2,7 @@
"extends": "../shared.tsconfig.json", "extends": "../shared.tsconfig.json",
"compileOnSave": true, "compileOnSave": true,
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true,
"outDir": "./out", "outDir": "./out",
"lib": [ "lib": [
"es6", "es6",

View File

@@ -275,6 +275,13 @@
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245"
integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ== integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ==
agent-base@4, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"
ajv@^6.5.5: ajv@^6.5.5:
version "6.12.0" version "6.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7"
@@ -338,6 +345,16 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
azdata-test@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/azdata-test/-/azdata-test-1.1.1.tgz#0feb13ec01397841f7bbce0ad25a41e4a3afb5b5"
integrity sha512-+jZ5/4Orqt5Q2MLPqmV+iQjGYxG5FK4pC3UqTOPdSzlbs0JQhOXckC8Hmw8ZOS0N6Cn7mNDCpfaqEU9coTyVsw==
dependencies:
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.4"
rimraf "^2.6.3"
typemoq "^2.1.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -510,6 +527,18 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0" jsbn "~0.1.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -647,6 +676,14 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
http-proxy-agent@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
dependencies:
agent-base "4"
debug "3.1.0"
http-signature@~1.2.0: http-signature@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -656,6 +693,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2" jsprim "^1.2.2"
sshpk "^1.7.0" sshpk "^1.7.0"
https-proxy-agent@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.3.0"
debug "^3.1.0"
inflight@^1.0.4: inflight@^1.0.4:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -1204,7 +1249,7 @@ type-detect@4.0.8, type-detect@^4.0.8:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typemoq@2.1.0: typemoq@2.1.0, typemoq@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8" resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"
integrity sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw== integrity sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==

View File

@@ -2,7 +2,7 @@
"name": "asde-deployment", "name": "asde-deployment",
"displayName": "%extension-displayName%", "displayName": "%extension-displayName%",
"description": "%extension-description%", "description": "%extension-description%",
"version": "0.4.0", "version": "0.4.1",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
@@ -338,11 +338,13 @@
"confirmationRequired": true, "confirmationRequired": true,
"confirmationLabel": "%vm_password_confirm%", "confirmationLabel": "%vm_password_confirm%",
"required": true, "required": true,
"validations" : [{ "validations": [
"type": "regex_match", {
"regex": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_])[A-Za-z\\d\\W_]{12,123}$", "type": "regex_match",
"description": "%vm_password_validation_error_message%" "regex": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[\\W_])[A-Za-z\\d\\W_]{12,123}$",
}] "description": "%vm_password_validation_error_message%"
}
]
} }
] ]
}, },
@@ -580,19 +582,22 @@
"when": "type=azure-multi-device" "when": "type=azure-multi-device"
} }
], ],
"agreement": { "agreements": [
"template": "%edge-agreement%", {
"links": [ "template": "%edge-agreement%",
{ "links": [
"text": "%microsoft-privacy-statement%", {
"url": "https://go.microsoft.com/fwlink/?LinkId=853010" "text": "%microsoft-privacy-statement%",
}, "url": "https://go.microsoft.com/fwlink/?LinkId=853010"
{ },
"text": "%edge-eula%", {
"url": "https://go.microsoft.com/fwlink/?linkid=2128283" "text": "%edge-eula%",
} "url": "https://go.microsoft.com/fwlink/?linkid=2128283"
] }
} ],
"when": "true"
}
]
} }
] ]
}, },

View File

@@ -2,14 +2,14 @@
"name": "azdata", "name": "azdata",
"displayName": "%azdata.displayName%", "displayName": "%azdata.displayName%",
"description": "%azdata.description%", "description": "%azdata.description%",
"version": "0.4.1", "version": "0.5.3",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
"icon": "images/extension.png", "icon": "images/extension.png",
"engines": { "engines": {
"vscode": "*", "vscode": "*",
"azdata": ">=1.23.0" "azdata": ">=1.26.0"
}, },
"activationEvents": [ "activationEvents": [
"*" "*"

View File

@@ -12,7 +12,7 @@ import * as constants from './constants';
import * as loc from './localizedConstants'; import * as loc from './localizedConstants';
import { AzdataToolService } from './services/azdataToolService'; import { AzdataToolService } from './services/azdataToolService';
function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata { export function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
throwIfNoAzdata(azdata); throwIfNoAzdata(azdata);
if (!eulaAccepted) { if (!eulaAccepted) {
Logger.log(loc.eulaNotAccepted); Logger.log(loc.eulaNotAccepted);
@@ -45,47 +45,57 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
return { return {
arc: { arc: {
dc: { dc: {
create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string) => { create: async (
namespace: string,
name: string,
connectivityMode: string,
resourceGroup: string,
location: string,
subscription: string,
profileName?: string,
storageClass?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass); return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars, session);
}, },
endpoint: { endpoint: {
list: async () => { list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.endpoint.list(); return azdataToolService.localAzdata.arc.dc.endpoint.list(additionalEnvVars, session);
} }
}, },
config: { config: {
list: async () => { list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.config.list(); return azdataToolService.localAzdata.arc.dc.config.list(additionalEnvVars, session);
}, },
show: async () => { show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.config.show(); return azdataToolService.localAzdata.arc.dc.config.show(additionalEnvVars, session);
} }
} }
}, },
postgres: { postgres: {
server: { server: {
delete: async (name: string) => { delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.delete(name); return azdataToolService.localAzdata.arc.postgres.server.delete(name, additionalEnvVars, session);
}, },
list: async () => { list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.list(); return azdataToolService.localAzdata.arc.postgres.server.list(additionalEnvVars, session);
}, },
show: async (name: string) => { show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.show(name); return azdataToolService.localAzdata.arc.postgres.server.show(name, additionalEnvVars, session);
}, },
edit: async ( edit: async (
name: string, name: string,
@@ -102,29 +112,31 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
replaceEngineSettings?: boolean; replaceEngineSettings?: boolean;
workers?: number; workers?: number;
}, },
additionalEnvVars?: { [key: string]: string; }) => { engineVersion?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, additionalEnvVars); return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, engineVersion, additionalEnvVars, session);
} }
} }
}, },
sql: { sql: {
mi: { mi: {
delete: async (name: string) => { delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.delete(name); return azdataToolService.localAzdata.arc.sql.mi.delete(name, additionalEnvVars, session);
}, },
list: async () => { list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.list(); return azdataToolService.localAzdata.arc.sql.mi.list(additionalEnvVars, session);
}, },
show: async (name: string) => { show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.show(name); return azdataToolService.localAzdata.arc.sql.mi.show(name, additionalEnvVars, session);
}, },
edit: async ( edit: async (
name: string, name: string,
@@ -134,10 +146,13 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
memoryLimit?: string; memoryLimit?: string;
memoryRequest?: string; memoryRequest?: string;
noWait?: boolean; noWait?: boolean;
}) => { },
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession
) => {
await localAzdataDiscovered; await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.edit(name, args); return azdataToolService.localAzdata.arc.sql.mi.edit(name, args, additionalEnvVars, session);
} }
} }
} }
@@ -147,9 +162,13 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
throwIfNoAzdata(azdataToolService.localAzdata); throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.getPath(); return azdataToolService.localAzdata.getPath();
}, },
login: async (endpoint: string, username: string, password: string) => { login: async (endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => {
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.login(endpoint, username, password); return azdataToolService.localAzdata.login(endpoint, username, password, additionalEnvVars);
},
acquireSession: async (endpoint: string, username: string, password: string, additionEnvVars?: azdataExt.AdditionalEnvVars) => {
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata?.acquireSession(endpoint, username, password, additionEnvVars);
}, },
getSemVersion: async () => { getSemVersion: async () => {
await localAzdataDiscovered; await localAzdataDiscovered;

View File

@@ -13,6 +13,7 @@ import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataRele
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess'; import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
import { HttpClient } from './common/httpClient'; import { HttpClient } from './common/httpClient';
import Logger from './common/logger'; import Logger from './common/logger';
import { Deferred } from './common/promise';
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils'; import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants'; import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
import * as loc from './localizedConstants'; import * as loc from './localizedConstants';
@@ -31,7 +32,20 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
* @param args The args to pass to azdata * @param args The args to pass to azdata
* @param parseResult A function used to parse out the raw result into the desired shape * @param parseResult A function used to parse out the raw result into the desired shape
*/ */
executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>> executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>>
}
class AzdataSession implements azdataExt.AzdataSession {
private _session = new Deferred<void>();
public sessionEnded(): Promise<void> {
return this._session.promise;
}
public dispose(): void {
this._session.resolve();
}
} }
/** /**
@@ -40,6 +54,10 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
export class AzdataTool implements azdataExt.IAzdataApi { export class AzdataTool implements azdataExt.IAzdataApi {
private _semVersion: SemVer; private _semVersion: SemVer;
private _currentSession: azdataExt.AzdataSession | undefined = undefined;
private _currentlyExecutingCommands: Deferred<void>[] = [];
private _queuedCommands: { deferred: Deferred<void>, session?: azdataExt.AzdataSession }[] = [];
constructor(private _path: string, version: string) { constructor(private _path: string, version: string) {
this._semVersion = new SemVer(version); this._semVersion = new SemVer(version);
} }
@@ -62,7 +80,17 @@ export class AzdataTool implements azdataExt.IAzdataApi {
public arc = { public arc = {
dc: { dc: {
create: (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string): Promise<azdataExt.AzdataOutput<void>> => { create: (
namespace: string,
name: string,
connectivityMode: string,
resourceGroup: string,
location: string,
subscription: string,
profileName?: string,
storageClass?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
const args = ['arc', 'dc', 'create', const args = ['arc', 'dc', 'create',
'--namespace', namespace, '--namespace', namespace,
'--name', name, '--name', name,
@@ -76,32 +104,32 @@ export class AzdataTool implements azdataExt.IAzdataApi {
if (storageClass) { if (storageClass) {
args.push('--storage-class', storageClass); args.push('--storage-class', storageClass);
} }
return this.executeCommand<void>(args); return this.executeCommand<void>(args, additionalEnvVars, session);
}, },
endpoint: { endpoint: {
list: (): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => { list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list']); return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, session);
} }
}, },
config: { config: {
list: (): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => { list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list']); return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, session);
}, },
show: (): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => { show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show']); return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, session);
} }
} }
}, },
postgres: { postgres: {
server: { server: {
delete: (name: string): Promise<azdataExt.AzdataOutput<void>> => { delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force']); return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, session);
}, },
list: (): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => { list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list']); return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, session);
}, },
show: (name: string): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => { show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name]); return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars, session);
}, },
edit: ( edit: (
name: string, name: string,
@@ -118,7 +146,9 @@ export class AzdataTool implements azdataExt.IAzdataApi {
replaceEngineSettings?: boolean, replaceEngineSettings?: boolean,
workers?: number workers?: number
}, },
additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> => { engineVersion?: string,
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name]; const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
if (args.adminPassword) { argsArray.push('--admin-password'); } if (args.adminPassword) { argsArray.push('--admin-password'); }
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); } if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
@@ -131,20 +161,21 @@ export class AzdataTool implements azdataExt.IAzdataApi {
if (args.port) { argsArray.push('--port', args.port.toString()); } if (args.port) { argsArray.push('--port', args.port.toString()); }
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); } if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
if (args.workers) { argsArray.push('--workers', args.workers.toString()); } if (args.workers) { argsArray.push('--workers', args.workers.toString()); }
return this.executeCommand<void>(argsArray, additionalEnvVars); if (engineVersion) { argsArray.push('--engine-version', engineVersion); }
return this.executeCommand<void>(argsArray, additionalEnvVars, session);
} }
} }
}, },
sql: { sql: {
mi: { mi: {
delete: (name: string): Promise<azdataExt.AzdataOutput<void>> => { delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name]); return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, session);
}, },
list: (): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => { list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list']); return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, session);
}, },
show: (name: string): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => { show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name]); return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars, session);
}, },
edit: ( edit: (
name: string, name: string,
@@ -154,21 +185,69 @@ export class AzdataTool implements azdataExt.IAzdataApi {
memoryLimit?: string, memoryLimit?: string,
memoryRequest?: string, memoryRequest?: string,
noWait?: boolean, noWait?: boolean,
}): Promise<azdataExt.AzdataOutput<void>> => { },
additionalEnvVars?: azdataExt.AdditionalEnvVars,
session?: azdataExt.AzdataSession
): Promise<azdataExt.AzdataOutput<void>> => {
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name]; const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); } if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); } if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); }
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); } if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); } if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
if (args.noWait) { argsArray.push('--no-wait'); } if (args.noWait) { argsArray.push('--no-wait'); }
return this.executeCommand<void>(argsArray); return this.executeCommand<void>(argsArray, additionalEnvVars, session);
} }
} }
} }
}; };
public login(endpoint: string, username: string, password: string): Promise<azdataExt.AzdataOutput<void>> { public async login(endpoint: string, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<azdataExt.AzdataOutput<void>> {
return this.executeCommand<void>(['login', '-e', endpoint, '-u', username], { 'AZDATA_PASSWORD': password }); // Since login changes the context we want to wait until all currently executing commands are finished before this is executed
while (this._currentlyExecutingCommands.length > 0) {
await this._currentlyExecutingCommands[0];
}
// Logins need to be done outside the session aware logic so call impl directly
return this.executeCommandImpl<void>(['login', '-e', endpoint, '-u', username], Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }));
}
public async acquireSession(endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataSession> {
const session = new AzdataSession();
session.sessionEnded().then(async () => {
// Wait for all commands running for this session to end
while (this._currentlyExecutingCommands.length > 0) {
await this._currentlyExecutingCommands[0].promise;
}
this._currentSession = undefined;
// Start our next command now that we're all done with this session
// TODO: Should we check if the command has a session that hasn't started? That should never happen..
// TODO: Look into kicking off multiple commands
this._queuedCommands.shift()?.deferred.resolve();
});
// We're not in a session or waiting on anything so just set the current session right now
if (!this._currentSession && this._queuedCommands.length === 0) {
this._currentSession = session;
} else {
// We're in a session or another command is executing so add this to the end of the queued commands and wait our turn
const deferred = new Deferred<void>();
deferred.promise.then(() => {
this._currentSession = session;
// We've started a new session so look at all our queued commands and start
// the ones for this session now.
this._queuedCommands = this._queuedCommands.filter(c => {
if (c.session === this._currentSession) {
c.deferred.resolve();
return false;
}
return true;
});
});
this._queuedCommands.push({ deferred, session: undefined });
await deferred.promise;
}
await this.login(endpoint, username, password, additionalEnvVars);
return session;
} }
/** /**
@@ -186,7 +265,33 @@ export class AzdataTool implements azdataExt.IAzdataApi {
}; };
} }
public async executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>> { public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<R>> {
if (this._currentSession && this._currentSession !== session) {
const deferred = new Deferred<void>();
this._queuedCommands.push({ deferred, session: session });
await deferred.promise;
}
const executingDeferred = new Deferred<void>();
this._currentlyExecutingCommands.push(executingDeferred);
try {
return await this.executeCommandImpl<R>(args, additionalEnvVars);
}
finally {
this._currentlyExecutingCommands = this._currentlyExecutingCommands.filter(c => c !== executingDeferred);
executingDeferred.resolve();
// If there isn't an active session and we still have queued commands then we have to manually kick off the next one
if (this._queuedCommands.length > 0 && !this._currentSession) {
this._queuedCommands.shift()?.deferred.resolve();
}
}
}
/**
* Executes the specified azdata command. This is NOT session-aware so should only be used for calls that don't care about a session
* @param args The args to pass to azdata
* @param additionalEnvVars Additional environment variables to set for this execution
*/
private async executeCommandImpl<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>> {
try { try {
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout); const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
return { return {
@@ -607,7 +712,7 @@ async function discoverLatestStableAzdataVersionDarwin(): Promise<SemVer> {
return new SemVer(azdataPackageVersionInfo.versions.stable); return new SemVer(azdataPackageVersionInfo.versions.stable);
} }
async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: { [key: string]: string } = {}): Promise<ProcessOutput> { async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<ProcessOutput> {
additionalEnvVars = Object.assign(additionalEnvVars, { 'ACCEPT_EULA': 'yes' }); additionalEnvVars = Object.assign(additionalEnvVars, { 'ACCEPT_EULA': 'yes' });
const debug = vscode.workspace.getConfiguration(azdataConfigSection).get(debugConfigKey); const debug = vscode.workspace.getConfiguration(azdataConfigSection).get(debugConfigKey);
if (debug) { if (debug) {

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { AdditionalEnvVars } from 'azdata-ext';
import * as cp from 'child_process'; import * as cp from 'child_process';
import * as sudo from 'sudo-prompt'; import * as sudo from 'sudo-prompt';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
@@ -47,7 +48,7 @@ export type ProcessOutput = { stdout: string, stderr: string };
* @param args Optional args to pass, every arg and arg value must be a separate item in the array * @param args Optional args to pass, every arg and arg value must be a separate item in the array
* @param additionalEnvVars Additional environment variables to add to the process environment * @param additionalEnvVars Additional environment variables to add to the process environment
*/ */
export async function executeCommand(command: string, args: string[], additionalEnvVars?: { [key: string]: string },): Promise<ProcessOutput> { export async function executeCommand(command: string, args: string[], additionalEnvVars?: AdditionalEnvVars): Promise<ProcessOutput> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Logger.log(loc.executingCommand(command, args)); Logger.log(loc.executingCommand(command, args));
const stdoutBuffers: Buffer[] = []; const stdoutBuffers: Buffer[] = [];

View File

@@ -8,7 +8,7 @@
*/ */
export class Deferred<T> { export class Deferred<T> {
promise: Promise<T>; promise: Promise<T>;
resolve!: (value?: T | PromiseLike<T>) => void; resolve!: (value: T | PromiseLike<T>) => void;
reject!: (reason?: any) => void; reject!: (reason?: any) => void;
constructor() { constructor() {
this.promise = new Promise<T>((resolve, reject) => { this.promise = new Promise<T>((resolve, reject) => {

View File

@@ -22,7 +22,7 @@ export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
*/ */
export function searchForCmd(exe: string): Promise<string> { export function searchForCmd(exe: string): Promise<string> {
// Note : This is separated out to allow for easy test stubbing // Note : This is separated out to allow for easy test stubbing
return new Promise<string>((resolve, reject) => which(exe, (err, path) => err ? reject(err) : resolve(path))); return new Promise<string>((resolve, reject) => which(exe, (err, path) => err ? reject(err) : resolve(path || '')));
} }
/** /**

View File

@@ -65,7 +65,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
// register option source(s) // register option source(s)
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports; const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)); context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)));
return azdataApi; return azdataApi;
} }

View File

@@ -10,7 +10,7 @@ import * as azdataExt from 'azdata-ext';
* Class that provides options sources for an Arc Data Controller * Class that provides options sources for an Arc Data Controller
*/ */
export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider { export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider {
readonly optionsSourceId = 'arc.controller.config.profiles'; readonly id = 'arc.controller.config.profiles';
constructor(private _azdataExtApi: azdataExt.IExtension) { } constructor(private _azdataExtApi: azdataExt.IExtension) { }
async getOptions(): Promise<string[]> { async getOptions(): Promise<string[]> {
const isEulaAccepted = await this._azdataExtApi.isEulaAccepted(); const isEulaAccepted = await this._azdataExtApi.isEulaAccepted();

View File

@@ -18,9 +18,7 @@ export class AzdataToolService {
} }
/** /**
* Sets the localAzdata that was last saved * Sets the localAzdata object to be used for azdata operations
*
* @param memento The memento that stores the localAzdata object
*/ */
set localAzdata(azdata: IAzdataTool | undefined) { set localAzdata(azdata: IAzdataTool | undefined) {
this._localAzdata = azdata; this._localAzdata = azdata;

View File

@@ -3,45 +3,126 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as childProcess from '../common/childProcess';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import * as should from 'should';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { getExtensionApi } from '../api'; import { getExtensionApi, throwIfNoAzdataOrEulaNotAccepted } from '../api';
import { AzdataToolService } from '../services/azdataToolService'; import { AzdataToolService } from '../services/azdataToolService';
import { assertRejected } from './testUtils'; import { assertRejected } from './testUtils';
import { AzdataTool, IAzdataTool, AzdataDeployOption } from '../azdata';
describe('api', function (): void { describe('api', function (): void {
afterEach(function (): void { afterEach(function (): void {
sinon.restore(); 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 { describe('getExtensionApi', function (): void {
it('throws when no azdata', async function(): Promise<void> { it('throws when no azdata', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>(); const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const azdataToolService = new AzdataToolService(); const azdataToolService = new AzdataToolService();
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(undefined)); const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(undefined));
await assertRejected(api.isEulaAccepted(), 'isEulaAccepted'); await assertRejected(api.isEulaAccepted(), 'isEulaAccepted');
await assertApiCalls(api, assertRejected);
await assertRejected(api.azdata.getPath(), 'getPath');
await assertRejected(api.azdata.getSemVersion(), 'getSemVersion');
await assertRejected(api.azdata.login('', '', ''), 'login');
await assertRejected(api.azdata.version(), 'version');
await assertRejected(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
await assertRejected(api.azdata.arc.dc.config.list(), 'arc dc config list');
await assertRejected(api.azdata.arc.dc.config.show(), 'arc dc config show');
await assertRejected(api.azdata.arc.dc.endpoint.list(), 'arc dc endpoint list');
await assertRejected(api.azdata.arc.sql.mi.list(), 'arc sql mi list');
await assertRejected(api.azdata.arc.sql.mi.delete(''), 'arc sql mi delete');
await assertRejected(api.azdata.arc.sql.mi.show(''), 'arc sql mi show');
await assertRejected(api.azdata.arc.postgres.server.list(), 'arc sql postgres server list');
await assertRejected(api.azdata.arc.postgres.server.delete(''), 'arc sql postgres server delete');
await assertRejected(api.azdata.arc.postgres.server.show(''), 'arc sql postgres server show');
await assertRejected(api.azdata.arc.postgres.server.edit('', { }), 'arc sql postgres server edit');
}); });
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 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('', '1.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: `1.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('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');
});
/**
* 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('', '', ''), 'login');
await assertCallback((async () => {
let session: azdataExt.AzdataSession | undefined;
try {
session = await api.azdata.acquireSession('', '', '');
} finally {
session?.dispose();
}
})(), 'acquireSession');
await assertCallback(api.azdata.version(), 'version');
await assertCallback(api.azdata.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');
}
}); });
}); });

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as should from 'should'; import * as should from 'should';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
@@ -16,6 +17,7 @@ import * as fs from 'fs';
import { AzdataReleaseInfo } from '../azdataReleaseInfo'; import { AzdataReleaseInfo } from '../azdataReleaseInfo';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { eulaAccepted } from '../constants'; import { eulaAccepted } from '../constants';
import { sleep } from './testUtils';
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0'); const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0');
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999'); const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
@@ -170,18 +172,6 @@ describe('azdata', function () {
}); });
}); });
}); });
it('login', async function (): Promise<void> {
const endpoint = 'myEndpoint';
const username = 'myUsername';
const password = 'myPassword';
await azdataTool.login(endpoint, username, password);
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
});
it('version', async function (): Promise<void> {
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
await azdataTool.version();
verifyExecuteCommandCalledWithArgs(['--version']);
});
it('general error throws', async function (): Promise<void> { it('general error throws', async function (): Promise<void> {
const err = new Error(); const err = new Error();
executeCommandStub.throws(err); executeCommandStub.throws(err);
@@ -228,12 +218,136 @@ describe('azdata', function () {
}); });
}); });
it('login', async function (): Promise<void> {
const endpoint = 'myEndpoint';
const username = 'myUsername';
const password = 'myPassword';
await azdataTool.login(endpoint, username, password);
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
});
describe('acquireSession', function (): void {
it('calls login', async function (): Promise<void> {
const endpoint = 'myEndpoint';
const username = 'myUsername';
const password = 'myPassword';
const session = await azdataTool.acquireSession(endpoint, username, password);
session.dispose();
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
});
it('command executed under current session completes', async function (): Promise<void> {
const session = await azdataTool.acquireSession('', '', '');
try {
await azdataTool.arc.dc.config.show(undefined, session);
} finally {
session.dispose();
}
verifyExecuteCommandCalledWithArgs(['login'], 0);
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
});
it('multiple commands executed under current session completes', async function (): Promise<void> {
const session = await azdataTool.acquireSession('', '', '');
try {
// Kick off multiple commands at the same time and then ensure that they both complete
await Promise.all([
azdataTool.arc.dc.config.show(undefined, session),
azdataTool.arc.sql.mi.list(undefined, session)
]);
} finally {
session.dispose();
}
verifyExecuteCommandCalledWithArgs(['login'], 0);
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
});
it('command executed without session context is queued up until session is closed', async function (): Promise<void> {
const session = await azdataTool.acquireSession('', '', '');
let nonSessionCommand: Promise<any> | undefined = undefined;
try {
// Start one command in the current session
await azdataTool.arc.dc.config.show(undefined, session);
// Verify that the command isn't executed until after the session is disposed
let isFulfilled = false;
nonSessionCommand = azdataTool.arc.sql.mi.list().then(() => isFulfilled = true);
await sleep(2000);
should(isFulfilled).equal(false, 'The command should not be completed yet');
} finally {
session.dispose();
}
await nonSessionCommand;
verifyExecuteCommandCalledWithArgs(['login'], 0);
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
});
it('multiple commands executed without session context are queued up until session is closed', async function (): Promise<void> {
const session = await azdataTool.acquireSession('', '', '');
let nonSessionCommand1: Promise<any> | undefined = undefined;
let nonSessionCommand2: Promise<any> | undefined = undefined;
try {
// Start one command in the current session
await azdataTool.arc.dc.config.show(undefined, session);
// Verify that neither command is completed until the session is closed
let isFulfilled = false;
nonSessionCommand1 = azdataTool.arc.sql.mi.list().then(() => isFulfilled = true);
nonSessionCommand2 = azdataTool.arc.postgres.server.list().then(() => isFulfilled = true);
await sleep(2000);
should(isFulfilled).equal(false, 'The commands should not be completed yet');
} finally {
session.dispose();
}
await Promise.all([nonSessionCommand1, nonSessionCommand2]);
verifyExecuteCommandCalledWithArgs(['login'], 0);
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 2);
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'list'], 3);
});
it('attempting to acquire a second session while a first is still active queues the second session', async function (): Promise<void> {
const firstSession = await azdataTool.acquireSession('', '', '');
let sessionPromise: Promise<azdataExt.AzdataSession> | undefined = undefined;
let secondSessionCommand: Promise<any> | undefined = undefined;
try {
try {
// Start one command in the current session
await azdataTool.arc.dc.config.show(undefined, firstSession);
// Verify that none of the commands for the second session are completed before the first is disposed
let isFulfilled = false;
sessionPromise = azdataTool.acquireSession('', '', '');
sessionPromise.then(session => {
isFulfilled = true;
secondSessionCommand = azdataTool.arc.sql.mi.list(undefined, session).then(() => isFulfilled = true);
});
await sleep(2000);
should(isFulfilled).equal(false, 'The commands should not be completed yet');
} finally {
firstSession.dispose();
}
} finally {
(await sessionPromise)?.dispose();
}
should(secondSessionCommand).not.equal(undefined, 'The second command should have been queued already');
await secondSessionCommand!;
verifyExecuteCommandCalledWithArgs(['login'], 0);
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show'], 1);
verifyExecuteCommandCalledWithArgs(['login'], 2);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list'], 3);
});
});
it('version', async function (): Promise<void> {
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
await azdataTool.version();
verifyExecuteCommandCalledWithArgs(['--version']);
});
/** /**
* Verifies that the specified args were included in the call to executeCommand * Verifies that the specified args were included in the call to executeCommand
* @param args The args to check were included in the execute command call * @param args The args to check were included in the execute command call
*/ */
function verifyExecuteCommandCalledWithArgs(args: string[]): void { function verifyExecuteCommandCalledWithArgs(args: string[], callIndex = 0): void {
const commandArgs = executeCommandStub.args[0][1] as string[]; const commandArgs = executeCommandStub.args[callIndex][1] as string[];
args.forEach(arg => should(commandArgs).containEql(arg)); args.forEach(arg => should(commandArgs).containEql(arg));
} }
@@ -469,8 +583,8 @@ describe('azdata', function () {
}); });
}); });
describe('promptForEula', function(): void { describe('promptForEula', function (): void {
it('skipped because of config', async function(): Promise<void> { it('skipped because of config', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -479,7 +593,7 @@ describe('azdata', function () {
should(result).be.false(); should(result).be.false();
}); });
it('always prompt if user requested', async function(): Promise<void> { it('always prompt if user requested', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -490,7 +604,7 @@ describe('azdata', function () {
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user'); 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> { it('prompt if config set to do so', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -501,7 +615,7 @@ describe('azdata', function () {
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user'); 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> { it('update config if user chooses not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -513,7 +627,7 @@ describe('azdata', function () {
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user'); should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
}); });
it('user accepted EULA', async function(): Promise<void> { it('user accepted EULA', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -525,7 +639,7 @@ describe('azdata', function () {
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user'); should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
}); });
it('user accepted EULA - require user action', async function(): Promise<void> { it('user accepted EULA - require user action', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>(); const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt); configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object); sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
@@ -538,7 +652,7 @@ describe('azdata', function () {
}); });
}); });
describe('isEulaAccepted', function(): void { describe('isEulaAccepted', function (): void {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>(); const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true); mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
should(azdata.isEulaAccepted(mementoMock.object)).be.true(); should(azdata.isEulaAccepted(mementoMock.object)).be.true();

View File

@@ -52,7 +52,7 @@ describe('HttpClient', function (): void {
it('errors on write stream error', async function (): Promise<void> { it('errors on write stream error', async function (): Promise<void> {
const downloadFolder = os.tmpdir(); const downloadFolder = os.tmpdir();
const mockWriteStream = new PassThrough(); const mockWriteStream = new PassThrough();
const deferredPromise = new Deferred(); const deferredPromise = new Deferred<void>();
sinon.stub(fs, 'createWriteStream').callsFake(() => { sinon.stub(fs, 'createWriteStream').callsFake(() => {
deferredPromise.resolve(); deferredPromise.resolve();
return <any>mockWriteStream; return <any>mockWriteStream;

View File

@@ -9,7 +9,7 @@ import { Deferred } from '../../common/promise';
describe('DeferredPromise', function (): void { describe('DeferredPromise', function (): void {
it('Resolves correctly', async function(): Promise<void> { it('Resolves correctly', async function(): Promise<void> {
const deferred = new Deferred(); const deferred = new Deferred<void>();
deferred.resolve(); deferred.resolve();
await should(deferred.promise).be.resolved(); await should(deferred.promise).be.resolved();
}); });
@@ -21,7 +21,7 @@ describe('DeferredPromise', function (): void {
}); });
it('Chains then correctly', function(done): void { it('Chains then correctly', function(done): void {
const deferred = new Deferred(); const deferred = new Deferred<void>();
deferred.then( () => { deferred.then( () => {
done(); done();
}); });

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as should from 'should'; import * as should from 'should';
import { searchForCmd as searchForExe } from '../../common/utils'; import { NoAzdataError, searchForCmd as searchForExe } from '../../common/utils';
describe('utils', function () { describe('utils', function () {
describe('searchForExe', function (): void { describe('searchForExe', function (): void {
@@ -14,4 +14,13 @@ describe('utils', function () {
await should(searchForExe('someFakeExe')).be.rejected(); 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

@@ -18,3 +18,7 @@ export async function assertRejected(promise: Promise<any>, message: string): Pr
throw new Error(message); throw new Error(message);
} }
export async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -5,6 +5,7 @@
declare module 'azdata-ext' { declare module 'azdata-ext' {
import { SemVer } from 'semver'; import { SemVer } from 'semver';
import * as vscode from 'vscode';
/** /**
* Covers defining what the azdata extension exports to other extensions * Covers defining what the azdata extension exports to other extensions
@@ -16,6 +17,8 @@ declare module 'azdata-ext' {
name = 'Microsoft.azdata' name = 'Microsoft.azdata'
} }
export type AdditionalEnvVars = { [key: string]: string };
export interface ErrorWithLink extends Error { export interface ErrorWithLink extends Error {
messageWithLink: string; messageWithLink: string;
} }
@@ -219,6 +222,17 @@ declare module 'azdata-ext' {
state: string, // "Ready" state: string, // "Ready"
logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:pg1')) logSearchDashboard: string, // https://127.0.0.1:30777/kibana/app/kibana#/discover?_a=(query:(language:kuery,query:'custom_resource_name:pg1'))
metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=pg1 metricsDashboard: string, // https://127.0.0.1:30777/grafana/d/40q72HnGk/sql-managed-instance-metrics?var-hostname=pg1
podsStatus: {
conditions: {
lastTransitionTime: string, // "2020-08-19T17:05:39Z"
message?: string, // "containers with unready status: [fluentbit postgres telegraf]"
reason?: string, // "ContainersNotReady"
status: string, // "True"
type: string // "Ready"
}[],
name: string, // "pg-instancew-0",
role: string // "worker"
}[]
} }
} }
@@ -230,23 +244,25 @@ declare module 'azdata-ext' {
code?: number code?: number
} }
export interface AzdataSession extends vscode.Disposable { }
export interface IAzdataApi { export interface IAzdataApi {
arc: { arc: {
dc: { dc: {
create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string): Promise<AzdataOutput<void>>, create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
endpoint: { endpoint: {
list(): Promise<AzdataOutput<DcEndpointListResult[]>> list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcEndpointListResult[]>>
}, },
config: { config: {
list(): Promise<AzdataOutput<DcConfigListResult[]>>, list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcConfigListResult[]>>,
show(): Promise<AzdataOutput<DcConfigShowResult>> show(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<DcConfigShowResult>>
} }
}, },
postgres: { postgres: {
server: { server: {
delete(name: string): Promise<AzdataOutput<void>>, delete(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
list(): Promise<AzdataOutput<PostgresServerListResult[]>>, list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<PostgresServerListResult[]>>,
show(name: string): Promise<AzdataOutput<PostgresServerShowResult>>, show(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<PostgresServerShowResult>>,
edit( edit(
name: string, name: string,
args: { args: {
@@ -262,14 +278,17 @@ declare module 'azdata-ext' {
replaceEngineSettings?: boolean, replaceEngineSettings?: boolean,
workers?: number workers?: number
}, },
additionalEnvVars?: { [key: string]: string }): Promise<AzdataOutput<void>> engineVersion?: string,
additionalEnvVars?: AdditionalEnvVars,
session?: AzdataSession
): Promise<AzdataOutput<void>>
} }
}, },
sql: { sql: {
mi: { mi: {
delete(name: string): Promise<AzdataOutput<void>>, delete(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<void>>,
list(): Promise<AzdataOutput<SqlMiListResult[]>>, list(additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<SqlMiListResult[]>>,
show(name: string): Promise<AzdataOutput<SqlMiShowResult>>, show(name: string, additionalEnvVars?: AdditionalEnvVars, session?: AzdataSession): Promise<AzdataOutput<SqlMiShowResult>>,
edit( edit(
name: string, name: string,
args: { args: {
@@ -278,13 +297,24 @@ declare module 'azdata-ext' {
memoryLimit?: string, memoryLimit?: string,
memoryRequest?: string, memoryRequest?: string,
noWait?: boolean, noWait?: boolean,
} },
additionalEnvVars?: AdditionalEnvVars,
session?: AzdataSession
): Promise<AzdataOutput<void>> ): Promise<AzdataOutput<void>>
} }
} }
}, },
getPath(): Promise<string>, getPath(): Promise<string>,
login(endpoint: string, username: string, password: string): Promise<AzdataOutput<any>>, login(endpoint: string, username: string, password: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzdataOutput<void>>,
/**
* Acquires a session for the specified controller, which will log in to the specified controller and then block all other commands
* that are not part of the original session from executing until the session is released (disposed).
* @param endpoint
* @param username
* @param password
* @param additionalEnvVars
*/
acquireSession(endpoint: string, username: string, password: string, additionalEnvVars?: AdditionalEnvVars): Promise<AzdataSession>,
/** /**
* The semVersion corresponding to this installation of azdata. version() method should have been run * The semVersion corresponding to this installation of azdata. version() method should have been run
* before fetching this value to ensure that correct value is returned. This is almost always correct unless * before fetching this value to ensure that correct value is returned. This is almost always correct unless

View File

@@ -322,7 +322,7 @@
"dependencies": { "dependencies": {
"@azure/arm-resourcegraph": "^2.0.0", "@azure/arm-resourcegraph": "^2.0.0",
"@azure/arm-subscriptions": "1.0.0", "@azure/arm-subscriptions": "1.0.0",
"axios": "^0.19.2", "axios": "^0.21.1",
"qs": "^6.9.1", "qs": "^6.9.1",
"vscode-nls": "^4.0.0", "vscode-nls": "^4.0.0",
"ws": "^7.2.0" "ws": "^7.2.0"

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