From f0b82e49ddb5630704f4b108bfcf700857c3b286 Mon Sep 17 00:00:00 2001 From: Ben Gordon Date: Mon, 8 Feb 2021 17:55:34 -0600 Subject: [PATCH] V2.10.6 (#107) * initial fixes * git + package update * as3/biqiq done, starting cfgView updates * cfgView Welcome screen * changeLog/remove command from tenant/target * reworked for corkscrew configs in explosion * add new cache dir to vsce ignore * single target tenants for bigiq as3 fix * version HF bump to v2.10.1 * corkscrew v0.6.0 * leading slash work around * cs v0.7.0 + change log * v2.10.3 changeVersion command * update vsce ignore for cpuprofiling * more ignore file tweaks * fixed cfgExploreRawCorkscrew * orders + as3 map start * working old+new models/indexs * full as3 tree enhancement working * v2.10.4-beta version bump * single target fix * v.2.10.5 fix version bump * crgExplore App sort tweak * v2.10.6 start * as3->fast yaml template cmd complete * changelog linting * local corkscrew + remove seed log * deviceImport final * as3ToFastYml ADC/AS3 options * main corkscrew v0.8 * moving from beta to full release --- .gitignore | 3 + .markdownlint.yml | 2 + .vscode/extensions.json | 3 +- .vscodeignore | 8 +- CHANGELOG.md | 187 +++++- README.md | 16 +- docs/README.md | 12 +- docs/index.html | 70 +- package-lock.json | 329 +++++++--- package.json | 76 ++- snippets.json | 488 +++++++------- snippets/fastYamlSnippets.json | 29 + src/as3_data_modeling.ts | 243 +++++++ src/cfgExplorer.ts | 85 ++- src/changeVersion.ts | 77 +++ src/deviceImport.ts | 4 +- src/extension.ts | 694 ++++++++++++-------- src/fastCore.ts | 193 ++++++ src/treeViewsProviders/as3TreeProvider.ts | 568 +++++++++++++--- src/treeViewsProviders/cfgTreeProvider.ts | 140 ++-- src/treeViewsProviders/hostsTreeProvider.ts | 135 ++-- src/utils/as3Models.ts | 615 +++++++++++++++++ src/utils/f5DeviceClient.ts.old | 445 +++++++++++++ src/utils/rpmMgmt.ts | 12 +- 24 files changed, 3444 insertions(+), 990 deletions(-) create mode 100644 .markdownlint.yml create mode 100644 snippets/fastYamlSnippets.json create mode 100644 src/as3_data_modeling.ts create mode 100644 src/changeVersion.ts create mode 100644 src/fastCore.ts create mode 100644 src/utils/as3Models.ts create mode 100644 src/utils/f5DeviceClient.ts.old diff --git a/.gitignore b/.gitignore index 3f99ae5..9e67497 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules # ignore ilx rpm cache directory atc_ilx_rpm_cache +cache .vscode-test/ # ignore any extension install files and zips *.vsix @@ -22,3 +23,5 @@ atc_ilx_rpm_cache *.tmpl # ignore temp mini_ucs for config explorere (corkscrew) mini_ucs.tar.gz +# ignore node cpu profiling outputs +*.cpuprofile diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..45c49d1 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,2 @@ +MD024: false +MD013: false \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3ac9aeb..9064be7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "dbaeumer.vscode-eslint" + "bitwisecook.iapp", + "bitwisecook.irule" ] } diff --git a/.vscodeignore b/.vscodeignore index 39c90d2..d8b2848 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -14,9 +14,13 @@ vsc-extension-quickstart.md **/*.map **/*.ts # **/*.gif +# ignore cache directories atc_ilx_rpm_cache/** +cache fastTemplateFolderUploadTemp/** # ignore test seed file -.vscode-f5.json +*.vscode-f5.json # ignore github actions -.github/** \ No newline at end of file +.github/** +# ignore node cpu profiling file +*.cpuprofile \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 48d93c8..81a523d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,125 @@ All notable changes to the "vscode-f5" extension will be documented in this file. -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file + +--- + +## [2.10.6] - (02-02-2021) + +### Added + +- as3 to fast yaml conversion command + - takes an as3 declaration and converts it to a FAST YAML template + - detects ADC vs AS3 declaration parent level + - includes the first step of changing the tenant definition to a template parameter +- command to list and download other extension versions on github + +### Modified + +- fixed cfgExplore/App sorting +- corkscrew v0.8 + - will now error on application parsing, but continue with next application + - error does not stop entire process + - converts \r\n line returns to \n + - loosened file checking for parent tmos objects + - Added data-group extraction from irules +- fixed app component counts in as3 view + +### Removed + +- removed log that indicated seed file was not found on extension load + - this seemed to cause unnecessary confusion + +--- + +## [2.10.5] - (02-02-2021) + +### Modified + +- fixed single target render problem (again) + +--- + +## [2.10.4] - (02-02-2021) + +### Modified + +- AS3 view enhanced to show targets/tenants/apps/app-components + - including app component counts + - hover/tooltip includes tenant/app/app-component information when possible +- AS3 targets/tenants/apps are now alphabetically ordered +- Config Explorer apps are now alphabetically ordered +- Get all tenants declarations for a target + +--- + +## [2.10.3] - (01-27-2021) + +### Added + +- command to select and download github releases of the extension to allow users easy access to future beta versions + - Like RPM mgmt, it will query github for all the releases (including betas) and provide a list for the user to select the desired version. It will then attempt to install the version. Success on the install command is very subjective, but at least it will provide the user with the path to the file so it can be installed through the UI + +### Modified + +- fixed cfgExploreRawCorkscrew command input path bug + +--- + +## [2.10.2] - (01-25-2021) + +### Modified + +- updated f5-corkscrew to v0.7.0 + - fixed a bug where extracted irules were missing a closing bracket + - fixed a bug that was causing application extractions to fail + - removed logic that attempted to discover pools reference via variables in irules + - improved speed by removing some unecessary JSON.stringify/parsing + - converted most functions to async + - this allows errors to bubble up from deep within the code + - added extractApp events + - This was feed back into the OUTPUT for better understanding where processing is and possibly where it failed + +--- + +## [2.10.1] - (01-20-2021) + +### Modified + +- fixed problem where single bigiq as3 target did not track target details and looked like local as3 declaration + - this included adding an object description noting it's target + +--- + +## [2.10.0] - (01-19-2021) + +### Modified + +- corkscrew updates + - corkscrew returns source config files in explosion output to more easily import into extension view +- Config Explorer + - Clearing config explorer no longer makes it inoperable + - Config explorer view is now always visible and has welcome options for accesing documentation and importing local files + - Now supports browsing and importing files through local file system +- fixed a bug where TS command enablement was tied to DO installed +- bigiq/as3 integration + - displays targets/tenants appropriately + - get/modify/repost declaration for target tenant +- Updated docs: + - Added "Edit in Github" and modified date header to each page + - fixed changelog reference --- ## [2.8.2] - (11-05-2020) ### Added + - Refresh command for Config Explerer view ### Modified + - Major documentation updates for new doc site and features - Tweaked some error handling for exiting mid device mgmt workflows - Updated Documentation view with latest changes @@ -23,16 +132,17 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [2.8.1] - (11-02-2020) ### Modified + - Fixed where clearing of password was not happening in all scenarios - Fixed fast template uploading from non-windows based file systems - Fixed config explore tree refresh when new config is 'explored' - --- ## [2.8.0] - (10-30-2020) ### Added + - function to attempt to remove old extension if detected - the functions/settings it provides will conflict with new/reBranded extension - Finished function to inject/remove schema reference from declaration @@ -40,8 +150,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - if a schema reference is present, it will remove it - if no schema is present, it will attempt to discover the declaration type (as3/do/ts) and inject the appropriate schema reference - If the declaration is not a valid json object or it is not able to figure out what kind of ATC declaration it is, then it will prompt a given selection to inject anyway - - https://f5devcentral.github.io/vscode-f5/#/schema_validation?id=injectremove-schema-reference-command - + - --- @@ -84,6 +193,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [2.5.1] - (10-7-2020) ### Modified + - TMOS Config Explorer - Updates for corkscrew v2 - Includes faster processing, mini_ucs fetch of connected device, Base config (vlans/Selfs), all partitions configs @@ -108,8 +218,8 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [2.4.0] - (9-30-2020) - ### Modified + - FAST Template Render HTML preview - now respects tab settings like other windows - Re-renders appropriately when sending new content (like changing template params) @@ -129,6 +239,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [2.3.0] - (9-8-2020) ### Added + - OUTPUT Logging - Moved most console.log() information to the OUTPUT window at the bottom of the editor - Channnel name is `f5-fast` @@ -139,8 +250,8 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - Examples shows terminal connecting to ssh and tailing ltm logs - then disconnecting ssh when extension disconnects - ### Modified + - Editor tab mgmt/re-use - This includes displaying json and HTTP responses with json - Tabs are now managed and re-used as configured @@ -174,6 +285,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [2.2.0] - (8-21-2020) ### Added + - tcl/iRule/iApp functionality - create/modify/delete irules - upload/import/create/modify/delete iApp templates @@ -181,9 +293,10 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - included options for redeploying iApp-App with current paramters and deleting an iApp-App - `Merge TCL/TMOS` can be used to merge ANY TMOS config item -Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs/tcl.md +Documentation: ### Modified + - extension now has dynamic config-settings changes, meaning, when an extension setting is changed, it is applied to extension without reload of workspace --- @@ -191,6 +304,7 @@ Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs ## [2.1.0] - (8-5-2020) ### Added + - Make HTTP/S Requests function provides the necessary flexbility to make any API call to ANYthing (mostly...) - Highlight text, right-click, select: `Make HTTPS Request` - support raw URL, json and yaml structures @@ -198,21 +312,21 @@ Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs - Also accepts enough parameters to craft an external API for any destination - Includes connection status pop up and error handling like other api calls in the extension - Documented usage and examples: - - * [Crafting raw API calls](./README_docs/rawApiCalls.md) - + - [Crafting raw API calls](./README_docs/rawApiCalls.md) ### Modified + - Added nodejs nock for api tests - Started refactoring device mgmt functions for automated testing - Device Add/Remove - allowing entry/command functions to take parameters that would normally be collected from the user by some sort of input, like a click or input/select box - --- ## [2.0.1] - (7-28-2020) ### Added + - Function to migration legacy devices config to new devices config --- @@ -220,6 +334,7 @@ Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs ## [2.0.0] - (7-28-2020) ### Added + - ATC rpm install/un-install/upgrade - will download and cache rpm from official github repo - cache is located in %USERPROFILE%\.vscode\extensions\%extensionInstall%\atc_ilx_rpm_cache @@ -237,6 +352,7 @@ Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs - Also added function to show configured logon provider (right-click device in list) ### Modified + - Combined AS3 Tenant and Tasks views - This should provider a cleaner and more efficient interface - Now showing number of configured tenants and tasks @@ -254,13 +370,14 @@ Documentation: https://github.com/f5devcentral/vscode-f5/blob/master/README_docs Created a git repo for documenting the building of fast templates and a bunch of other things for demo'ing the extension -> https://github.com/DumpySquare/f5-fasting +> --- ## [1.0.1-2] - (6-23-2020) ### Changed + - Documentation tweaks --- @@ -297,20 +414,22 @@ Should cover most prominent F5 (A)utomated (T)ool(C)hain workflows (FAST/AS3/DO/ ## [0.1.11] - (6-4-2020) -- Fixed the following -``` -CVE-2020-7598 -moderate severity -Vulnerable versions: < 0.2.1 -Patched version: 0.2.1 -minimist before 1.2.2 could be tricked into adding or modifying properties of Object.prototype using a "constructor" or "proto" payload. -``` +- Fixed the following + + ```text + CVE-2020-7598 + moderate severity + Vulnerable versions: < 0.2.1 + Patched version: 0.2.1 + minimist before 1.2.2 could be tricked into adding or modifying properties of Object.prototype using a "constructor" or "proto" payload. + ``` --- ## [0.1.10] - (6-4-2020) ### Added + - Populated fast view on left - list deployed fast apps - click to view individual app configurations and deployment constants @@ -334,6 +453,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.9] - (5-31-2020) ### Added + - Added warning when posting syncronous DO dec that async is highly recommended - Defined file types for the different ATC services (as3/do/ts) to provide auto schema validation - This happend with files from a defined workspace, like opening a folder @@ -342,7 +462,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob - *.ts.json - will auto reference the latest online ts schema - Added following right click on as3 tenant options - - https://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/latest/refguide/as3-api.html + - - show=full - show=expanded @@ -364,14 +484,16 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.8] - (5-25-2020) ### Modified + - Now allowing all http responses so it would show more information about failing declarations - Was only allowing 200/201/202/404/422 - This was to allow for more robust error handling for async post operations - Updated password prompt to provide more clarity of what is expected - Refined conditions that clear cached passwords - - [issue #19]https://github.com/f5devcentral/vscode-f5/issues/19 + - [issue #19] ### Added + - Auto-refresh AS3 trees after tenant delete or declaration post - includes a slight pause to let processing complete before refresh - AS3 async post @@ -384,6 +506,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.7] - (5-20-2020) ### Modified + - More work to allow port specification on device item: user@device.domain.net:8443 - Added more feedback (warning pop up) for failed api calls - Documentation on client side logging and BIG-IQ usage @@ -393,15 +516,17 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.6] - (5-19-2020) ### Modified + - Device add/modify - Relaxed regex to allow :port for single nic ve - - [issue #5] https://github.com/f5devcentral/vscode-f5/issues/5 + - [issue #5] --- ## [0.1.5] - (5-18-2020) ### Added + - AS3 Delete Tenant command - Right click tenant item in tree - auto refresh AS3 trees when AS3 service detected - Documentation in README, including demo gifs of workflows @@ -411,6 +536,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.4] - (5-15-2020) ### Added + - AS3 tenants tree - two level tree hiarchy representing deployed tenant and apps for each tenant - click to get 'Get-All-Tenants' to get ALL declarations @@ -421,6 +547,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.3] - (5-13-2020) ### Added + - Tree view to display TS examples from github - get/post Delcaratinve Onboarding (DO) declarations - base example snippets for as3/do/ts @@ -435,6 +562,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.2] - (5-10-2020) ### Added + - AS3/DO/TS service checking - display in tool bar with version if installed - GET/POST TS declaration - Execute BASH command on device @@ -444,11 +572,13 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.1.1] - (5-8-2020) ### Added + - password caching with keytar ## [0.1.0] - (5-7-2020) ### Added + - load sample as3 declaration - post as3 declaration to connected F5 @@ -457,6 +587,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.0.4] - (5-4-2020) ### Added + - Device Tree features - Modify entry - Remove entry @@ -467,6 +598,7 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.0.3] - (4-26-2020) ### Added + - BIG-IP authentication via auth token - Device tree refresh button @@ -475,9 +607,10 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.0.2] - (4-26-2020) ### Added + - Tree View container with F5 icon - - list configured devices from config file - - testing with carTreeView and depenencyTreeView + - list configured devices from config file + - testing with carTreeView and depenencyTreeView - Status bar information about connected device --- @@ -485,6 +618,6 @@ minimist before 1.2.2 could be tricked into adding or modifying properties of Ob ## [0.0.1] - (4-24-2020) - Initial release! - - Make api to get Chuck Norris joke, display in new editor window for testing, learning and laughs... - - Started extension settings to host devices - - Command skeleton for next call to be coded \ No newline at end of file + - Make api to get Chuck Norris joke, display in new editor window for testing, learning and laughs... + - Started extension settings to host devices + - Command skeleton for next call to be coded diff --git a/README.md b/README.md index be67dd7..67a5769 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,16 @@ Any comments, questions or feature requests, please open a github repository iss ### New documentation site!!! > https://f5devcentral.github.io/vscode-f5/#/ - +> > this page is in progress as we transition documents to the new site above -If you mention the words "F5" and "API" with any sort of consistency, then you need to be using The F5 VSCode Extension... +If you consistently use APIs while working with F5s, the F5 VSCode Extension needs to be a permanent addition to your toolkit! The F5 VSCode Extension will not only only supercharge your abilities to write (A)utomated (T)ool(C)hain declarations with snippets, examples and declaration schema validation, but also assist with connecting, deploying, retrieving and updating declarations on F5 devices. -If that wasn't enough to get your attention, the extension can also... +If you are not excited yet, the extension can also: - GET/POST/DELETE of all ATC services, including FAST/AS3/DO/TS - links to quickly open related ATC documentation @@ -37,6 +37,14 @@ If that wasn't enough to get your attention, the extension can also... The best path is to install Visual Studio Code from: https://code.visualstudio.com/ +### VSCode Marketplace + +- https://marketplace.visualstudio.com/items?itemName=F5DevCentral.vscode-f5 + +### Open Source Marketplace + +- https://open-vsx.org/extension/F5DevCentral/vscode-f5 + Then install the extension following the steps below: Select the extensions view @@ -157,7 +165,7 @@ Different ways to install vsix: - Clone and install dependencies: ```bash git clone https://github.com/f5devcentral/vscode-f5.git - cd cd vscode-f5-fast/ + cd vscode-f5-fast/ npm install code . ``` diff --git a/docs/README.md b/docs/README.md index 3f650a4..72b3324 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,11 +2,11 @@ # The F5 VSCode Extension -If you mention the words "F5" and "API" with any sort of consistency, then you need to be using The F5 VSCode Extension... +If you consistently use APIs while working with F5s, the F5 VSCode Extension needs to be a permanent addition to your toolkit! The F5 VSCode Extension will not only only supercharge your abilities to write (A)utomated (T)ool(C)hain declarations with snippets, examples and declaration schema validation, but also assist with connecting, deploying, retrieving and updating declarations on F5 devices. -If that wasn't enough to get your attention, the extension can also... +If you are not excited yet, the extension can also: - GET/POST/DELETE of all ATC services, including FAST/AS3/DO/TS - links to quickly open related ATC documentation @@ -22,6 +22,14 @@ If that wasn't enough to get your attention, the extension can also... The best path is to install Visual Studio Code from: https://code.visualstudio.com/ +### VSCode Marketplace + +- https://marketplace.visualstudio.com/items?itemName=F5DevCentral.vscode-f5 + +### Open Source Marketplace + +- https://open-vsx.org/extension/F5DevCentral/vscode-f5 + Then install the extension following the steps below: Select the extensions view diff --git a/docs/index.html b/docs/index.html index e55b1d6..7c4776c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,16 +11,16 @@ - - - - - - - - - - + + + + + + + + + + @@ -28,7 +28,7 @@ - + @@ -98,7 +110,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 378b5a6..df3b2c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-f5", - "version": "2.8.2", + "version": "2.10.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -68,6 +68,12 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, "@types/decompress": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.3.tgz", @@ -101,9 +107,9 @@ } }, "@types/js-yaml": { - "version": "3.12.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.5.tgz", - "integrity": "sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww==" + "version": "3.12.6", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.6.tgz", + "integrity": "sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ==" }, "@types/json-schema": { "version": "7.0.6", @@ -124,9 +130,9 @@ "dev": true }, "@types/node": { - "version": "12.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.1.tgz", - "integrity": "sha512-/xaVmBBjOGh55WCqumLAHXU9VhjGtmyTGqJzFBXRWZzByOXI5JAJNx9xPVGEsNizrNwcec92fQMj458MWfjN1A==" + "version": "12.19.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.16.tgz", + "integrity": "sha512-7xHmXm/QJ7cbK2laF+YYD7gb5MggHIIQwqyjin3bpEGiSuvScMQ5JZZXPvRipi1MwckTQbJZROMns/JxdnIL1Q==" }, "@types/uuid": { "version": "8.3.0", @@ -134,23 +140,23 @@ "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" }, "@types/vscode": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.50.0.tgz", - "integrity": "sha512-QnIeyi4L2DiD9M2bAQKRzT/EQvc80qP9UL6JD5TiLlNRL1khIDg4ej4mDSRbtFrDAsRntFI1RhMvdomUThMsqg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.53.0.tgz", + "integrity": "sha512-XjFWbSPOM0EKIT2XhhYm3D3cx3nn3lshMUcWNy1eqefk+oqRuBq8unVb6BYIZqXy9lQZyeUl7eaBCOZWv+LcXQ==", "dev": true }, "@types/yargs": { - "version": "15.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", - "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { "version": "2.34.0", @@ -245,12 +251,12 @@ "dev": true }, "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "debug": "4" } }, "ajv": { @@ -424,6 +430,22 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -440,6 +462,12 @@ "readable-stream": "^3.4.0" } }, + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -497,6 +525,18 @@ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" }, + "buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -513,6 +553,15 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -719,12 +768,12 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, "decamelize": { @@ -892,6 +941,32 @@ "esutils": "^2.0.2" } }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -905,21 +980,6 @@ "once": "^1.4.0" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1139,8 +1199,8 @@ } }, "f5-corkscrew": { - "version": "git+https://github.com/f5devcentral/f5-corkscrew.git#a15106ccc6e7d502986a272c4f0c4fe13ff1d578", - "from": "git+https://github.com/f5devcentral/f5-corkscrew.git#a15106ccc6e7d502986a272c4f0c4fe13ff1d578", + "version": "git+https://github.com/f5devcentral/f5-corkscrew.git#7b0bc49b31e5eab81536c3a9cff140e06d525968", + "from": "git+https://github.com/f5devcentral/f5-corkscrew.git", "requires": { "@types/decompress": "^4.2.3", "@types/deepmerge": "^2.2.0", @@ -1154,9 +1214,9 @@ }, "dependencies": { "cliui": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.3.tgz", - "integrity": "sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -1164,9 +1224,9 @@ } }, "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "wrap-ansi": { "version": "7.0.0", @@ -1184,23 +1244,23 @@ "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" }, "yargs": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.0.tgz", - "integrity": "sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", - "y18n": "^5.0.2", + "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.3.tgz", - "integrity": "sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww==" + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" } } }, @@ -1314,6 +1374,18 @@ "dev": true, "optional": true }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -1447,23 +1519,24 @@ "dev": true }, "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "agent-base": "4", - "debug": "3.1.0" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" } }, "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "6", + "debug": "4" } }, "iconv-lite": { @@ -1638,9 +1711,9 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1729,6 +1802,12 @@ "type-check": "~0.3.2" } }, + "listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1862,9 +1941,9 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -1978,6 +2057,16 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2142,9 +2231,9 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "mustache": { @@ -2182,32 +2271,15 @@ "dev": true }, "nock": { - "version": "13.0.4", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.4.tgz", - "integrity": "sha512-alqTV8Qt7TUbc74x1pKRLSENzfjp4nywovcJgi/1aXDiUxXdt7TkruSTF5MDWPP7UoPVgea4F9ghVdmX0xxnSA==", + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.7.tgz", + "integrity": "sha512-WBz73VYIjdbO6BwmXODRQLtn7B5tldA9pNpWJe5QTtTEscQlY5KXU4srnGzBOK2fWakkXj69gfTnXGzmrsaRWw==", "dev": true, "requires": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "lodash.set": "^4.3.2", "propagate": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "node-abi": { @@ -2558,6 +2630,12 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2798,6 +2876,12 @@ "is-number": "^7.0.0" } }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -2851,6 +2935,41 @@ "through": "^2.3.8" } }, + "unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "dev": true, + "requires": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", @@ -2908,14 +3027,26 @@ "integrity": "sha1-9j/+2iSL8opnqNSODjtGGhZluvg=" }, "vscode-test": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.4.0.tgz", - "integrity": "sha512-Jt7HNGvSE0+++Tvtq5wc4hiXLIr2OjDShz/gbAfM/mahQpy4rKBnmOK33D+MR67ATWviQhl+vpmU3p/qwSH/Pg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.5.0.tgz", + "integrity": "sha512-svwE/mhBBqrB77C1U7pkUKfUmxnkzg0dLGi1vEmitsleu88oNsqZEhG3ANZrL/Ia4m0CW0oYEKRw2EojpFxLlQ==", "dev": true, "requires": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.4", - "rimraf": "^2.6.3" + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "rimraf": "^3.0.2", + "unzipper": "^0.10.11" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "which": { diff --git a/package.json b/package.json index fd991ea..b97d3ff 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "The F5 Extension", "description": "Supercharge your F5 automation development", "publisher": "F5DevCentral", - "version": "2.8.2", + "version": "2.10.6", "keywords": [ "F5", "F5Networks", @@ -50,6 +50,10 @@ { "language": "json", "path": "./snippets.json" + }, + { + "language": "yaml", + "path": "./snippets/fastYamlSnippets.json" } ], "yamlValidation": [ @@ -81,6 +85,12 @@ } ] }, + "viewsWelcome": [ + { + "view": "cfgTree", + "contents": "Import TMOS Config to explore.\n[Documenation](https://f5devcentral.github.io/vscode-f5/#/config_explorer)\n[Import .conf/UCS/QKVIEW from local file](command:f5.cfgExplore)\n[Import Corkscrew output](command:f5.cfgExploreRawCorkscrew)" + } + ], "views": { "f5-view-container": [ { @@ -108,7 +118,6 @@ }, { "id": "cfgTree", - "when": "f5.cfgTreeContxt", "name": "Config Explorer" } ] @@ -259,6 +268,11 @@ "title": "Connect to Device", "category": "F5" }, + { + "command": "f5.cfgExploreReveal", + "title": "Reveal Config Explorer", + "category": "F5" + }, { "command": "f5.disconnect", "title": "Disconnect from Device", @@ -469,6 +483,11 @@ "title": "Render Fast Template HTML Preview", "category": "F5-Fast" }, + { + "command": "f5-fast.as3ToFastYml", + "title": "AS3 -> FAST YAML", + "category": "F5-Fast" + }, { "command": "f5-fast.refreshTemplates", "title": "Refresh", @@ -485,12 +504,6 @@ "category": "F5-AS3", "enablement": "f5.as3Installed" }, - { - "command": "f5-as3.fullTenant", - "title": "Full Tenant", - "category": "F5-AS3", - "enablement": "view == as3Tenants" - }, { "command": "f5-as3.expandedTenant", "title": "Expanded Tenant", @@ -546,7 +559,7 @@ "command": "f5-ts.postDec", "title": "Post as TS Declaration", "category": "F5-TS", - "enablement": "f5.doInstalled" + "enablement": "f5.tsInstalled" }, { "command": "f5.editHost", @@ -588,6 +601,11 @@ "title": "base64 Decode", "category": "F5" }, + { + "command": "f5.changeVersion", + "title": "Change Extension Version", + "category": "F5" + }, { "command": "chuckJoke", "title": "Chuck Joke", @@ -617,6 +635,10 @@ "command": "f5-fast.renderHtmlPreview", "group": "F5" }, + { + "command": "f5-fast.as3ToFastYml", + "group": "F5" + }, { "command": "f5.injectSchemaRef", "group": "F5" @@ -862,10 +884,6 @@ "command": "f5-as3.deleteTenant", "when": "none" }, - { - "command": "f5-as3.fullTenant", - "when": "none" - }, { "command": "f5-as3.expandedTenant", "when": "none" @@ -918,36 +936,36 @@ "view/item/context": [ { "command": "f5.editHost", - "when": "view == f5Hosts", + "when": "view == f5Hosts && viewItem == f5Host", "group": "inline" }, { "command": "f5.editHost", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.editDeviceProvider", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.getProvider", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.removeHost", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.clearPassword", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.getF5HostInfo", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.cfgExploreOnConnect", - "when": "view == f5Hosts" + "when": "view == f5Hosts && viewItem == f5Host" }, { "command": "f5.cfgExplore-show", @@ -985,10 +1003,6 @@ "command": "f5-fast.renderHtmlPreview", "when": "view == fastView && viewItem == fastTemplate" }, - { - "command": "f5-as3.fullTenant", - "when": "view == as3Tenants && viewItem == as3Tenant" - }, { "command": "f5-as3.expandedTenant", "when": "view == as3Tenants && viewItem == as3Tenant" @@ -1014,23 +1028,23 @@ "devDependencies": { "@types/glob": "^7.1.3", "@types/mocha": "^7.0.1", - "@types/node": "^12.12.55", + "@types/node": "^12.19.16", "@types/vscode": "^1.42.0", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", "eslint": "^6.8.0", "glob": "^7.1.6", "mocha": "^8.1.3", - "nock": "^13.0.4", + "nock": "^13.0.7", "typescript": "^3.9.7", - "vscode-test": "^1.4.0" + "vscode-test": "^1.5.0" }, "dependencies": { "@f5devcentral/f5-fast-core": "^0.7.0", - "@types/js-yaml": "^3.12.5", + "@types/js-yaml": "^3.12.6", "axios": "^0.21.1", - "f5-corkscrew": "git+https://github.com/f5devcentral/f5-corkscrew.git#a15106ccc6e7d502986a272c4f0c4fe13ff1d578", - "js-yaml": "^3.14.0", + "f5-corkscrew": "git+https://github.com/f5devcentral/f5-corkscrew.git", + "js-yaml": "^3.14.1", "keytar": "^6.0.1" } } diff --git a/snippets.json b/snippets.json index 18cc9f7..a6732ba 100644 --- a/snippets.json +++ b/snippets.json @@ -1,246 +1,248 @@ { - "example_F5_AS3_declaration": { - "scope": "json", - "prefix": [ - "as3-Sample_01" - ], - "body": [ - "{", - " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/latest/as3-schema.json\",", - " \"class\": \"AS3\",", - " \"action\": \"deploy\",", - " \"persist\": true,", - " \"declaration\": {", - " \"class\": \"ADC\",", - " \"schemaVersion\": \"3.0.0\",", - " \"id\": \"urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d\",", - " \"label\": \"Sample 1\",", - " \"remark\": \"Simple HTTP application with RR pool\",", - " \"Sample_01\": {", - " \"class\": \"Tenant\",", - " \"A1\": {", - " \"class\": \"Application\",", - " \"template\": \"http\",", - " \"serviceMain\": {", - " \"class\": \"Service_HTTP\",", - " \"virtualAddresses\": [", - " \"10.0.1.10\"", - " ],", - " \"pool\": \"web_pool\"", - " },", - " \"web_pool\": {", - " \"class\": \"Pool\",", - " \"monitors\": [", - " \"http\"", - " ],", - " \"members\": [{", - " \"servicePort\": 80,", - " \"serverAddresses\": [", - " \"192.0.1.10\",", - " \"192.0.1.11\"", - " ]", - " }]", - " }", - " }", - " }", - " }", - "}" - ], + "example_F5_AS3_declaration": { + "scope": "json", + "prefix": [ + "as3-Sample_01" + ], + "body": [ + "{", + " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/latest/as3-schema.json\",", + " \"class\": \"AS3\",", + " \"action\": \"deploy\",", + " \"persist\": true,", + " \"declaration\": {", + " \"class\": \"ADC\",", + " \"schemaVersion\": \"3.0.0\",", + " \"id\": \"urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d\",", + " \"label\": \"Sample 1\",", + " \"remark\": \"Simple HTTP application with RR pool\",", + " \"Sample_01\": {", + " \"class\": \"Tenant\",", + " \"A1\": {", + " \"class\": \"Application\",", + " \"template\": \"http\",", + " \"serviceMain\": {", + " \"class\": \"Service_HTTP\",", + " \"virtualAddresses\": [", + " \"10.0.1.10\"", + " ],", + " \"pool\": \"web_pool\"", + " },", + " \"web_pool\": {", + " \"class\": \"Pool\",", + " \"monitors\": [", + " \"http\"", + " ],", + " \"members\": [", + " {", + " \"servicePort\": 80,", + " \"serverAddresses\": [", + " \"192.0.1.10\",", + " \"192.0.1.11\"", + " ]", + " }", + " ]", + " }", + " }", + " }", + " }", + "}" + ], + "description": "" + }, + "example_F5_DO_declaration": { + "prefix": [ + "do-sample1" + ], + "body": [ + "{", + " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-declarative-onboarding/master/src/schema/latest/base.schema.json\",", + " \"schemaVersion\": \"1.0.0\",", + " \"class\": \"Device\",", + " \"async\": true,", + " \"webhook\": \"https://example.com/myHook\",", + " \"label\": \"my BIG-IP declaration for declarative onboarding\",", + " \"Common\": {", + " \"class\": \"Tenant\",", + " \"mySystem\": {", + " \"class\": \"System\",", + " \"hostname\": \"bigip.example.com\",", + " \"cliInactivityTimeout\": 1200,", + " \"consoleInactivityTimeout\": 1200,", + " \"autoPhonehome\": false", + " },", + " \"myLicense\": {", + " \"class\": \"License\",", + " \"licenseType\": \"regKey\",", + " \"regKey\": \"AAAAA-BBBBB-CCCCC-DDDDD-EEEEEEE\"", + " },", + " \"myDns\": {", + " \"class\": \"DNS\",", + " \"nameServers\": [", + " \"8.8.8.8\",", + " \"2001:4860:4860::8844\"", + " ],", + " \"search\": [", + " \"f5.com\"", + " ]", + " },", + " \"myNtp\": {", + " \"class\": \"NTP\",", + " \"servers\": [", + " \"0.pool.ntp.org\",", + " \"1.pool.ntp.org\",", + " \"2.pool.ntp.org\"", + " ],", + " \"timezone\": \"UTC\"", + " },", + " \"root\": {", + " \"class\": \"User\",", + " \"userType\": \"root\",", + " \"oldPassword\": \"default\",", + " \"newPassword\": \"myNewPass1word\"", + " },", + " \"admin\": {", + " \"class\": \"User\",", + " \"userType\": \"regular\",", + " \"password\": \"asdfjkl\",", + " \"shell\": \"bash\"", + " },", + " \"guestUser\": {", + " \"class\": \"User\",", + " \"userType\": \"regular\",", + " \"password\": \"guestNewPass1\",", + " \"partitionAccess\": {", + " \"Common\": {", + " \"role\": \"guest\"", + " }", + " }", + " },", + " \"anotherUser\": {", + " \"class\": \"User\",", + " \"userType\": \"regular\",", + " \"password\": \"myPass1word\",", + " \"shell\": \"none\",", + " \"partitionAccess\": {", + " \"all-partitions\": {", + " \"role\": \"guest\"", + " }", + " }", + " },", + " \"myProvisioning\": {", + " \"class\": \"Provision\",", + " \"ltm\": \"nominal\",", + " \"gtm\": \"minimum\"", + " },", + " \"internal\": {", + " \"class\": \"VLAN\",", + " \"tag\": 4093,", + " \"mtu\": 1500,", + " \"interfaces\": [", + " {", + " \"name\": \"1.2\",", + " \"tagged\": true", + " }", + " ],", + " \"cmpHash\": \"dst-ip\"", + " },", + " \"internal-self\": {", + " \"class\": \"SelfIp\",", + " \"address\": \"10.10.0.100/24\",", + " \"vlan\": \"internal\",", + " \"allowService\": \"default\",", + " \"trafficGroup\": \"traffic-group-local-only\"", + " },", + " \"external\": {", + " \"class\": \"VLAN\",", + " \"tag\": 4094,", + " \"mtu\": 1500,", + " \"interfaces\": [", + " {", + " \"name\": \"1.1\",", + " \"tagged\": true", + " }", + " ],", + " \"cmpHash\": \"src-ip\"", + " },", + " \"external-self\": {", + " \"class\": \"SelfIp\",", + " \"address\": \"10.20.0.100/24\",", + " \"vlan\": \"external\",", + " \"allowService\": \"none\",", + " \"trafficGroup\": \"traffic-group-local-only\"", + " },", + " \"default\": {", + " \"class\": \"Route\",", + " \"gw\": \"10.10.0.1\",", + " \"network\": \"default\",", + " \"mtu\": 1500", + " },", + " \"managementRoute\": {", + " \"class\": \"ManagementRoute\",", + " \"gw\": \"1.2.3.4\",", + " \"network\": \"default\",", + " \"mtu\": 1500", + " },", + " \"myRouteDomain\": {", + " \"class\": \"RouteDomain\",", + " \"id\": 100,", + " \"bandWidthControllerPolicy\": \"bwcPol\",", + " \"connectionLimit\": 5432991,", + " \"flowEvictionPolicy\": \"default-eviction-policy\",", + " \"ipIntelligencePolicy\": \"ip-intelligence\",", + " \"enforcedFirewallPolicy\": \"enforcedPolicy\",", + " \"stagedFirewallPolicy\": \"stagedPolicy\",", + " \"securityNatPolicy\": \"securityPolicy\",", + " \"servicePolicy\": \"servicePolicy\",", + " \"strict\": false,", + " \"routingProtocols\": [", + " \"RIP\"", + " ],", + " \"vlans\": [", + " \"external\"", + " ]", + " },", + " \"dbvars\": {", + " \"class\": \"DbVariables\",", + " \"ui.advisory.enabled\": true,", + " \"ui.advisory.color\": \"green\",", + " \"ui.advisory.text\": \"/Common/hostname\"", + " }", + " }", + "}" + ], + "description": "" + }, + "example_F5_Telemetry_Streaming_declaration": { + "prefix": [ + "ts-sample1" + ], + "body": [ + "{", + " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-telemetry-streaming/master/src/schema/latest/base_schema.json\",", + " \"class\": \"Telemetry\",", + " \"My_System\": {", + " \"class\": \"Telemetry_System\",", + " \"systemPoller\": {", + " \"interval\": 60", + " }", + " },", + " \"My_Listener\": {", + " \"class\": \"Telemetry_Listener\",", + " \"port\": 6514", + " },", + " \"My_Consumer\": {", + " \"class\": \"Telemetry_Consumer\",", + " \"type\": \"Splunk\",", + " \"host\": \"192.0.2.1\",", + " \"protocol\": \"https\",", + " \"port\": 8088,", + " \"passphrase\": {", + " \"cipherText\": \"apikey\"", + " }", + " }", + "}" + ] + }, "description": "" - }, - "example_F5_DO_declaration": { - "prefix": [ - "do-sample1" - ], - "body": [ - "{", - " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-declarative-onboarding/master/src/schema/latest/base.schema.json\",", - " \"schemaVersion\": \"1.0.0\",", - " \"class\": \"Device\",", - " \"async\": true,", - " \"webhook\": \"https://example.com/myHook\",", - " \"label\": \"my BIG-IP declaration for declarative onboarding\",", - " \"Common\": {", - " \"class\": \"Tenant\",", - " \"mySystem\": {", - " \"class\": \"System\",", - " \"hostname\": \"bigip.example.com\",", - " \"cliInactivityTimeout\": 1200,", - " \"consoleInactivityTimeout\": 1200,", - " \"autoPhonehome\": false", - " },", - " \"myLicense\": {", - " \"class\": \"License\",", - " \"licenseType\": \"regKey\",", - " \"regKey\": \"AAAAA-BBBBB-CCCCC-DDDDD-EEEEEEE\"", - " },", - " \"myDns\": {", - " \"class\": \"DNS\",", - " \"nameServers\": [", - " \"8.8.8.8\",", - " \"2001:4860:4860::8844\"", - " ],", - " \"search\": [", - " \"f5.com\"", - " ]", - " },", - " \"myNtp\": {", - " \"class\": \"NTP\",", - " \"servers\": [", - " \"0.pool.ntp.org\",", - " \"1.pool.ntp.org\",", - " \"2.pool.ntp.org\"", - " ],", - " \"timezone\": \"UTC\"", - " },", - " \"root\": {", - " \"class\": \"User\",", - " \"userType\": \"root\",", - " \"oldPassword\": \"default\",", - " \"newPassword\": \"myNewPass1word\"", - " },", - " \"admin\": {", - " \"class\": \"User\",", - " \"userType\": \"regular\",", - " \"password\": \"asdfjkl\",", - " \"shell\": \"bash\"", - " },", - " \"guestUser\": {", - " \"class\": \"User\",", - " \"userType\": \"regular\",", - " \"password\": \"guestNewPass1\",", - " \"partitionAccess\": {", - " \"Common\": {", - " \"role\": \"guest\"", - " }", - " }", - " },", - " \"anotherUser\": {", - " \"class\": \"User\",", - " \"userType\": \"regular\",", - " \"password\": \"myPass1word\",", - " \"shell\": \"none\",", - " \"partitionAccess\": {", - " \"all-partitions\": {", - " \"role\": \"guest\"", - " }", - " }", - " },", - " \"myProvisioning\": {", - " \"class\": \"Provision\",", - " \"ltm\": \"nominal\",", - " \"gtm\": \"minimum\"", - " },", - " \"internal\": {", - " \"class\": \"VLAN\",", - " \"tag\": 4093,", - " \"mtu\": 1500,", - " \"interfaces\": [", - " {", - " \"name\": \"1.2\",", - " \"tagged\": true", - " }", - " ],", - " \"cmpHash\": \"dst-ip\"", - " },", - " \"internal-self\": {", - " \"class\": \"SelfIp\",", - " \"address\": \"10.10.0.100/24\",", - " \"vlan\": \"internal\",", - " \"allowService\": \"default\",", - " \"trafficGroup\": \"traffic-group-local-only\"", - " },", - " \"external\": {", - " \"class\": \"VLAN\",", - " \"tag\": 4094,", - " \"mtu\": 1500,", - " \"interfaces\": [", - " {", - " \"name\": \"1.1\",", - " \"tagged\": true", - " }", - " ],", - " \"cmpHash\": \"src-ip\"", - " },", - " \"external-self\": {", - " \"class\": \"SelfIp\",", - " \"address\": \"10.20.0.100/24\",", - " \"vlan\": \"external\",", - " \"allowService\": \"none\",", - " \"trafficGroup\": \"traffic-group-local-only\"", - " },", - " \"default\": {", - " \"class\": \"Route\",", - " \"gw\": \"10.10.0.1\",", - " \"network\": \"default\",", - " \"mtu\": 1500", - " },", - " \"managementRoute\": {", - " \"class\": \"ManagementRoute\",", - " \"gw\": \"1.2.3.4\",", - " \"network\": \"default\",", - " \"mtu\": 1500", - " },", - " \"myRouteDomain\": {", - " \"class\": \"RouteDomain\",", - " \"id\": 100,", - " \"bandWidthControllerPolicy\": \"bwcPol\",", - " \"connectionLimit\": 5432991,", - " \"flowEvictionPolicy\": \"default-eviction-policy\",", - " \"ipIntelligencePolicy\": \"ip-intelligence\",", - " \"enforcedFirewallPolicy\": \"enforcedPolicy\",", - " \"stagedFirewallPolicy\": \"stagedPolicy\",", - " \"securityNatPolicy\": \"securityPolicy\",", - " \"servicePolicy\": \"servicePolicy\",", - " \"strict\": false,", - " \"routingProtocols\": [", - " \"RIP\"", - " ],", - " \"vlans\": [", - " \"external\"", - " ]", - " },", - " \"dbvars\": {", - " \"class\": \"DbVariables\",", - " \"ui.advisory.enabled\": true,", - " \"ui.advisory.color\": \"green\",", - " \"ui.advisory.text\": \"/Common/hostname\"", - " }", - " }", - "}" - ], - "description": "" - }, - "example_F5_Telemetry_Streaming_declaration": { - "prefix": [ - "ts-sample1" - ], - "body": [ - "{", - " \"\\$schema\": \"https://raw.githubusercontent.com/F5Networks/f5-telemetry-streaming/master/src/schema/latest/base_schema.json\",", - " \"class\": \"Telemetry\",", - " \"My_System\": {", - " \"class\": \"Telemetry_System\",", - " \"systemPoller\": {", - " \"interval\": 60", - " }", - " },", - " \"My_Listener\": {", - " \"class\": \"Telemetry_Listener\",", - " \"port\": 6514", - " },", - " \"My_Consumer\": {", - " \"class\": \"Telemetry_Consumer\",", - " \"type\": \"Splunk\",", - " \"host\": \"192.0.2.1\",", - " \"protocol\": \"https\",", - " \"port\": 8088,", - " \"passphrase\": {", - " \"cipherText\": \"apikey\"", - " }", - " }", - "}" - ] - }, - "description": "" } \ No newline at end of file diff --git a/snippets/fastYamlSnippets.json b/snippets/fastYamlSnippets.json new file mode 100644 index 0000000..2de481f --- /dev/null +++ b/snippets/fastYamlSnippets.json @@ -0,0 +1,29 @@ +{ + "FAST YAML Base": { + "prefix": "fast", + "body": [ + "title: ${1:template title}", + "description: ${2:template description}", + "template: | ", + "" + ], + "description": "Base FAST YAML template properties" + }, + "FAST YAML Extended": { + "prefix": "fast", + "body": [ + "title: ${1:template title}", + "description: ${2:template description}", + "parameters:", + " tenant_name: ${3:default tenant name}", + "definitions: ", + " tenant_name:", + " title: Tenant Name", + " type: string", + " description: partition on bigip", + "template: | ", + "" + ], + "description": "Extended FAST YAML template" + } +} \ No newline at end of file diff --git a/src/as3_data_modeling.ts b/src/as3_data_modeling.ts new file mode 100644 index 0000000..c446abc --- /dev/null +++ b/src/as3_data_modeling.ts @@ -0,0 +1,243 @@ + + +interface As3App { + class: 'Application', + [key: string]: object | string, +} + +interface As3Declaration { + class: 'AS3', + [key: string]: AdcDeclaration | string +} + + +interface AdcDeclaration { + id: string; + class: string; + target?: As3Tenant | string | Target; + updateMode: string; + schemaVersion: string; + [key: string]: As3Tenant | string | Target | undefined +} + + +interface As3Tenant { + class: 'Tenant', + [key: string]: As3App | string +}; + +interface Target { + address?: string, + hostname?: string +}; + + +const some: AdcDeclaration[] = [ + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "10.200.244.5", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + core1_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.1.10", + "192.0.1.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + core1_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.2.10", + "192.0.2.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "10.200.244.6", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + core1_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.2.10", + "192.0.2.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + core1_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.1.10", + "192.0.1.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "192.168.200.131", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + tparty_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.1.20", + "19.0.1.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + tparty_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.2.20", + "19.0.2.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, + ]; \ No newline at end of file diff --git a/src/cfgExplorer.ts b/src/cfgExplorer.ts index eed1be5..0026996 100644 --- a/src/cfgExplorer.ts +++ b/src/cfgExplorer.ts @@ -11,7 +11,7 @@ import logger from './utils/logger'; -export async function getMiniUcs (): Promise { +export async function getMiniUcs(): Promise { return await window.withProgress({ location: ProgressLocation.Notification, @@ -24,9 +24,9 @@ export async function getMiniUcs (): Promise { return new Error(`User canceled External API Request`); }); - progress.report({ message: `Saving config on device`}); + progress.report({ message: `Saving config on device` }); logger.debug('Saving config on F5 using bash api, "tmsh save sys config"'); - + // /** // * save config before capturing mini_ucs!!! // * over bash api: tmsh save sys config @@ -38,12 +38,12 @@ export async function getMiniUcs (): Promise { command: 'save' } }); - - + + const tempFile = `mini_ucs.tar.gz`; - - progress.report({ message: `Collecting -> Downloading configs`}); - + + progress.report({ message: `Collecting -> Downloading configs` }); + // build mini ucs const ucsCmd = 'tar -czf /shared/images/${tempFile} /config/bigip.conf /config/bigip_base.conf /config/partitions'; logger.debug(`building mini_ucs on device with following command over bash api: ${ucsCmd}`); @@ -54,7 +54,7 @@ export async function getMiniUcs (): Promise { utilCmdArgs: `-c '${ucsCmd}'` } }); - + const coreDir = ext.context.extensionPath; const zipDown = path.join(coreDir, tempFile); @@ -68,9 +68,9 @@ export async function getMiniUcs (): Promise { } }); -} +} -export async function makeExplosion (file: string) { +export async function makeExplosion(file: string) { return await window.withProgress({ @@ -83,38 +83,59 @@ export async function makeExplosion (file: string) { logger.debug("User canceled External API Request"); return new Error(`User canceled External API Request`); }); - - progress.report({ message: `Unpacking Archive`}); - + + progress.report({ message: `Unpacking Archive` }); + + // look at moving this to the cfgExplorer view so further functions can be called after inintial explode (ie. add-defaults/add-file and possibly only extract the app details when clicked by user) This would make troubleshooting easier by isolating app extractions const bigipConf = new BigipConfig(); - + const parsedFileEvents = []; const parsedObjEvents = []; + const extractAppEvents = []; let currentFile = ''; - bigipConf.on('parseFile', async x => { + bigipConf.on('parseFile', async x => { parsedFileEvents.push(x); currentFile = `file: ${x.num} of ${x.of}`; - + // progress.report({ message: `Processing Config`}); }); - bigipConf.on('parseObject', async x => { + bigipConf.on('parseObject', async x => { parsedObjEvents.push(x); - progress.report({ message: `${currentFile}\n object: ${x.num} of ${x.of}`}); + progress.report({ message: `${currentFile}\n object: ${x.num} of ${x.of}` }); logger.debug(`Corkscrew parsing ${currentFile}, object: ${x.num} of ${x.of}`); }); - + + bigipConf.on('extractApp', async x => { + extractAppEvents.push(x); + logger.debug(`Corkscrew extracting app: ${x.app}, took: ${x.time}`); + }); + logger.debug(`Corkscrew -> Loading files`); - const loadTime = await bigipConf.load(file); - - logger.debug(`Corkscrew -> Parsing files`); - progress.report({ message: `Parsing Configs` }); - const parseTime = bigipConf.parse(); - - - const explosion = bigipConf.explode(); - logger.debug(`Corkscrew -> explosion stats:`, JSON.stringify(explosion.stats, undefined, 4)); - - return { config: bigipConf.configFiles, obj: bigipConf.configObject, explosion }; + + // load the .conf/ucs/qkview + return await bigipConf.load(file) + .then(async _ => { + + logger.debug(`Corkscrew -> Parsing files`); + progress.report({ message: `Parsing Configs` }); + + // then parse the configs + return await bigipConf.parse() + .then(async () => { + + // then extract apps + return await bigipConf.explode() + .then(explosion => { + logger.debug(`Corkscrew -> explosion stats:`, JSON.stringify(explosion.stats, undefined, 4)); + // return exp; + return { obj: bigipConf.configObject, explosion }; + }) + .catch(err => { + logger.error(err); + throw err; + }); + + }); + }); }); - // }); } \ No newline at end of file diff --git a/src/changeVersion.ts b/src/changeVersion.ts new file mode 100644 index 0000000..f158891 --- /dev/null +++ b/src/changeVersion.ts @@ -0,0 +1,77 @@ +'use-strict'; + +import * as path from 'path'; +import * as cp from 'child_process'; + +import { commands, ExtensionContext, window } from 'vscode'; +import logger from './utils/logger'; +import { getRPMgit, listGitReleases } from './utils/rpmMgmt'; + +/** + * Provides command to download github releases of this extension so users can easily access beta versions for testing + */ +export class ChangeVersion { + /** + * base repo releases + */ + readonly repo = 'https://api.github.com/repos/f5devcentral/vscode-f5/releases'; + + constructor(context: ExtensionContext) { + + context.subscriptions.push(commands.registerCommand('f5.changeVersion', async () => { + + // 1. list releases on github repo + // 2. provide list/selector for version + // 3. download version + // 4. install version, reload window? + + // get current extension home folder + // const hostDir = path.resolve(context.extensionPath, '..'); + const hostDir = context.extensionPath; + + logger.info('f5.changeVersion, fetching github releases'); + + // list available git releases and have the user select one + const chosenVersion: string | { label: string, asset: string } | undefined = await listGitReleases(this.repo) + .then( async versions => { + logger.debug('f5.changeVersion, available version', versions); + return await window.showQuickPick(versions, { placeHolder: 'Select Version' }); + }); + + logger.info('f5.changeVersion, downloading chosen version', chosenVersion); + + // if we have a selection from the previous list + if( chosenVersion && typeof chosenVersion === 'object' && chosenVersion.asset ) { + await getRPMgit(chosenVersion.asset, hostDir) + .then( resp => { + + // this command really doesn't work. + // https://github.com/microsoft/vscode-remote-release/issues/385 + // https://github.com/microsoft/vscode-remote-release/issues/2749 + // furthermore, code-server command for web based code would require detecting the environemnt and changing the command accordingly + logger.info('f5.changeVersion, this probably will not work, but at least the new extenion version will be downloaded and easily installable via the UI'); + + const cmd2Install = `code --install-extension ${resp}`; + logger.info('f5.changeVersion, installing: ', cmd2Install); + + try { + // try to issue the install command via node sub-process + const installResp = cp.execSync(cmd2Install).toString(); + logger.info('f5.changeVersion, install response', installResp); + + // TODO: this need some work. over remote-ssh file does not download and it doesn't fail appropriately + } catch (e) { + + logger.info('f5.changeVersion, failed install', e); + } + }) + .catch( err => { + logger.error('f5.changeVersion, failed download', err); + }); + } else { + logger.info('f5.changeVersion, valid selection details not detected, user probably exited quick pick dropdown'); + } + + })); + } +} \ No newline at end of file diff --git a/src/deviceImport.ts b/src/deviceImport.ts index 7bf8044..572231b 100644 --- a/src/deviceImport.ts +++ b/src/deviceImport.ts @@ -27,7 +27,9 @@ export async function deviceImportOnLoad(extPath: string, hostsTreeProvider: F5T * can also look into searching a workspace if there is one at start up */ } catch (e) { - return logger.debug('device seed file not found', e.message); + // 2.7.2021 -> disabled this log message since it was causing confusion + // return logger.debug('device seed file not found', e.message); + return; } const q = await window.showInformationMessage('Device seed file detected, would you like to import?', 'Yes', 'Yes-Consume', 'No'); diff --git a/src/extension.ts b/src/extension.ts index 03a9415..61f22b7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -27,10 +27,15 @@ import { TextDocumentView } from './editorViews/editorView'; import { getMiniUcs, makeExplosion } from './cfgExplorer'; import { unInstallOldExtension } from './extMigration'; import { injectSchema } from './atcSchema'; +import { ChangeVersion } from './changeVersion'; +import { FastCore } from './fastCore'; export async function activate(context: ExtensionContext) { - + + new ChangeVersion(context); + new FastCore(context); + await unInstallOldExtension(); // assign context to global name space @@ -49,12 +54,12 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(ext.doBar); ext.tsBar = window.createStatusBarItem(StatusBarAlignment.Left, 10); context.subscriptions.push(ext.tsBar); - + ext.connectBar = window.createStatusBarItem(StatusBarAlignment.Left, 9); context.subscriptions.push(ext.connectBar); ext.connectBar.command = 'f5.connectDevice'; - ext.connectBar.text = 'F5 -> Connect!'; + ext.connectBar.text = 'F5 -> Connect!'; ext.connectBar.tooltip = 'Click to connect!'; ext.connectBar.show(); @@ -87,17 +92,17 @@ export async function activate(context: ExtensionContext) { * http://patorjk.com/software/taag/#p=display&h=0&f=Banner3&t=Devices * ######################################################################### */ - - + + const hostsTreeProvider = new F5TreeProvider(''); window.registerTreeDataProvider('f5Hosts', hostsTreeProvider); commands.registerCommand('f5.refreshHostsTree', () => hostsTreeProvider.refresh()); - + context.subscriptions.push(commands.registerCommand('f5.connectDevice', async (device) => { - logger.info('selected device', device); // preferred at the moment + logger.info('selected device', device); // preferred at the moment - if(ext.mgmtClient) { + if (ext.mgmtClient) { ext.mgmtClient.disconnect(); } @@ -105,7 +110,7 @@ export async function activate(context: ExtensionContext) { device: string, provider: string }; - + if (!device) { const bigipHosts: Array | undefined = await workspace.getConfiguration().get('f5.hosts'); @@ -117,11 +122,11 @@ export async function activate(context: ExtensionContext) { * loop through config array of objects and build quickPick list appropriate labels * [ {label: admin@192.168.1.254:8443, target: { host: 192.168.1.254, user: admin, ...}}, ...] */ - const qPickHostList = bigipHosts.map( item => { + const qPickHostList = bigipHosts.map(item => { return { label: item.device, target: item }; }); - device = await window.showQuickPick(qPickHostList, {placeHolder: 'Select Device'}); + device = await window.showQuickPick(qPickHostList, { placeHolder: 'Select Device' }); if (!device) { throw new Error('user exited device input'); } else { @@ -129,13 +134,13 @@ export async function activate(context: ExtensionContext) { device = device.target; } } - + var [user, host] = device.device.split('@'); var [host, port] = host.split(':'); const password: string = await utils.getPassword(device.device); - ext.mgmtClient = new MgmtClient( device.device, { + ext.mgmtClient = new MgmtClient(device.device, { host, port, user, @@ -145,10 +150,10 @@ export async function activate(context: ExtensionContext) { const connect = await ext.mgmtClient.connect(); logger.debug(`F5 Connect Discovered ${JSON.stringify(connect)}`); - setTimeout( () => { tclTreeProvider.refresh();}, 300); + setTimeout(() => { tclTreeProvider.refresh(); }, 300); })); - + context.subscriptions.push(commands.registerCommand('f5.getProvider', async () => { const resp: any = await ext.mgmtClient?.makeRequest('/mgmt/tm/auth/source'); ext.panel.render(resp); @@ -157,11 +162,11 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.getF5HostInfo', async () => { var device: string | undefined = ext.hostStatusBar.text; - + if (!device) { device = await commands.executeCommand('f5.connectDevice'); } - + if (device === undefined) { throw new Error('no hosts in configuration'); } @@ -172,7 +177,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.disconnect', () => { - if(ext.mgmtClient) { + if (ext.mgmtClient) { ext.mgmtClient.disconnect(); ext.mgmtClient = undefined; // cfgProvider.clear(); @@ -191,55 +196,55 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.removeHost', async (hostID) => { return await hostsTreeProvider.removeDevice(hostID); })); - + context.subscriptions.push(commands.registerCommand('f5.editHost', async (hostID) => { - + logger.debug(`Edit Host command: ${JSON.stringify(hostID)}`); - - let bigipHosts: {device: string} [] | undefined= workspace.getConfiguration().get('f5.hosts'); + + let bigipHosts: { device: string }[] | undefined = workspace.getConfiguration().get('f5.hosts'); logger.debug(`Current bigipHosts: ${JSON.stringify(bigipHosts)}`); - + window.showInputBox({ - prompt: 'Update Device/BIG-IP/Host', + prompt: 'Update Device/BIG-IP/Host', value: hostID.label, ignoreFocusOut: true }) - .then( input => { + .then(input => { - logger.debug('user input', input); + logger.debug('user input', input); - if (input === undefined || bigipHosts === undefined) { - // throw new Error('Update device inputBox cancelled'); - logger.warn('Update device inputBox cancelled'); - return; - } + if (input === undefined || bigipHosts === undefined) { + // throw new Error('Update device inputBox cancelled'); + logger.warn('Update device inputBox cancelled'); + return; + } - const deviceRex = /^[\w-.]+@[\w-.]+(:[0-9]+)?$/; - const devicesString = JSON.stringify(bigipHosts); - - if (!devicesString.includes(`\"${input}\"`) && deviceRex.test(input)) { + const deviceRex = /^[\w-.]+@[\w-.]+(:[0-9]+)?$/; + const devicesString = JSON.stringify(bigipHosts); + + if (!devicesString.includes(`\"${input}\"`) && deviceRex.test(input)) { + + bigipHosts.forEach((item: { device: string; }) => { + if (item.device === hostID.label) { + item.device = input; + } + }); + + workspace.getConfiguration().update('f5.hosts', bigipHosts, ConfigurationTarget.Global); + setTimeout(() => { hostsTreeProvider.refresh(); }, 300); + } else { + + window.showErrorMessage('Already exists or invalid format: @:'); + } + }); - bigipHosts.forEach( (item: { device: string; }) => { - if(item.device === hostID.label) { - item.device = input; - } - }); - - workspace.getConfiguration().update('f5.hosts', bigipHosts, ConfigurationTarget.Global); - setTimeout( () => { hostsTreeProvider.refresh();}, 300); - } else { - - window.showErrorMessage('Already exists or invalid format: @:'); - } - }); - })); context.subscriptions.push(commands.registerCommand('f5.editDeviceProvider', async (hostID) => { - - let bigipHosts: {device: string} [] | undefined= workspace.getConfiguration().get('f5.hosts'); + + let bigipHosts: { device: string }[] | undefined = workspace.getConfiguration().get('f5.hosts'); const providerOptions: string[] = [ 'local', @@ -252,34 +257,34 @@ export async function activate(context: ExtensionContext) { 'custom for bigiq' ]; - window.showQuickPick(providerOptions, {placeHolder: 'Default BIGIP providers'}) - .then( async input => { + window.showQuickPick(providerOptions, { placeHolder: 'Default BIGIP providers' }) + .then(async input => { - logger.debug('user input', input); + logger.debug('user input', input); - if (input === undefined || bigipHosts === undefined) { - // throw new Error('Update device inputBox cancelled'); - logger.warn('Update device inputBox cancelled'); - return; - } + if (input === undefined || bigipHosts === undefined) { + // throw new Error('Update device inputBox cancelled'); + logger.warn('Update device inputBox cancelled'); + return; + } - if (input === 'custom for bigiq') { - input = await window.showInputBox({ - prompt: "Input custom bigiq login provider" + if (input === 'custom for bigiq') { + input = await window.showInputBox({ + prompt: "Input custom bigiq login provider" + }); + } + + bigipHosts.forEach((item: { device: string; provider?: string; }) => { + if (item.device === hostID.label) { + item.provider = input; + } }); - } - bigipHosts.forEach( (item: { device: string; provider?: string; }) => { - if(item.device === hostID.label) { - item.provider = input; - } + workspace.getConfiguration().update('f5.hosts', bigipHosts, ConfigurationTarget.Global); + + setTimeout(() => { hostsTreeProvider.refresh(); }, 300); }); - - workspace.getConfiguration().update('f5.hosts', bigipHosts, ConfigurationTarget.Global); - setTimeout( () => { hostsTreeProvider.refresh();}, 300); - }); - })); @@ -287,7 +292,7 @@ export async function activate(context: ExtensionContext) { // get editor window var editor = window.activeTextEditor; - if (!editor) { + if (!editor) { return; // No open text editor } @@ -297,11 +302,11 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } await deviceImport(text); - setTimeout( () => { hostsTreeProvider.refresh();}, 1000); + setTimeout(() => { hostsTreeProvider.refresh(); }, 1000); })); @@ -330,7 +335,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.installRPM', async (selectedRPM) => { - if(selectedRPM) { + if (selectedRPM) { // set rpm path/location from oject return in explorer tree selectedRPM = selectedRPM.fsPath; logger.debug(`workspace selected rpm`, selectedRPM); @@ -343,11 +348,11 @@ export async function activate(context: ExtensionContext) { // const iRpms = await rpmMgmt.installedRPMs(); logger.debug('selected rpm', selectedRPM); - if(!selectedRPM) { + if (!selectedRPM) { debugger; // probably need to setup error handling for this situation } - + const installedRpm = await rpmMgmt.rpmInstaller(selectedRPM); logger.debug('installed rpm', installedRpm); ext.mgmtClient?.connect(); // refresh connect/status bars @@ -355,25 +360,25 @@ export async function activate(context: ExtensionContext) { })); context.subscriptions.push(commands.registerCommand('f5.unInstallRPM', async (rpm) => { - + // if no rpm sent in from update command - if(!rpm) { + if (!rpm) { // get installed packages const installedRPMs = await rpmMgmt.installedRPMs(); // have user select package - rpm = await window.showQuickPick(installedRPMs, {placeHolder: 'select rpm to remove'}); + rpm = await window.showQuickPick(installedRPMs, { placeHolder: 'select rpm to remove' }); } else { // rpm came from rpm update call... } - if(!rpm) { // return error pop-up if quickPick escaped + if (!rpm) { // return error pop-up if quickPick escaped return window.showWarningMessage('user exited - did not select rpm to un-install'); } const status = await rpmMgmt.unInstallRpm(rpm); window.showInformationMessage(`rpm ${rpm} removal ${status}`); // debugger; - + // used to pause between uninstalling and installing a new version of the same atc // should probably put this somewhere else await new Promise(resolve => { setTimeout(resolve, 2000); }); @@ -387,9 +392,9 @@ export async function activate(context: ExtensionContext) { * ########################################################################### * * TTTTTTT CCCCC LL - * TTT CC C LL - * TTT CC LL - * TTT CC C LL + * TTT CC C LL + * TTT CC LL + * TTT CC C LL * TTT CCCCC LLLLLLL * * ############################################################################ @@ -400,33 +405,33 @@ export async function activate(context: ExtensionContext) { const tclTreeProvider = new TclTreeProvider(); window.registerTreeDataProvider('as3Tasks', tclTreeProvider); commands.registerCommand('f5.refreshTclTree', () => tclTreeProvider.refresh()); - + // --- IRULE COMMANDS --- context.subscriptions.push(commands.registerCommand('f5-tcl.getRule', async (rule) => { return tclTreeProvider.displayRule(rule); })); - + context.subscriptions.push(commands.registerCommand('f5-tcl.deleteRule', async (rule) => { return tclTreeProvider.deleteRule(rule); })); - - - - + + + + // --- IAPP COMMANDS --- context.subscriptions.push(commands.registerCommand('f5-tcl.getApp', async (item) => { logger.debug('f5-tcl.getApp command: ', item); return ext.panel.render(item); })); - + context.subscriptions.push(commands.registerCommand('f5-tcl.getTemplate', async (item) => { // returns json view of iApp Template return ext.panel.render(item); })); - + context.subscriptions.push(commands.registerCommand('f5-tcl.getTMPL', async (item) => { // gets the original .tmpl output @@ -459,7 +464,7 @@ export async function activate(context: ExtensionContext) { const resp: any = await tclTreeProvider.deleteTMPL(item); return resp; })); - + context.subscriptions.push(commands.registerCommand('f5-tcl.mergeTCL', async (item) => { await tclTreeProvider.mergeTCL(item); })); @@ -471,15 +476,15 @@ export async function activate(context: ExtensionContext) { * ########################################################################### * * FFFFFFF AAA SSSSS TTTTTTT - * FF AAAAA SS TTT - * FFFF AA AA SSSSS TTT - * FF AAAAAAA SS TTT - * FF AA AA SSSSS TTT + * FF AAAAA SS TTT + * FFFF AA AA SSSSS TTT + * FF AAAAAAA SS TTT + * FF AA AA SSSSS TTT * * ############################################################################ * http://patorjk.com/software/taag/#p=display&h=0&f=Letters&t=FAST */ - + // setting up hosts tree const fastTreeProvider = new FastTemplatesTreeProvider(); window.registerTreeDataProvider('fastView', fastTreeProvider); @@ -495,7 +500,7 @@ export async function activate(context: ExtensionContext) { // get editor window var editor = window.activeTextEditor; - if (!editor) { + if (!editor) { return; // No open text editor } @@ -505,17 +510,17 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } // TODO: make this a try sequence to only parse the json once let jsonText: object; - if(utils.isValidJson(text)){ + if (utils.isValidJson(text)) { jsonText = JSON.parse(text); } else { window.showWarningMessage(`Not valid json object`); return; } - + const resp = await f5FastApi.deployFastApp(jsonText); ext.panel.render(resp); @@ -558,7 +563,8 @@ export async function activate(context: ExtensionContext) { // get editor window var editor = window.activeTextEditor; - if (!editor) { return; // No open text editor + if (!editor) { + return; // No open text editor } // capture selected text or all text in editor @@ -567,11 +573,11 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } logger.debug(JSON.stringify(text)); - if(utils.isValidJson(text)){ + if (utils.isValidJson(text)) { //TODO: parse object and find the level for just ADC, // need to remove all the AS3 details since fast will handle that @@ -588,12 +594,12 @@ export async function activate(context: ExtensionContext) { let text: string | Buffer; - if(!sFile) { + if (!sFile) { // not right click from explorer view, so gather file details // get editor window var editor = window.activeTextEditor; - if (!editor) { + if (!editor) { return; // No open text editor } @@ -602,7 +608,7 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } } else { // right click from explorer view, so load file contents const fileContents = fs.readFileSync(sFile.fsPath); @@ -621,16 +627,16 @@ export async function activate(context: ExtensionContext) { logger.debug('postTemplateSet selection', sPath); let wkspPath; let selectedFolder; - - if(!sPath) { + + if (!sPath) { // didn't get a path passed in from right click, so we have to gather necessary details // get list of open workspaces const workspaces = workspace.workspaceFolders; logger.debug('workspaces', workspaces); - + // if no open workspace... - if(!workspaces) { + if (!workspaces) { // Show message to select workspace await window.showInformationMessage('See top bar to open a workspace with Fast Templates first'); // pop up to selecte a workspace @@ -638,13 +644,13 @@ export async function activate(context: ExtensionContext) { // return to begining of function to try again return commands.executeCommand('f5-fast.postTemplateSet'); } - + const folder1 = workspace.workspaceFolders![0]!.uri; wkspPath = folder1.fsPath; const folder2 = await workspace.fs.readDirectory(folder1); - + logger.debug('workspace name', workspace.name); - + /** * having problems typing the workspaces to a list for quick pick * todo: get the following working @@ -657,11 +663,11 @@ export async function activate(context: ExtensionContext) { // // else select the first workspace // wkspc = workspaces[0]; // } - + let wFolders = []; for (const [name, type] of await workspace.fs.readDirectory(folder1)) { - if (type === FileType.Directory){ + if (type === FileType.Directory) { logger.debug('---directory', name); wFolders.push(name); } @@ -669,8 +675,8 @@ export async function activate(context: ExtensionContext) { // have user select first level folder in workspace selectedFolder = await window.showQuickPick(wFolders); - - if(!selectedFolder) { + + if (!selectedFolder) { // if user "escaped" folder selection window return window.showInformationMessage('Must select a Fast Template Set folder'); } @@ -690,12 +696,12 @@ export async function activate(context: ExtensionContext) { })); context.subscriptions.push(commands.registerCommand('f5-fast.deleteFastApp', async (tenApp) => { - + // var device: string | undefined = ext.hostStatusBar.text; // const password = await utils.getPassword(device); const resp = await f5FastApi.delTenApp(tenApp.label); ext.panel.render(resp); - + // give a little time to finish await new Promise(resolve => { setTimeout(resolve, 2000); }); fastTreeProvider.refresh(); @@ -720,9 +726,9 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5-fast.renderHtmlPreview', async (item) => { let text: string = 'empty'; - let title: string = 'Fast Template'; + let title: string = 'Fast Template'; - if(item?.hasOwnProperty('scheme') && item?.scheme === 'file') { + if (item?.hasOwnProperty('scheme') && item?.scheme === 'file') { // right click from explorer view initiation, so load file contents const fileContents = fs.readFileSync(item.fsPath); // convert from buffer to string @@ -747,24 +753,24 @@ export async function activate(context: ExtensionContext) { } else { // right-click or commandpalette initiation, so get editor text var editor = window.activeTextEditor; - if (editor) { + if (editor) { if (editor?.selection?.isEmpty) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } } } fastPanel.render(text); })); - - - + + + /** * ############################################################################ * @@ -778,42 +784,89 @@ export async function activate(context: ExtensionContext) { * http://patorjk.com/software/taag/#p=display&h=0&f=Letters&t=AS3 */ - + // setting up as3 tree const as3Tree = new AS3TreeProvider(); window.registerTreeDataProvider('as3Tenants', as3Tree); commands.registerCommand('f5-as3.refreshTenantsTree', () => as3Tree.refresh()); - + context.subscriptions.push(commands.registerCommand('f5-as3.getDecs', async (tenant) => { - // set blank value if not defined -> get all tenants dec - tenant = tenant ? tenant : ''; + if (typeof tenant === 'object') { - const resp: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare/${tenant}`); - ext.panel.render(resp); - })); + // just a regular as3 declaration object + ext.panel.render(tenant); + } else { - context.subscriptions.push(commands.registerCommand('f5-as3.fullTenant', async (tenant) => { - commands.executeCommand('f5-as3.getDecs', `${tenant.label}?show=full`); + // got a simple tenant name as string with uri parameter, + // this is typically for extended information + // so fetch fresh information with param + // await ext.f5Client?.as3?.getDecs({ tenant }) + await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare/${tenant}`) + .then((resp: any) => ext.panel.render(resp.data)) + .catch(err => logger.error('get as3 tenant with param failed:', err)); + } })); + + context.subscriptions.push(commands.registerCommand('f5-as3.expandedTenant', async (tenant) => { commands.executeCommand('f5-as3.getDecs', `${tenant.label}?show=expanded`); })); - - + + context.subscriptions.push(commands.registerCommand('f5-as3.deleteTenant', async (tenant) => { - - const progress = await window.withProgress({ + + await window.withProgress({ location: ProgressLocation.Notification, + // location: { viewId: 'as3Tenants'}, title: `Deleting ${tenant.label} Tenant` }, async (progress) => { - - const resp: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare/${tenant.label}`, { - method: 'DELETE' - }); - const resp2 = resp.data.results[0]; - progress.report({message: `${resp2.code} - ${resp2.message}`}); + + await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare`, { + method: 'POST', + body: { + class: 'AS3', + declaration: { + schemaVersion: tenant.command.arguments[0].schemaVersion, + class: 'ADC', + target: tenant.command.arguments[0].target, + [tenant.label]: { + class: 'Tenant' + } + } + } + }) + // await ext.f5Client?.https(`/mgmt/shared/appsvcs/declare`, { + // method: 'POST', + // data: { + // class: 'AS3', + // declaration: { + // schemaVersion: tenant.command.arguments[0].schemaVersion, + // class: 'ADC', + // target: tenant.command.arguments[0].target, + // [tenant.label]: { + // class: 'Tenant' + // } + // } + // } + // }) + .then((resp: any) => { + const resp2 = resp.data.results[0]; + progress.report({ message: `${resp2.code} - ${resp2.message}` }); + + }) + .catch(err => { + progress.report({ message: `${err.message}` }); + // might need to adjust logging depending on the error, but this works for now, or at least the main HTTP responses... + logger.error('as3 delete tenant failed with:', { + respStatus: err.response.status, + respText: err.response.statusText, + errMessage: err.message, + errRespData: err.response.data + }); + }); + // hold the status box for user and let things finish before refresh await new Promise(resolve => { setTimeout(resolve, 5000); }); }); @@ -823,13 +876,15 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5-as3.getTask', (id) => { - window.withProgress({ + return window.withProgress({ location: ProgressLocation.Notification, title: `Getting AS3 Task` }, async () => { - const resp: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/task/${id}`); - ext.panel.render(resp); + return await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/task/${id}`) + // await ext.f5Client?.as3?.getTasks(id) + .then(resp => ext.panel.render(resp)) + .catch(err => logger.error('as3 get task failed:', err)); }); })); @@ -839,7 +894,7 @@ export async function activate(context: ExtensionContext) { ext.as3AsyncPost = workspace.getConfiguration().get('f5.as3Post.async'); let postParam; - if(ext.as3AsyncPost) { + if (ext.as3AsyncPost) { postParam = 'async=true'; } else { postParam = undefined; @@ -855,7 +910,7 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } if (!utils.isValidJson(text)) { return window.showErrorMessage('Not valid JSON object'); @@ -876,7 +931,7 @@ export async function activate(context: ExtensionContext) { // Get the active text editor const editor = window.activeTextEditor; - + if (editor) { let text: string; const document = editor.document; @@ -886,10 +941,10 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } + + const [newText, validDec] = await injectSchema(text); - const [ newText, validDec ] = await injectSchema(text); - // check if modification worked if (newText && validDec) { editor.edit(edit => { @@ -951,16 +1006,16 @@ export async function activate(context: ExtensionContext) { })); context.subscriptions.push(commands.registerCommand('f5-ts.postDec', async () => { - + // if selected text, capture that, if not, capture entire document var editor = window.activeTextEditor; let text: string; - if(editor) { + if (editor) { if (editor.selection.isEmpty) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } if (!utils.isValidJson(text)) { return window.showErrorMessage('Not valid JSON object'); @@ -981,7 +1036,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.getGitHubExample', async (decUrl) => { - const resp = await extAPI.makeRequest({ url: decUrl }); + const resp = await extAPI.makeRequest({ url: decUrl }); return ext.panel.render(resp); })); @@ -989,18 +1044,18 @@ export async function activate(context: ExtensionContext) { -/** - * ######################################################################### - * - * █████   ██████  - * ██   ██ ██    ██  - * ██  ██ ██  ██  - * ██  ██ ██  ██  - * █████    ██████   - * - * ######################################################################### - * http://patorjk.com/software/taag/#p=display&h=0&f=ANSI%20Regular&t=DO - */ + /** + * ######################################################################### + * + * █████   ██████  + * ██   ██ ██    ██  + * ██  ██ ██  ██  + * ██  ██ ██  ██  + * █████    ██████   + * + * ######################################################################### + * http://patorjk.com/software/taag/#p=display&h=0&f=ANSI%20Regular&t=DO + */ context.subscriptions.push(commands.registerCommand('f5-do.getDec', async () => { @@ -1027,7 +1082,7 @@ export async function activate(context: ExtensionContext) { text = editor.document.getText(); // entire editor/doc window } else { text = editor.document.getText(editor.selection); // highlighted text - } + } if (!utils.isValidJson(text)) { return window.showErrorMessage('Not valid JSON object'); @@ -1046,7 +1101,7 @@ export async function activate(context: ExtensionContext) { }, async () => { const resp: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/declarative-onboarding/inspect`); ext.panel.render(resp); - }); + }); })); @@ -1067,18 +1122,18 @@ export async function activate(context: ExtensionContext) { -/** - * ######################################################################### - * - * UU UU TTTTTTT IIIII LL - * UU UU TTT III LL - * UU UU TTT III LL - * UU UU TTT III LL - * UUUUU TTT IIIII LLLLLLL - * - * ######################################################################### - * http://patorjk.com/software/taag/#p=display&h=0&f=Letters&t=UTIL - */ + /** + * ######################################################################### + * + * UU UU TTTTTTT IIIII LL + * UU UU TTT III LL + * UU UU TTT III LL + * UU UU TTT III LL + * UUUUU TTT IIIII LLLLLLL + * + * ######################################################################### + * http://patorjk.com/software/taag/#p=display&h=0&f=Letters&t=UTIL + */ // register example delarations tree @@ -1113,33 +1168,38 @@ export async function activate(context: ExtensionContext) { * UCS arcive, qkview, or our special little archive from above * */ - - if (!ext.mgmtClient) { - /** - * loop this back into the connect flow, since we have the device, automatically connect - * - this should probably happen in the main extension.ts - */ - // await commands.executeCommand('f5.connectDevice', item.label); - return window.showWarningMessage('Connect to BIGIP Device first'); + + if (!ext.mgmtClient) { + /** + * loop this back into the connect flow, since we have the device, automatically connect + * - this should probably happen in the main extension.ts + */ + // await commands.executeCommand('f5.connectDevice', item.label); + return window.showWarningMessage('Connect to BIGIP Device first'); } - + const file = await getMiniUcs(); let expl: any = undefined; if (file) { logger.debug('Got mini_ucs -> extracting config with corkscrew'); - + expl = await makeExplosion(file); - + if (expl) { - await cfgProvider.explodeConfig(expl.config, expl.obj, expl.explosion); + await cfgProvider.explodeConfig(expl.explosion); + + // inject the config files (not included in explosion output by default) + // cfgProvider.bigipConfs = expl.config; + // inject the config object (just for looks...) + cfgProvider.confObj = expl.obj; } logger.debug('Deleting mini_ucs file at', file); try { // wait couple seconds before we try to delete the mini_ucs - setTimeout( () => { fs.unlinkSync(file); }, 2000); + setTimeout(() => { fs.unlinkSync(file); }, 2000); } catch (e) { logger.error('Not able to delete mini_ucs at:', file); } @@ -1150,42 +1210,148 @@ export async function activate(context: ExtensionContext) { + cfgProvider.refresh(); // refresh with the new information })); /** * this command is exposed via right click in editor so user does not have to connect to F5 + * this flow assumes the file is local */ context.subscriptions.push(commands.registerCommand('f5.cfgExplore', async (item) => { - let expl; - if (item._fsPath) { - // I think this is the better way for windows? - logger.debug('f5.cfgExplore got _fsPath:', item._fsPath); - expl = await makeExplosion(item._fsPath); - } else if (item.path) { - // only path is seen when working in wsl2 - logger.debug('f5.cfgExplore got path:', item.path); - expl = await makeExplosion(item.path); + let filePath: string; + + if (!item) { + // no input means we need to browse for a local file + item = await window.showOpenDialog({ + canSelectMany: false + }); + + // if we got a file from the showOpenDialog, it comes in an array, even though we told it to only allow single item selection -> return the single array item + if (Array.isArray(item)) { + item = item[0]; + } + } + + if (item?._fsPath) { + + logger.info(`f5.cfgExplore _fsPath recieved:`, item._fsPath); + filePath = item._fsPath; + + } else if (item?.path) { + + logger.info(`f5.cfgExplore path revieved:`, item.path); + filePath = item.path; + } else { + return logger.error('f5.cfgExplore -> Neither path supplied was valid', JSON.stringify(item)); + + } + + try { + // test that we can access the file + const x = fs.statSync(filePath); + } catch (e) { + // if we couldn't get to the file, trim leading character + // remove leading slash -> i think this is a bug like: https://github.com/microsoft/vscode-remote-release/issues/1583 + // filePath = filePath.replace(/^(\\|\/)/, ''); + logger.info(`could not find file with supplied path of ${filePath}, triming leading character`); + filePath = filePath.substr(1); + } + + + + logger.info(`f5.cfgExplore: exploding config @ ${filePath}`); + + await makeExplosion(filePath) + .then(async expl => { + + if (expl.explosion) { + await cfgProvider.explodeConfig(expl.explosion); + } + + if (expl.obj) { + // inject the config object (just for looks...) + cfgProvider.confObj = expl.obj; + } + cfgProvider.refresh(); // refresh with the new information + }) + .catch(err => { + logger.error('cfgExplorer error', err); + }); + + })); + + + context.subscriptions.push(commands.registerCommand('f5.cfgExploreRawCorkscrew', async (text) => { + // no input means we need to browse for a local file + const file = await window.showOpenDialog({ + canSelectMany: false + }).then(x => { + if (Array.isArray(x)) { + return x[0]; + } + }); + + let filePath; + + if (file?.fsPath) { + + logger.info(`f5.cfgExploreRawCorkscrew _fsPath recieved:`, file.fsPath); + filePath = file.fsPath; + + } else if (file?.path) { + + logger.info(`f5.cfgExploreRawCorkscrew path revieved:`, file.path); + filePath = file.path; + + } else { + + return logger.error('f5.cfgExploreRawCorkscrew -> Neither path supplied was valid', JSON.stringify(file)); + } - if (expl) { - cfgProvider.explodeConfig(expl.config, expl.obj, expl.explosion); - // starting to setup the ability to have the view come into focus when excuted - // I believe this will require enabling experimental features, so I'm tabling - // for now - // cfgView.reveal('Apps', { focus: true, select: false, expand: true } ); - - // // cfgView.title = 'yay!!!'; - // cfgView.description = 'descrrrrr'; - // cfgView.message = 'messsg'; - // cfgView.selection; - // cfgView.visible + try { + // test that we can access the file + const x = fs.statSync(filePath); + } catch (e) { + // if we couldn't get to the file, trim leading character + // remove leading slash -> i think this is a bug like: https://github.com/microsoft/vscode-remote-release/issues/1583 + // filePath = filePath.replace(/^(\\|\/)/, ''); + logger.info(`could not find file with supplied path of ${filePath}, triming leading character`); + filePath = filePath.substr(1); } + if (filePath) { + try { + const read = fs.readFileSync(filePath, 'utf-8'); + // parse json + const read2 = JSON.parse(read); + await cfgProvider.explodeConfig(read2); + } catch (e) { + logger.error('cfgExploreRawCorkscrew import failed', e); + } + } + + cfgProvider.refresh(); // refresh with the new information + })); + + + + context.subscriptions.push(commands.registerCommand('f5.cfgExploreReveal', async (text) => { + // await new Promise(resolve => { setTimeout(resolve, 2000); }); + if (cfgProvider.viewElement) { + cfgView.reveal(cfgProvider.viewElement, { + select: true, + focus: true, + expand: true + }); + } })); + + context.subscriptions.push(commands.registerCommand('f5.cfgExploreClear', async (text) => { cfgProvider.clear(); })); @@ -1201,7 +1367,7 @@ export async function activate(context: ExtensionContext) { if (Array.isArray(x) && x.length > 1) { // got multi-select array, push all necessary details to a single object - x.forEach( (el) => { + x.forEach((el) => { const y = el.command?.arguments; if (y) { full.push(y[0].join('\n')); @@ -1210,8 +1376,8 @@ export async function activate(context: ExtensionContext) { }); text = full; - // } else if (Array.isArray(x) && x.length === 1) { - // return window.showWarningMessage('Select multiple apps with "Control" key'); + // } else if (Array.isArray(x) && x.length === 1) { + // return window.showWarningMessage('Select multiple apps with "Control" key'); } else if (typeof text === 'string') { // just text, convert to single array with render text = [text]; @@ -1222,7 +1388,7 @@ export async function activate(context: ExtensionContext) { cfgProvider.render(text); })); - + // /** // * // * @@ -1237,24 +1403,24 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.jsonYmlConvert', async () => { const editor = window.activeTextEditor; - if(!editor) { + if (!editor) { return; } - const selection = editor.selection; + const selection = editor.selection; const text = editor.document.getText(editor.selection); // highlighted text - + let newText: string; if (utils.isValidJson(text)) { logger.debug('converting json -> yaml'); // since it was valid json -> dump it to yaml - newText = jsYaml.safeDump(JSON.parse(text), {indent: 4}); + newText = jsYaml.safeDump(JSON.parse(text), { indent: 4 }); } else { logger.debug('converting yaml -> json'); newText = JSON.stringify(jsYaml.safeLoad(text), undefined, 4); } - editor.edit( editBuilder => { + editor.edit(editBuilder => { editBuilder.replace(selection, newText); }); })); @@ -1282,12 +1448,12 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.b64Encode', () => { const editor = window.activeTextEditor; - if(!editor){ + if (!editor) { return; } const text = editor.document.getText(editor.selection); // highlighted text const encoded = Buffer.from(text).toString('base64'); - editor.edit( editBuilder => { + editor.edit(editBuilder => { editBuilder.replace(editor.selection, encoded); }); })); @@ -1295,12 +1461,12 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.b64Decode', () => { const editor = window.activeTextEditor; - if(!editor){ + if (!editor) { return; } const text = editor.document.getText(editor.selection); // highlighted text const decoded = Buffer.from(text, 'base64').toString('ascii'); - editor.edit( editBuilder => { + editor.edit(editBuilder => { editBuilder.replace(editor.selection, decoded); }); })); @@ -1316,11 +1482,11 @@ export async function activate(context: ExtensionContext) { const editor = window.activeTextEditor; let resp; - if(editor){ + if (editor) { var text: any = editor.document.getText(editor.selection); // highlighted text // see if it's json or yaml or string - if(utils.isValidJson(text)) { + if (utils.isValidJson(text)) { logger.debug('JSON detected -> parsing'); text = JSON.parse(text); @@ -1328,17 +1494,17 @@ export async function activate(context: ExtensionContext) { } else { logger.debug('NOT JSON'); - - if(text.includes('url:')) { + + if (text.includes('url:')) { // if yaml should have url: param logger.debug('yaml with url: param -> parsing raw to JSON', JSON.stringify(text)); text = jsYaml.safeLoad(text); - + } else { // not yaml logger.debug('http with OUT url param -> converting to json'); // trim line breaks - text = text.replace(/(\r\n|\n|\r)/gm,""); + text = text.replace(/(\r\n|\n|\r)/gm, ""); text = { url: text }; } } @@ -1348,7 +1514,7 @@ export async function activate(context: ExtensionContext) { * depending on the parameters, it's an F5 call, or an external call */ - if(text.url.includes('http')) { + if (text.url.includes('http')) { resp = await window.withProgress({ location: ProgressLocation.Notification, @@ -1360,14 +1526,14 @@ export async function activate(context: ExtensionContext) { logger.debug("User canceled External API Request"); return new Error(`User canceled External API Request`); }); - + //external call logger.debug('external call -> ', JSON.stringify(text)); return await extAPI.makeRequest(text); }); - + } else { - + resp = await window.withProgress({ location: ProgressLocation.Notification, title: `Making API Request`, @@ -1380,11 +1546,11 @@ export async function activate(context: ExtensionContext) { }); // f5 device call - if(!ext.mgmtClient) { + if (!ext.mgmtClient) { // connect to f5 if not already connected await commands.executeCommand('f5.connectDevice'); } - + logger.debug('device call -> ', JSON.stringify(text)); return await ext.mgmtClient?.makeRequest(text.url, { method: text.method, @@ -1393,7 +1559,7 @@ export async function activate(context: ExtensionContext) { }); } - if(resp) { + if (resp) { ext.panel.render(resp); } } @@ -1404,8 +1570,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('f5.remoteCommand', async () => { const cmd = await window.showInputBox({ placeHolder: 'Bash Command to Execute?', ignoreFocusOut: true }); - - if ( cmd === undefined ) { + + if (cmd === undefined) { // maybe just showInformationMessage and exit instead of error? throw new Error('Remote Command inputBox cancelled'); } @@ -1419,7 +1585,7 @@ export async function activate(context: ExtensionContext) { }); ext.panel.render(resp.data.commandResult); - })); + })); @@ -1429,7 +1595,7 @@ export async function activate(context: ExtensionContext) { const newEditorColumn = ext.settings.previewColumn; const wndw = window.visibleTextEditors; let viewColumn: ViewColumn | undefined; - + wndw.forEach(el => { // const el1 = element; if (el.document.fileName === 'chuck-joke.json') { @@ -1437,11 +1603,11 @@ export async function activate(context: ExtensionContext) { viewColumn = el.viewColumn; } }); - - - const resp: any = await extAPI.makeRequest({url: 'https://api.chucknorris.io/jokes/random'}); + + + const resp: any = await extAPI.makeRequest({ url: 'https://api.chucknorris.io/jokes/random' }); // let activeColumn = window.activeTextEditor?.viewColumn; - + logger.debug('chuck-joke->resp.data', resp.data); const content = JSON.stringify(resp.data, undefined, 4); @@ -1451,15 +1617,15 @@ export async function activate(context: ExtensionContext) { var vDoc: Uri = Uri.parse("untitled:" + "chuck-Joke.json"); workspace.openTextDocument(vDoc) - .then((a: TextDocument) => { - window.showTextDocument(a, viewColumn, false).then(e => { - e.edit(edit => { - const startPosition = new Position(0, 0); - const endPosition = a.lineAt(a.lineCount - 1).range.end; - edit.replace(new Range(startPosition, endPosition), content); + .then((a: TextDocument) => { + window.showTextDocument(a, viewColumn, false).then(e => { + e.edit(edit => { + const startPosition = new Position(0, 0); + const endPosition = a.lineAt(a.lineCount - 1).range.end; + edit.replace(new Range(startPosition, endPosition), content); + }); }); }); - }); // chuckJoke1(); @@ -1473,4 +1639,4 @@ export async function activate(context: ExtensionContext) { // this method is called when your extension is deactivated -export function deactivate() {} +export function deactivate() { } diff --git a/src/fastCore.ts b/src/fastCore.ts new file mode 100644 index 0000000..7f46683 --- /dev/null +++ b/src/fastCore.ts @@ -0,0 +1,193 @@ +/* + * Copyright 2020. F5 Networks, Inc. See End User License Agreement ("EULA") for + * license terms. Notwithstanding anything to the contrary in the EULA, Licensee + * may copy and modify this software product for its internal business purposes. + * Further, Licensee may upload, publish and distribute the modified version of + * the software product on devcentral.f5.com. + */ + +'use strict'; + +import { commands, EndOfLine, ExtensionContext, languages, Position, Range, Selection, window } from 'vscode'; +import logger from './utils/logger'; +import { isObject } from './treeViewsProviders/as3TreeProvider'; + +/** + * Provides command to download github releases of this extension so users can easily access beta versions for testing + */ +export class FastCore { + + // fastYamlBase = [ + // 'title: $1', + // 'description: $2', + // 'template: |', + // ]; + // fastYamlExtended = [ + // 'parameters:', + // 'definitions:' + // ]; + + constructor(context: ExtensionContext) { + + context.subscriptions.push(commands.registerCommand('f5-fast.as3ToFastYml', async (text) => { + + logger.info('f5-fast.as3ToFastYml, converting as3 declaration to fast yaml template'); + const editor = window.activeTextEditor; + + if (editor) { + const document = editor.document; + + // capture selected text or all text in editor + if (editor.selection.isEmpty) { + text = editor.document.getText(); // entire editor/doc window + } else { + text = editor.document.getText(editor.selection); // highlighted text + } + + await as3TemplateStrings(text) + .then(text => { + + editor.edit(edit => { + edit.setEndOfLine(EndOfLine.LF); + + text = text.replace(/\r\n/g, '\n'); + + // split on lines, add two spaces at beginning of each line, then put everything back together + text = text.split('\n').map((line: string) => ` ${line}`).join('\n'); + + const startPosition = new Position(0, 0); + const endPosition = document.lineAt(document.lineCount - 1).range.end; + edit.replace(new Range(startPosition, endPosition), text); + }); + + // change the editor language to yaml + languages.setTextDocumentLanguage(document, 'yaml'); + // move the cursor to the very beginning of the doc/editor + editor.selection = new Selection(new Position(0, 0), new Position(0, 0)); + // insert snippet + commands.executeCommand("editor.action.insertSnippet", { langId: "yaml", name: 'FAST YAML Extended' },); + + }) + .catch(err => { + logger.error('f5-fast.as3ToFastYml failed', err); + }); + + + } + })); + } +} + +/** + * look for as3 tenant object definition and replace with {{tenant_name}} for fast template + * @param text as3 declaration as string + */ +export async function as3TemplateStrings(text: string): Promise { + + // let's try to parse the as3 declarationn and put in a template string for the tenant + const dec = JSON.parse(text); + + if (dec?.declaration) { + + logger.info('f5-fast.as3ToFastYml: AS3 parent class'); + + // we got an AS3/declaration + Object.entries(dec.declaration).forEach(([key, value]) => { + + // look at the objects (application pieces) + if (isObject(value) && (value as unknown as { class: string })?.class) { + + // recast object param as needed + const appVal: { class: string } = (value as unknown) as { class: string }; + + // capture the class of each application piece + if (appVal?.class === 'Tenant') { + // we found the tenant class + delete dec.declaration[key]; + dec.declaration['{{tenant_name}}'] = value; + + // made our needed change, let's exit + return; + } + } + }); + } else { + + logger.info('f5-fast.as3ToFastYml: ADC parent class'); + + // this is a list of ADC tenants + Object.entries(dec).forEach(([key, value]) => { + + // look at the objects (application pieces) + if (isObject(value) && (value as unknown as { class: string })?.class) { + + // recast object param as needed + const appVal: { class: string } = (value as unknown) as { class: string }; + + // capture the class of each application piece + if (appVal?.class === 'Tenant') { + // we found the tenant class + delete dec[key]; + dec['{{tenant_name}}'] = value; + + // made our needed change, let's exit + return; + } + } + }); + } + + // return the declaration back in string form but with 2 spaces to better match yamls format + return JSON.stringify(dec, undefined, 2); +} + + +const exampleFastTemplate = ` +title: Simple UDP Application +description: Simple UDP load balancer using the same port on client and server side. +parameters: + tenant_name: AgilityFastTemplate + application_name: defaultsUDP_5555 + virtual_address: 192.50.2.1 + virtual_port: 5555 + server_addresses: + - 192.50.2.2 + - 192.50.2.3 + service_port: 8888 +definitions: + tenant_name: + title: Tenant Name + type: string + description: partition on bigip +template: | + { + "class": "ADC", + "schemaVersion": "3.20.0", + "{{tenant_name}}": { + "class": "Tenant", + "{{application_name}}": { + "class": "Application", + "template": "udp", + "serviceMain": { + "class": "Service_UDP", + "virtualAddresses": [ + "{{virtual_address}}" + ], + "virtualPort": {{virtual_port::integer}}, + "pool": "{{application_name}}_Pool1" + }, + "{{application_name}}_Pool1": { + "class": "Pool", + "monitors": [ + "icmp" + ], + "members": [ + { + "serverAddresses": {{server_addresses::array}}, + "servicePort": {{service_port::integer}} + } + ] + } + } + } + }`; \ No newline at end of file diff --git a/src/treeViewsProviders/as3TreeProvider.ts b/src/treeViewsProviders/as3TreeProvider.ts index bc07b57..383ae99 100644 --- a/src/treeViewsProviders/as3TreeProvider.ts +++ b/src/treeViewsProviders/as3TreeProvider.ts @@ -1,172 +1,522 @@ -import * as vscode from 'vscode'; +import { + Command, + Event, + EventEmitter, + TreeDataProvider, + TreeItem, + TreeItemCollapsibleState +} from 'vscode'; +import * as jsYaml from 'js-yaml'; +import { AdcDeclaration, As3AppMap, As3AppMapTargets, As3AppMapTenants, As3Declaration, As3Tenant } from '../utils/as3Models'; import { ext } from '../extensionVariables'; -// import { isArray } from 'util'; +import logger from '../utils/logger'; -export class AS3TreeProvider implements vscode.TreeDataProvider { +export class AS3TreeProvider implements TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private _onDidChangeTreeData: EventEmitter = new EventEmitter(); + readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; + + /** + * target/tenant/app map from /declare endpoint + */ + as3DeclareMap: As3AppMap | undefined; + + /** + * original declare output + */ + declare: AdcDeclaration[] = []; + + targets: boolean = false; - private _tenants: string[] = []; - private _bigiqTenants: any = []; private _tasks: string[] = []; constructor() { } refresh(): void { + this.as3DeclareMap = undefined; + this.declare = []; + this._tasks = []; this._onDidChangeTreeData.fire(undefined); } - getTreeItem(element: AS3item): vscode.TreeItem { + getTreeItem(element: AS3item): TreeItem { return element; } async getChildren(element?: AS3item) { let treeItems: AS3item[] = []; - - if (element) { - if(element.label === 'Targets'){ + if (ext.as3Bar.text) { - const targetTenCount = this._bigiqTenants.length.toString(); + if (element) { - treeItems = this._bigiqTenants.map( (el: { target: string; tensList: any; }) => { - return new AS3item(el.target, targetTenCount, '', '', vscode.TreeItemCollapsibleState.Collapsed, - { command: '', title: '', arguments: el.tensList }); - }); + if (element.label === 'Targets') { + + this.declare.map((el: AdcDeclaration) => { + + // get target details, at this point we should know for certain that we are dealing with targets... + const target + = el?.target?.address ? el.target.address + : el?.target?.hostname ? el.target.hostname + : 'missing target - should not be here'; + + const targetTenCount = Object.entries(el).filter(([tKey, tVal]) => { + return (isObject(tVal) && tKey !== 'target' && tKey !== 'controls'); + }).length.toString(); + + if (!this.as3DeclareMap) { + // this should never happen, but TS needs it... + return; + } + + // get appStats and set as tooltip + const as3DecMap = this.as3DeclareMap[target]; + const as3DecMapStringified = jsYaml.safeDump(as3DecMap, { indent: 4 }); + + treeItems.push(new AS3item(target, targetTenCount, as3DecMapStringified, 'as3Target', TreeItemCollapsibleState.Collapsed, + { command: 'f5-as3.getDecs', title: '', arguments: [el] }) + ); + }); + + treeItems = sortTreeItems(treeItems); + + } else if (element.label === 'Tenants') { + + Object.entries(this.as3DeclareMap as object).forEach(([key, value]) => { + + if (!this.as3DeclareMap) { + return null; // should never happen + } + + const appStats = this.as3DeclareMap[key]; + const appCount = Object.keys(appStats).length.toString(); + const as3DecMapStringified = jsYaml.safeDump(appStats, { indent: 4 }); + + // get name of other tenants + const targetRemoval = Object.keys(this.as3DeclareMap).filter(x => x !== key); + // clone object to new variable + const newDec = JSON.parse(JSON.stringify(this.declare[0])); + // remove other tenant objects from clone + targetRemoval.forEach(el => { + delete newDec[el]; + }); + + treeItems.push( + new AS3item(key, appCount, as3DecMapStringified, 'as3Tenant', TreeItemCollapsibleState.Collapsed, + { command: 'f5-as3.getDecs', title: '', arguments: [newDec] }) + ); + }); + + treeItems = sortTreeItems(treeItems); + + } else if (element.context === 'as3Target') { + + // now use item label to find/return list items for target - should only get one + const targetDeclaration = this.declare.filter((el: AdcDeclaration) => { + if (el.target?.address && el.target.address === element.label) { + return el; + } else if (el.target?.hostname && el.target.hostname === element.label) { + return el; + } + })[0]; + + if (targetDeclaration) { + Object.entries(targetDeclaration).map(([targetKey, targetVal]) => { + if (isObject(targetVal) && targetKey !== 'target' && targetKey !== 'controls') { + + const appCount = Object.entries(targetVal as object).filter(([tenantKey, tenantValue]) => { + return (isObject(tenantValue)); + }).length.toString(); + + + if (!this.as3DeclareMap) { + // this should never happen, but TS needs it... + return; + } + + // get appStats and set as tooltip + const target = element.label as string; + const tenant = targetKey as string; + const as3DecMap = this.as3DeclareMap[target][tenant]; + const as3DecMapStringified = jsYaml.safeDump(as3DecMap, { indent: 4 }); + + // get name of other tenants + const targetRemoval = Object.keys(this.as3DeclareMap[target]).filter(key => key !== targetKey); + // clone object to new variable + const newDec = JSON.parse(JSON.stringify(targetDeclaration)); + // remove other tenant objects from clone + targetRemoval.forEach(el => { + delete newDec[el]; + }); + + treeItems.push( + new AS3item(targetKey, appCount, as3DecMapStringified, 'as3Tenant', TreeItemCollapsibleState.Collapsed, + { command: 'f5-as3.getDecs', title: '', arguments: [newDec] }) + ); + } + }); + } + + treeItems = sortTreeItems(treeItems); + + + + } else if (element.context === 'as3Tenant') { + + let tenantDeclaration: AdcDeclaration; + if (this.targets) { + // now use item label to find/return list items for target - should only get one + tenantDeclaration = this.declare.filter((el: AdcDeclaration) => { + return Object.entries(el).find(([key, value]) => ((key as unknown) as string === element.label)); + })[0]; + } else { + // no targets so get the one device declaration with multiple tenants + tenantDeclaration = this.declare[0]; + } + + + + if (tenantDeclaration) { + + // get target details, at this point we should know for certain that we are dealing with targets... + const toolTip + = tenantDeclaration?.target?.address ? `${tenantDeclaration.target.address}/${element.label}` + : tenantDeclaration?.target?.hostname ? `${tenantDeclaration.target.hostname}/${element.label}` + : `${element.label}`; + + + // get the device declaration with tenants + const apps = Object.entries(tenantDeclaration[element.label] as object).filter(([tenantKey, tenantValue]) => { + return (isObject(tenantValue)); + }); + + + apps.map( async app => { + // bunch of typing magic.. + const appDec = tenantDeclaration[element.label] as As3Tenant; + const appStats = await as3AppStats(appDec[app[0]] as object); + const appCount = Object.keys(appStats as object).length.toString(); + treeItems.push( + new AS3item(app[0], appCount, toolTip, 'as3App', TreeItemCollapsibleState.Collapsed, + { command: '', title: '', arguments: [] }) + ); + }); + } + + treeItems = sortTreeItems(treeItems); + + } else if (element.context === 'as3App') { + + if (!this.as3DeclareMap) { + return; // this should never happen, but TS needs it... + } + + let as3DecMap: any; + if (/\//.test(element.tooltip) && this.as3DeclareMap) { + const [target, tenant] = element.tooltip.split('/'); + as3DecMap = this.as3DeclareMap[target][tenant][element.label]; + } else { + as3DecMap = this.as3DeclareMap[element.tooltip][element.label]; + } + + Object.entries(as3DecMap).forEach(([key, value]) => { + + // since value is unknown at this point, cast it to a number, then convert to string + const count = (value as number).toString(); + treeItems.push( + new AS3item(key, count, '', '', TreeItemCollapsibleState.None, + { command: '', title: '', arguments: [] } + ) + ); + }); + + treeItems = sortTreeItems(treeItems); + + } else if (element.label === 'Tasks') { + + treeItems = this._tasks.map((task: any) => { + return new AS3item(task.iId.split('-').pop(), task.timeStamp, '', '', TreeItemCollapsibleState.None, + { command: 'f5-as3.getTask', title: '', arguments: [task.iId] }); + }); + + } - } else if(element.label === 'Tenants'){ - - treeItems = this._tenants.map( el => { - return new AS3item(el, '', '', 'as3Tenant', vscode.TreeItemCollapsibleState.None, - { command: 'f5-as3.getDecs', title: 'Get Tenant Declaration', arguments: [el] }); - }); - - } else if (element.label === 'Tasks'){ - - treeItems = this._tasks.map( (task: any) => { - return new AS3item(task.iId.split('-').pop(), task.timeStamp, '', '', vscode.TreeItemCollapsibleState.None, - { command: 'f5-as3.getTask', title: '', arguments: [task.iId] }); - }); } else { + /** - * this should happen when a target is selected + * todo: look at moving this to the refresh function, might cut back on how often it gets called */ + await this.getTenants(); // refresh tenant information + await this.getTasks(); // refresh tasks information - const x = element.command?.arguments?.map( el => { - return new AS3item(el, '', '', '', vscode.TreeItemCollapsibleState.None, - { command: 'f5-as3.getDecs', title: 'Get Tenant Declaration', arguments: [el] }); - }); - treeItems = x ? x : []; - } + const taskCount = this._tasks.length !== 0 ? this._tasks.length.toString() : ''; - } else { + // if we have bigiQ/targets + if (this.targets) { - /** - * todo: look at moving this to the refresh function, might cut back on how often it gets called - */ - await this.getTenants(); // refresh tenant information - await this.getTasks(); // refresh tasks information + const targetCount = Object.keys(this.as3DeclareMap as object).length.toString(); + treeItems.push( + new AS3item('Targets', targetCount, '', '', TreeItemCollapsibleState.Collapsed, + { command: '', title: '', arguments: [''] }) + ); + } else { - const targetCount = this._bigiqTenants.length !== 0 ? this._bigiqTenants.length.toString() : ''; - const tenCount = this._tenants.length !== 0 ? this._tenants.length.toString() : ''; - const taskCount = this._tasks.length !== 0 ? this._tasks.length.toString() : ''; + const tenCount = Object.keys(this.as3DeclareMap as object).length.toString(); + treeItems.push( + new AS3item('Tenants', tenCount, 'Get All Tenants', '', TreeItemCollapsibleState.Collapsed, + { command: 'f5-as3.getDecs', title: '', arguments: [this.declare] }) + ); - // if we have bigiq... - if(this._bigiqTenants.length > 0){ - treeItems.push( - new AS3item('Targets', targetCount, '', '', vscode.TreeItemCollapsibleState.Collapsed, - { command: '', title: '', arguments: [''] }) - ); - } + } - // if we have bigip - if(this._tenants.length > 0){ treeItems.push( - new AS3item('Tenants', tenCount, 'Get All Tenants', '', vscode.TreeItemCollapsibleState.Collapsed, - { command: 'f5-as3.getDecs', title: '', arguments: [''] }) + new AS3item('Tasks', taskCount, 'Get All Tasks', '', TreeItemCollapsibleState.Collapsed, + { command: 'f5-as3.getTask', title: '', arguments: [this._tasks] }) ); } - - - treeItems.push( - new AS3item('Tasks', taskCount, 'Get All Tasks', '', vscode.TreeItemCollapsibleState.Collapsed, - { command: 'f5-as3.getTask', title: '', arguments: [''] }) - ); } - return Promise.resolve(treeItems); + return Promise.resolve(treeItems); } private async getTenants() { - this._tenants = []; // clear current tenant list - this._bigiqTenants = []; // clear current bigiq tenant list - - const tenCall: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare/`); - - /** - * got an array, so this should be a bigiq list of devices with tenant information - */ - if(Array.isArray(tenCall.data)) { - this._bigiqTenants = tenCall.data.map( (el: any) => { - - const target = el.target.address; // got target bigip - - // now loop through to get all tenants - const tensList: string[] = []; - Object.entries(el).forEach(([key, val]) => { - if (isObject(val) && key !== 'target'){ - tensList.push(key); - } - }); - return { target, tensList }; - }); + // await ext.f5Client?.as3?.getDecs() + await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/declare/`) + .then(async (resp: any) => { + if ( resp.status === 200) { + // set targets boolens so we know if we are workign with targets or just tenants + this.targets = await targetDecsBool(resp.data); + + // assign the raw /declare output + this.declare = Array.isArray(resp.data) ? resp.data : [ resp.data ]; + + // create target/tenant/app map + this.as3DeclareMap = await mapAs3(resp.data); - } else { - /** - * should be a single bigip tenants object - * loop through, return object keys - */ - for ( const [tenant, dec] of Object.entries(tenCall.data)) { - if(isObject(dec) && tenant !== 'controls' && tenant !== 'target') { - this._tenants.push(tenant); } - } - } + + + }); } private async getTasks() { - const tasks: any = await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/task/`); + // await ext.f5Client?.as3?.getTasks() + await ext.mgmtClient?.makeRequest(`/mgmt/shared/appsvcs/task/`) + .then((resp: any) => { + this._tasks = []; // clear current tenant list + this._tasks = resp.data.items.map((item: any) => { + // if no decs in task or none on box, it returns limited details, but the request still gets an ID, so we blank in what's not there - also happens when getting-tasks + const timeStamp = item.declaration.hasOwnProperty('controls') ? item.declaration.controls.archiveTimestamp : ''; + const iId = item.id; + + return { iId, timeStamp }; + }); + }); + } +} - this._tasks = []; // clear current tenant list - this._tasks = tasks.data.items.map((item:any) => { - // if no decs in task or none on box, it returns limited details, but the request still gets an ID, so we blank in what's not there - also happens when getting-tasks - const timeStamp = item.declaration.hasOwnProperty('controls') ? item.declaration.controls.archiveTimestamp : ''; - const iId = item.id; +/** + * returns true if working with targets + * @param declare /declare endpoint output + */ +export async function targetDecsBool(declare: AdcDeclaration | AdcDeclaration[]): Promise { - return { iId, timeStamp }; - }); + // if array from bigiq/targets, assign, else put in array + const declareArray: AdcDeclaration[] = Array.isArray(declare) ? declare : [declare]; + + if (declareArray[0]?.target as boolean) { + return true; + } else { + return false; } +} + + +/** + * returns as3 application index of target?/tenant/app/app-stats + * + * ```ts + * + * ``` + * + * @param declare /declare endpoint output + */ +export async function mapAs3(declare: AdcDeclaration | AdcDeclaration[]): Promise { + + // if array from bigiq/targets, assign, else put in array + const declareArray: AdcDeclaration[] = Array.isArray(declare) ? declare : [declare]; + + const as3Map: As3AppMap = {}; + + /** + * this map represents what I feel is a more modern approach to building json structures. The F5 ATC method for building json structures heavily utilizes named objects which can be very difficult to crawl/discover, since one has to constantly loop and check to see what kind of data the key holds. This method can be very clean and concise + * + * The other method, which seems to be the more common method, makes the structure bigger but more predictable without having to inspect each object param to see what kind it is. !Example, the github api has many nested objects and lists, but rarely any 'named' objects. This predictable structure of object keys makes it way easier to type objects for typescript and get information from within the structure without having to discover each key/value (just look for the key, not inspect the keys value for another key/value pair). + * + * This new map is not returned as part of the function output, but built within this function as a demonstration and excercise to explore both options. The thought it to move this function to f5-conx-core and utilize across the projects + */ + const as3MapNew = []; + + // go through each item in the targets array + declareArray.map((el: any) => { + + const tenants: As3AppMap = {}; + const tenantsNew: As3AppMapTenants[] = []; + + // get target if defined + const target + = el?.target?.address ? el.target.address + : el?.target?.hostname ? el.target.hostname + : undefined; + + // loop through declaration (adc) level + Object.entries(el).forEach(([key, val]) => { + + // named object for each tenant + if (isObject(val) && key !== 'target' && key !== 'controls') { + + let apps2: any = {}; + let appsNew: { app: string; components: {}; }[] = []; + + // loop through items of the tenant + Object.entries(val as object).forEach(([tKey, tVal]) => { + + // if we are at an application object + if (isObject(tVal)) { + const appProps: any = {}; + + // loop through the items of the application + Object.entries(tVal).forEach(([aKey, aVal]) => { + + // look at the objects (application pieces) + if (isObject(aVal) && (aVal as { class: string })?.class) { + + const appVal: { class: string } = aVal as { class: string }; + + // capture the class of each application piece + if (appVal?.class in appProps) { + // already have this key, so add one + appProps[appVal.class] = appProps[appVal.class] + 1; + } else { + // key not detected, so create it + appProps[appVal.class] = 1; + } + } + }); + apps2[tKey] = appProps; + appsNew.push({ + app: tKey, + components: appProps + }); + } + }); + tenants[key] = apps2; + tenantsNew.push({ + tenant: key, + apps: appsNew + }); + } + }); + + if (target) { + as3Map[target] = tenants; + as3MapNew.push({ + target, + tenants: tenantsNew + }); + } else { + Object.assign(as3Map, tenants); + as3MapNew.push(...tenantsNew); + } + + }); + return as3Map; +} + +export async function as3AppsInTenant(as3Tenant: object): Promise { + const apps: string[] = []; + + // loop through the items of the tenant + Object.entries(as3Tenant).forEach(([aKey, aVal]) => { + // look at the objects (application pieces) + if (isObject(aVal)) { + apps.push(aKey); + } + }); + + return apps; +} + +export async function as3AppStats(as3App: object): Promise { + + const appProps: any = {}; + + // loop through the items of the application + Object.entries(as3App).forEach(([aKey, aVal]) => { + + // look at the objects (application pieces) + if (isObject(aVal) && (aVal as { class: string })?.class) { + + const appVal: { class: string } = aVal as { class: string }; + + // capture the class of each application piece + if (appVal?.class in appProps) { + // already have this key, so add one + appProps[appVal.class] + 1; + } else { + // key not detected, so create it + appProps[appVal.class] = 1; + } + } + }); + + return appProps; } -function isObject(x: any) { - // return object(true) if json object - return x === Object(x); + +/** + * sort tree items by label + */ +export function sortTreeItems(treeItems: AS3item[]) { + return treeItems.sort((a, b) => { + const x = a.label.toLowerCase(); + const y = b.label.toLowerCase(); + if (x < y) { + return -1; + } else { + return 1; + } + }); } -class AS3item extends vscode.TreeItem { +/** + * checks if input is object + * + * ***an array is an object!!! *** + * - use Array.isArray(x) => boolean + * @param x + * @returns boolean + */ +export function isObject(x: any): boolean { + return ( x !== null && typeof x === 'object' ? true : false); +}; + + + + +class AS3item extends TreeItem { constructor( public readonly label: string, public description: string, public tooltip: string, public context: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly command: vscode.Command, + public readonly collapsibleState: TreeItemCollapsibleState, + public readonly command: Command, ) { super(label, collapsibleState); } diff --git a/src/treeViewsProviders/cfgTreeProvider.ts b/src/treeViewsProviders/cfgTreeProvider.ts index 303cec2..d30286e 100644 --- a/src/treeViewsProviders/cfgTreeProvider.ts +++ b/src/treeViewsProviders/cfgTreeProvider.ts @@ -3,7 +3,6 @@ import { TreeItem, TreeItemCollapsibleState, Event, - commands, EventEmitter, Uri, Command, @@ -16,7 +15,10 @@ import { } from 'vscode'; import { ext } from '../extensionVariables'; -import { BigipConfObj, ConfigFiles, Explosion, TmosApp } from 'f5-corkscrew'; +import { BigipConfObj, Explosion, TmosApp } from 'f5-corkscrew'; + +// remodel everything here like this example: https://github.com/microsoft/vscode-extension-samples/blob/master/tree-view-sample/src/testView.ts +// it will provide a working 'reveal' function and a browsable tmos config tree in the view /** * Tree view provider class that hosts and present the data for the Config Explorer view @@ -26,50 +28,63 @@ export class CfgProvider implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - private bigipConfs: ConfigFiles = []; - private explosion: Explosion | undefined; - private confObj: BigipConfObj | undefined; + explosion: Explosion | undefined; + confObj: BigipConfObj | undefined; + /** + * trying to use this to make the view in focus after initialization + */ + viewElement: CfgApp | undefined; constructor() { } - async explodeConfig(configs: ConfigFiles, cfgObj: BigipConfObj, explosion: Explosion){ - // set context to make view visible - commands.executeCommand('setContext', 'f5.cfgTreeContxt', true); - // this.clear(); - this.bigipConfs = configs; - this.confObj = cfgObj; + async explodeConfig(explosion: Explosion){ this.explosion = explosion; - this.refresh(); } - refresh(): void { + async refresh(): Promise { this._onDidChangeTreeData.fire(undefined); } clear(): void { - // hide view from being visible - commands.executeCommand('setContext', 'f5.cfgTreeContxt', false); - // clear all the data - this.bigipConfs = []; this.confObj = undefined; this.explosion = undefined; this.refresh(); } - + + + getParent(element: CfgApp): CfgApp { + return element; + } getTreeItem(element: CfgApp): TreeItem { return element; - } + } async getChildren(element?: CfgApp): Promise { + if(!this.explosion) { + return Promise.resolve([]); + } + var treeItems: CfgApp[] = []; if(element) { if (element.label === 'Apps') { if (this.explosion) { - treeItems = this.explosion.config.apps.map((el: TmosApp) => { + + // sort the apps + const apps = this.explosion.config.apps.sort( (a, b) => { + const x = a.name.toLowerCase(); + const y = b.name.toLowerCase(); + if ( x < y ) { + return -1; + } else { + return 1; + } + }); + + treeItems = apps.map((el: TmosApp) => { const count = el.configs.length.toString(); return new CfgApp(el.name, '', count, 'cfgAppItem', TreeItemCollapsibleState.None, {command: 'f5.cfgExplore-show', title: '---newCmd', arguments: [el.configs]}); @@ -78,7 +93,7 @@ export class CfgProvider implements TreeDataProvider { } else if (element.label === 'Sources') { - treeItems = this.bigipConfs?.map((el: any) => { + treeItems = this.explosion.config.sources.map((el: any) => { return new CfgApp(el.fileName, '', '', '', TreeItemCollapsibleState.None, {command: 'f5.cfgExplore-show', title: '', arguments: [el.content]}); }); @@ -94,46 +109,45 @@ export class CfgProvider implements TreeDataProvider { : this.explosion?.config.sources[0].fileName ? this.explosion.config.sources[0].fileName // default value - this should never happen, but TS needed it... - : ''; + : 'hostname'; const version = this.explosion?.stats.sourceTmosVersion; const inputFileType = this.explosion?.inputFileType; const desc = `${inputFileType} - ${version}`; - treeItems.push(new CfgApp(title, 'Source Device Details', desc, '', TreeItemCollapsibleState.None)); + this.viewElement = new CfgApp(title, 'Source Config Details', desc, '', TreeItemCollapsibleState.None); + treeItems.push(this.viewElement); - const allSources = this.bigipConfs?.map((el) => el.content); + const allSources = this.explosion.config.sources.map((el) => el.content); - treeItems.push(new CfgApp('Sources', '', this.bigipConfs.length.toString(), '', TreeItemCollapsibleState.Collapsed, + treeItems.push(new CfgApp('Sources', '', this.explosion.config.sources.length.toString(), '', TreeItemCollapsibleState.Collapsed, {command: 'f5.cfgExplore-show', title: '', arguments: [allSources]})); - - // treeItems.push(new CfgApp('bigip_base.conf', 'just idea to add...', TreeItemCollapsibleState.None, - // {command: 'f5.cfgExplore-show', title: '', arguments: [{item: this.bigipConf, type: 'conf'}]})); // get all the apps configs const brkr = '\n\n##################################################\n\n'; const allApps = this.explosion?.config.apps.map((el: TmosApp) => el.configs.join('\n').concat(brkr)); - // allApps?.forEach( el => { - - // }) - // const allAppsFlat = allApps.join('\n\n##################################################\n\n'); const appsTotal = this.explosion?.config.apps ? this.explosion.config.apps.length.toString() : ''; const baseTotal = this.explosion?.config.base ? this.explosion.config.base.length.toString() : ''; const logTotal = this.explosion?.logs ? this.explosion.logs.length.toString() : ''; - // const taskCount = this._tasks.length !== 0 ? this._tasks.length.toString() : ''; treeItems.push(new CfgApp('Apps', 'All apps', appsTotal, '', TreeItemCollapsibleState.Collapsed, {command: 'f5.cfgExplore-show', title: '', arguments: [allApps]})); - - treeItems.push(new CfgApp('Base', '', baseTotal, '', TreeItemCollapsibleState.None, - {command: 'f5.cfgExplore-show', title: '', arguments: [this.explosion?.config.base]})); + + if (this.explosion?.config?.base) { + treeItems.push(new CfgApp('Base', '', baseTotal, '', TreeItemCollapsibleState.None, + {command: 'f5.cfgExplore-show', title: '', arguments: [this.explosion.config.base]})); + } - treeItems.push(new CfgApp('Logs', '', logTotal, '', TreeItemCollapsibleState.None, - {command: 'f5.cfgExplore-show', title: '', arguments: [this.explosion?.logs]})); + if ( this.explosion?.logs) { + treeItems.push(new CfgApp('Logs', '', logTotal, '', TreeItemCollapsibleState.None, + {command: 'f5.cfgExplore-show', title: '', arguments: [this.explosion.logs]})); + } - treeItems.push(new CfgApp('Config Object', '', '', '', TreeItemCollapsibleState.None, - {command: 'f5.cfgExplore-show', title: '', arguments: [this.confObj]})); + if(this.confObj) { + treeItems.push(new CfgApp('Config Object', '', '', '', TreeItemCollapsibleState.None, + {command: 'f5.cfgExplore-show', title: '', arguments: [this.confObj]})); + } } return Promise.resolve(treeItems); @@ -148,49 +162,17 @@ export class CfgProvider implements TreeDataProvider { let docName = 'app.conf'; let docContent: string; - - // if (isObject(items)) { - - // // if object = config object -> stringify as needed - // // if () { - // docContent = JSON.stringify(items, undefined, 4); - // // } - - // } else - // if (Array.isArray(items) && items.length === 1 && false) { if (Array.isArray(items)) { docContent = items.join('\n'); - } else if (isObject(items)) { + } else if (Object(items)) { // if array -> single selection, just join internal array normally -> display contents docContent = JSON.stringify(items, undefined, 4); - - // } else { - // // just string return - // docContent = items; - // // this shouo } - // if(x.type === 'app' || x.type === 'conf' || x.type === 'log'){ - // // render as app.conf - // docContent = x.item.join('\n'); - - // } else if ( x.type === 'all-apps') { - - // // feed single conf file - // // docContent = x.item[0]; - // const brkr = '\n\n##################################################\n\n'; - // docContent = x.item.join(brkr); - - // } else { - // // should be a obj - make it readable - // docContent = JSON.stringify(x.item, undefined, 2); - // docName = 'app.json'; - // } - editors.forEach(el => { if (el.document.fileName === 'app.conf' || el.document.fileName === 'app.json') { viewColumn = el.viewColumn; @@ -214,18 +196,6 @@ export class CfgProvider implements TreeDataProvider { } } -function isObject(x: any) { - // return object(true) if json object - return x === Object(x); -} - -// const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []) - -// function flatDeep(arr: any[], d = 1) { -// return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), []) : arr.slice(); -// }; - - export class CfgApp extends TreeItem { constructor( public readonly label: string, diff --git a/src/treeViewsProviders/hostsTreeProvider.ts b/src/treeViewsProviders/hostsTreeProvider.ts index 195479a..ca633f6 100644 --- a/src/treeViewsProviders/hostsTreeProvider.ts +++ b/src/treeViewsProviders/hostsTreeProvider.ts @@ -1,9 +1,10 @@ import * as vscode from 'vscode'; +import { TreeItemCollapsibleState } from 'vscode'; import { ext } from '../extensionVariables'; import logger from '../utils/logger'; export class F5TreeProvider implements vscode.TreeDataProvider { - + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; @@ -19,11 +20,11 @@ export class F5TreeProvider implements vscode.TreeDataProvider { } getChildren(element?: F5Host): Thenable { - - var bigipHosts: any | undefined = vscode.workspace.getConfiguration().get('f5.hosts'); + + var bigipHosts: any | undefined = vscode.workspace.getConfiguration().get('f5.hosts'); // logger.debug(`bigips: ${JSON.stringify(bigipHosts)}`); - - if ( bigipHosts === undefined) { + + if (bigipHosts === undefined) { throw new Error('No configured hosts - from hostTreeProvider'); } @@ -41,15 +42,15 @@ export class F5TreeProvider implements vscode.TreeDataProvider { */ // if devices in list and first list item is a string, not an object - if(bigipHosts.length > 0 && typeof(bigipHosts[0]) === 'string') { - - logger.debug('devices are type of:', typeof(bigipHosts[0])); - bigipHosts = bigipHosts.map( (el: any) => { + if (bigipHosts.length > 0 && typeof (bigipHosts[0]) === 'string') { + + logger.debug('devices are type of:', typeof (bigipHosts[0])); + bigipHosts = bigipHosts.map((el: any) => { let newObj: { device: string } = { device: el }; logger.debug(`device coverted from: ${el} -> ${JSON.stringify(newObj)}`); return newObj; }); - + logger.debug('conversion complete, saving new devices list:', bigipHosts); // save config vscode.workspace.getConfiguration().update('f5.hosts', bigipHosts, vscode.ConfigurationTarget.Global); @@ -58,7 +59,7 @@ export class F5TreeProvider implements vscode.TreeDataProvider { logger.debug('New device configuration list detected -> no conversion'); } - const treeItems = bigipHosts.map( (item: { + const treeItems = bigipHosts.map((item: { device: string; provider: string; }) => { @@ -72,40 +73,58 @@ export class F5TreeProvider implements vscode.TreeDataProvider { // } // add default provider=local if not defined - if(!item.hasOwnProperty('provider')){ + if (!item.hasOwnProperty('provider')) { item['provider'] = 'local'; } + let itemCollapsibleStat = TreeItemCollapsibleState.None; + // if (item.device === ext.f5Client?.device.device) { + // logger.debug('hostsTreeProvider, These devices are equal!'); + // itemCollapsibleStat = TreeItemCollapsibleState.Expanded; + // // start getting ucs/qkview details + + // ext.f5Client.ucs?.list() + // .then(resp => this.ucsList = resp.data.items); + + // } + // logger.debug('built item', device); - const treeItem = new F5Host(item.device, item.provider, vscode.TreeItemCollapsibleState.None, { - command: 'f5.connectDevice', - title: 'hostTitle', - arguments: [item] - }); + const treeItem = new F5Host( + item.device, + item.provider, + '', + 'f5Host', + itemCollapsibleStat, + { + command: 'f5.connectDevice', + title: 'hostTitle', + arguments: [item] + } + ); return treeItem; }); - return Promise.resolve(treeItems); + return Promise.resolve(treeItems); } async addDevice(newHost: string) { - let bigipHosts: {device: string} [] | undefined = await vscode.workspace.getConfiguration().get('f5.hosts'); + let bigipHosts: { device: string }[] | undefined = await vscode.workspace.getConfiguration().get('f5.hosts'); - if(!newHost) { + if (!newHost) { // attempt to get user to input new device newHost = await vscode.window.showInputBox({ prompt: 'Device/BIG-IP/Host', placeHolder: '@', ignoreFocusOut: true }) - .then( el => { - if(el) { - return el; - } else { - throw new Error('user escapted new device input'); - } - }); + .then(el => { + if (el) { + return el; + } else { + throw new Error('user escapted new device input'); + } + }); } if (bigipHosts === undefined) { @@ -119,8 +138,8 @@ export class F5TreeProvider implements vscode.TreeDataProvider { const deviceRex = /^[\w-.]+@[\w-.]+(:[0-9]+)?$/; // matches any username combo an F5 will accept and host/ip const devicesString = JSON.stringify(bigipHosts); - if (!devicesString.includes(`\"${newHost}\"`) && deviceRex.test(newHost)){ - bigipHosts.push({device: newHost}); + if (!devicesString.includes(`\"${newHost}\"`) && deviceRex.test(newHost)) { + bigipHosts.push({ device: newHost }); await vscode.workspace.getConfiguration().update('f5.hosts', bigipHosts, vscode.ConfigurationTarget.Global); // vscode.window.showInformationMessage(`Adding ${newHost} to list!`); this.refresh(); @@ -137,52 +156,52 @@ export class F5TreeProvider implements vscode.TreeDataProvider { logger.debug(`Remove Host command: ${JSON.stringify(hostID)}`); this.clearPassword(hostID.label); // clear cached password for device - - let bigipHosts: {device: string} [] | undefined = vscode.workspace.getConfiguration().get('f5.hosts'); - - if ( !bigipHosts || !hostID) { + + let bigipHosts: { device: string }[] | undefined = vscode.workspace.getConfiguration().get('f5.hosts'); + + if (!bigipHosts || !hostID) { throw new Error('device delete, no devices in config or no selected host to delete'); } - const newBigipHosts = bigipHosts.filter( item => item.device !== hostID.label); + const newBigipHosts = bigipHosts.filter(item => item.device !== hostID.label); - if(bigipHosts.length === (newBigipHosts.length+1) ) { + if (bigipHosts.length === (newBigipHosts.length + 1)) { logger.debug('successfully removed device!!!'); await vscode.workspace.getConfiguration().update('f5.hosts', newBigipHosts, vscode.ConfigurationTarget.Global); - setTimeout( () => { this.refresh();}, 300); + setTimeout(() => { this.refresh(); }, 300); return `successfully removed ${hostID.label} from devices configuration`; - } else { + } else { logger.debug('something with remove device FAILED!!!'); throw new Error('something with remove device FAILED!!!'); - } + } } - /** - * clears password - */ - async clearPassword(device?: string) { - - if (device) { - - // passed in from view click or deviceClient - logger.debug('CLEARING KEYTAR PASSWORD CACHE for', device); - return await ext.keyTar.deletePassword('f5Hosts', device); - + /** + * clears password + */ + async clearPassword(device?: string) { + + if (device) { + + // passed in from view click or deviceClient + logger.debug('CLEARING KEYTAR PASSWORD CACHE for', device); + return await ext.keyTar.deletePassword('f5Hosts', device); + } else { - + // get list of items in keytar for the 'f5Hosts' service logger.debug('CLEARING KEYTAR PASSWORD CACHE'); - const one1 = await ext.keyTar.findCredentials('f5Hosts').then( list => { + const one1 = await ext.keyTar.findCredentials('f5Hosts').then(list => { // map through and delete all list.map(item => ext.keyTar.deletePassword('f5Hosts', item.account)); - }); - /** - * future: setup clear all to return an array of touples to show which - * device passwords got cleared - */ + }); + /** + * future: setup clear all to return an array of touples to show which + * device passwords got cleared + */ } - } + } } @@ -190,6 +209,8 @@ export class F5Host extends vscode.TreeItem { constructor( public readonly label: string, public description: string, + public tooltip: string, + public contextValue: string, public readonly collapsibleState: vscode.TreeItemCollapsibleState, public readonly command?: vscode.Command ) { diff --git a/src/utils/as3Models.ts b/src/utils/as3Models.ts new file mode 100644 index 0000000..b7aa1c3 --- /dev/null +++ b/src/utils/as3Models.ts @@ -0,0 +1,615 @@ + + +/** + * target/tenant/app index type + * - now includes app pieces + * + * ```ts + * const exmp = { + * "target": { + * "tenant": { + * "app": { + * Pool: 1, + * Service_HTTP: 1, + * ... + * }, + * }, + * }, + * } + * ``` + * + */ +export interface As3AppMap { + // parentType: 'targets' | 'tenants', + [key: string]: { + [key: string]: { + [key: string]: any; + } + } +} + + + +/** + * target/tenant/app/app-pieces index type + * + * ```ts + * const example = { + * target: "10.200.244.5", + * tenants: [ + * { + * tenant: "core1_sample_01", + * apps: [ + * { + * app: "A1", + * parts: { + * Pool: 1, + * Service_HTTP: 1, + * }, + * }, + * ], + * }, + * ], + * }, + * ``` + * + */ +export interface As3AppMapTargets extends As3AppMapTenants { + target: string; + tenants: As3AppMapTenants +}; + + +/** + * tenant/app/app-pieces index type + * + * ```ts + * const example = { + * tenant: "core1_sample_01", + * apps: [ + * { + * app: "A1", + * parts: { + * Pool: 1, + * Service_HTTP: 1, + * }, + * }, + * ], + * }, + * ``` + * + */ +export interface As3AppMapTenants { + tenant: string; + apps: { + app: string; + components: {}; + }[]; +}; + + + + +export interface As3App { + class: 'Application', + [key: string]: object | string, +} + +export interface As3Declaration { + class: 'AS3', + $schema?: string, + persist?: boolean; + action?: string; + declaration: AdcDeclaration | boolean | string +} + +export interface As3Controls { + archiveTimestamp: string; +} + + +export interface AdcDeclaration { + id: string; + class: 'ADC'; + target?: Target; + updateMode: string; + controls?: As3Controls; + schemaVersion: string; + [key: string]: As3Tenant | As3Controls | Target | string | boolean | undefined +} + + +export interface As3Tenant { + class: 'Tenant', + [key: string]: As3App | string +}; + +export interface Target { + address?: string, + hostname?: string +}; + + +/** + * primary as3 example with TS type declaration + */ +const exampleAs3Declaration: As3Declaration = { + "$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/latest/as3-schema.json", + class: "AS3", + "action": "deploy", + "persist": true, + "declaration": { + "updateMode": "selective", + "class": "ADC", + "schemaVersion": "3.0.0", + "id": "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + "label": "Sample 1", + "remark": "Simple HTTP application with RR pool", + "Sample_01": { + "class": "Tenant", + "A1": { + "class": "Application", + "template": "http", + "serviceMain": { + "class": "Service_HTTP", + "virtualAddresses": [ + "10.0.1.10" + ], + "pool": "web_pool" + }, + "web_pool": { + "class": "Pool", + "monitors": [ + "http" + ], + "members": [{ + "servicePort": 80, + "serverAddresses": [ + "192.0.1.10", + "192.0.1.11" + ] + }] + } + } + } + } +}; + + +/** + * primary as3 example with target parameter and TS type declaration + */ +const exampleAs3DeclarationWithTarget: As3Declaration = { + "$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/latest/as3-schema.json", + class: "AS3", + "action": "deploy", + "persist": true, + "declaration": { + "updateMode": "selective", + "class": "ADC", + "target": { + "address": "10.200.244.5" + }, + "schemaVersion": "3.0.0", + "id": "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + "label": "Sample 1", + "remark": "Simple HTTP application with RR pool", + "Sample_01": { + "class": "Tenant", + "A1": { + "class": "Application", + "template": "http", + "serviceMain": { + "class": "Service_HTTP", + "virtualAddresses": [ + "10.0.1.10" + ], + "pool": "web_pool" + }, + "web_pool": { + "class": "Pool", + "monitors": [ + "http" + ], + "members": [{ + "servicePort": 80, + "serverAddresses": [ + "192.0.1.10", + "192.0.1.11" + ] + }] + } + } + } + } +}; + + +/** + * as3 /declare endpoint output for bigiq with multiple targets + */ +const As3DeclareEndpoint: AdcDeclaration[] = [ + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "10.200.244.5", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + core1_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.1.10", + "192.0.1.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + core1_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.2.10", + "192.0.2.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "10.200.244.6", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + core1_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.2.10", + "192.0.2.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + core1_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.1.10", + "192.0.1.11", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/core1_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "10.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, + { + id: "urn:uuid:33045210-3ab8-4636-9b2a-c98d22ab915d", + class: "ADC", + target: { + address: "192.168.200.131", + }, + updateMode: "selective", + schemaVersion: "3.0.0", + tparty_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.1.20", + "19.0.1.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.1.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + tparty_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.2.20", + "19.0.2.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.2.10", + ], + }, + schemaOverlay: "default", + }, + class: "Tenant", + }, + }, +]; + + +/** + * as3 /declare endpoint output for bigip, no targets tenants only + */ +const as3DeclarationEndpointLTM: AdcDeclaration = { + tparty_sample_02: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.2.20", + "19.0.2.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_02/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.2.10", + ], + }, + }, + class: "Tenant", + }, + tparty_sample_01: { + A1: { + class: "Application", + template: "http", + web_pool: { + class: "Pool", + members: [ + { + servicePort: 80, + serverAddresses: [ + "19.0.1.20", + "19.0.1.21", + ], + }, + ], + monitors: [ + "http", + ], + }, + serviceMain: { + pool: "/tparty_sample_01/A1/web_pool", + class: "Service_HTTP", + virtualAddresses: [ + "19.0.1.10", + ], + }, + }, + class: "Tenant", + }, + Sample_01: { + class: "Tenant", + A1: { + class: "Application", + template: "http", + serviceMain: { + class: "Service_HTTP", + virtualAddresses: [ + "10.0.1.10", + ], + pool: "web_pool", + }, + web_pool: { + class: "Pool", + monitors: [ + "http", + ], + members: [ + { + servicePort: 80, + serverAddresses: [ + "192.0.1.10", + "192.0.1.11", + ], + }, + ], + }, + }, + }, + class: "ADC", + schemaVersion: "3.23.0", + id: "urn:uuid:47fdeacb-804d-43d4-8b8e-836cb4b7ae09", + label: "Converted Declaration", + remark: "Auto-generated by Project Charon", + Common: { + class: "Tenant", + Shared: { + class: "Application", + template: "shared", + app1_t80_vs: { + layer4: "tcp", + iRules: [ + { + bigip: "/Common/_sys_https_redirect", + }, + ], + translateServerAddress: true, + translateServerPort: true, + class: "Service_HTTP", + profileHTTP: { + bigip: "/Common/http", + }, + profileTCP: { + bigip: "/Common/tcp", + }, + virtualAddresses: [ + "192.168.1.21", + ], + virtualPort: 80, + persistenceMethods: [ + ], + snat: "none", + }, + app1_t443_vs: { + layer4: "tcp", + pool: "app1_t80_pool", + translateServerAddress: true, + translateServerPort: true, + class: "Service_HTTP", + profileHTTP: { + bigip: "/Common/http", + }, + profileTCP: { + bigip: "/Common/tcp", + }, + virtualAddresses: [ + "192.168.1.21", + ], + virtualPort: 443, + persistenceMethods: [ + ], + snat: "auto", + }, + app1_t80_pool: { + members: [ + { + addressDiscovery: "static", + servicePort: 80, + serverAddresses: [ + "192.168.1.22", + "192.168.1.23", + ], + shareNodes: true, + }, + ], + monitors: [ + { + bigip: "/Common/http", + }, + { + bigip: "/Common/tcp", + }, + ], + class: "Pool", + }, + }, + }, + updateMode: "selective", + controls: { + archiveTimestamp: "2021-01-27T19:38:38.359Z", + }, +}; \ No newline at end of file diff --git a/src/utils/f5DeviceClient.ts.old b/src/utils/f5DeviceClient.ts.old new file mode 100644 index 0000000..17c1186 --- /dev/null +++ b/src/utils/f5DeviceClient.ts.old @@ -0,0 +1,445 @@ +// 'use strict'; + +// import { Terminal, window, workspace, ProgressLocation, StatusBarAlignment, commands } from 'vscode'; +// import { makeAuth, makeReqAXnew, multiPartUploadSDK, download } from './coreF5HTTPS'; +// import { ext, loadConfig } from '../extensionVariables'; +// import * as utils from './utils'; +// import logger from './logger'; + +// // import { F5Client } from 'f5-conx-core'; + +// export interface Device { +// device: string, +// provider?: string, +// onConnect?: string[], +// onDisconnect?: string[] +// } + +// // /** +// // * +// // * Basic Example: +// // * +// // * ``` +// // * const mgmtClient = new ManagementClient({ +// // * host: '192.0.2.1', +// // * port: 443, +// // * user: 'admin', +// // * password: 'admin' +// // * }); +// // * const variable = await mgmtClient.makeRequest('/mgmt/tm/sys/version'); +// // * ``` +// // */ +// export class MgmtClient { +// device: string; +// host: string; +// port: number | 443; +// provider: string; +// // f5Client: F5Client; +// protected _user: string; +// protected _password: string; +// protected _token: any; +// protected _tmrBar: any; +// protected _tokenTimeout: number = 0; +// private _onConnect: string[] = []; +// private _onDisconnect: string[] = []; +// private terminal: Terminal | undefined; + +// /** +// * @param options function options +// */ +// constructor( +// device: string, +// options: { +// host: string; +// port: number; +// user: string; +// provider: string; +// password: string; +// }) { +// this.device = device; +// this.host = options['host']; +// this.port = options['port'] || 443; +// this.provider = options['provider']; +// this._user = options['user']; +// this._password = options['password']; + +// const newOpts = { +// port: options?.port, +// provider: options?.provider, +// logger +// }; + +// // this.f5Client = new F5Client( +// // options.host, +// // options.user, +// // options.password, +// // newOpts +// // ); + +// this.getConfig(); +// } + +// /** +// * Get vscode workspace configuration for this device +// */ +// private getConfig() { + +// this._onConnect = []; +// this._onDisconnect = []; + +// const bigipHosts: Device[] | undefined = workspace.getConfiguration().get('f5.hosts'); +// const deviceConfig: any = bigipHosts?.find( item => item.device === this.device); + +// // if (deviceConfig?.hasOwnProperty('onConnect')) { +// // this._onConnect = deviceConfig.onConnect; +// // } + +// // if (deviceConfig?.hasOwnProperty('onDisconnect')) { +// // this._onDisconnect = deviceConfig.onDisconnect; +// // } + +// // short hand of above logic +// deviceConfig?.hasOwnProperty('onConnect') ? this._onConnect = deviceConfig.onConnect : []; +// deviceConfig?.hasOwnProperty('onDisconnect') ? this._onDisconnect = deviceConfig.onDisconnect : []; + +// // console.log('done getConfig'); +// } + + +// /** +// * Login (using credentials provided during instantiation) +// * sets/gets/refreshes auth token +// * @returns void +// */ +// private async getToken(): Promise { + +// logger.debug('getting auth token from: ', `${this.host}:${this.port}`); + +// const resp: any = await makeAuth(`${this.host}:${this.port}`, { +// username: this._user, +// password: this._password, +// loginProviderName: this.provider +// }); + +// this._token = resp.data.token; +// this._tokenTimeout = this._token.timeout; + +// // logger.debug('newTokn', this._token); + +// this.tokenTimer(); // start token timer +// } + +// /** +// * connect to f5 and discover ATC services +// * Pulls device/connection details from this. within the class +// */ +// async connect() { +// // await this.disconnect(); +// const progress = await window.withProgress({ +// location: ProgressLocation.Notification, +// title: `Connecting to ${this.host}`, +// cancellable: true +// }, async (progress, token) => { +// token.onCancellationRequested(() => { +// // this logs but doesn't actually cancel... +// logger.debug("User canceled device connect"); +// return new Error(`User canceled device connect`); +// }); + + +// // await this.getToken(); + +// let returnInfo: string[] = []; + + +// // cache password in keytar +// ext.keyTar.setPassword('f5Hosts', this.device, this._password); + +// utils.setHostStatusBar(this.device); // show device bar +// ext.connectBar.hide(); // hide connect bar + +// //********** Host info **********/ +// const hostInfo: any = await this.makeRequest('/mgmt/shared/identified-devices/config/device-info'); +// if (hostInfo.status === 200) { +// const text = `${hostInfo.data.hostname}`; +// const tip = `TMOS: ${hostInfo.data.version}`; +// utils.setHostnameBar(text, tip); +// returnInfo.push(text); +// } + +// progress.report({ message: `CONNECTED, checking installed ATC services...`}); + + +// //********** enable irules view **********/ +// const iRules: any = await this.makeRequest('/mgmt/tm/ltm/rule/'); + +// if(iRules.status === 200) { +// // if irules detected, device is iRulesAble, so set that flag, +// // then reload the config to make the view show +// ext.iRulesAble = true; +// loadConfig(); +// } + +// //********** FAST info **********/ +// const fastInfo: any = await this.makeRequest('/mgmt/shared/fast/info'); +// if (fastInfo.status === 200) { +// const text = `FAST(${fastInfo.data.version})`; +// utils.setFastBar(text); +// returnInfo.push(text); +// } + +// //********** AS3 info **********/ +// const as3Info: any = await this.makeRequest('/mgmt/shared/appsvcs/info'); + +// if (as3Info.status === 200) { +// const text = `AS3(${as3Info.data.version})`; +// const tip = `CLICK FOR ALL TENANTS \r\nschemaCurrent: ${as3Info.data.schemaCurrent} `; +// utils.setAS3Bar(text, tip); +// returnInfo.push(text); +// } + +// //********** DO info **********/ +// const doInfo: any = await this.makeRequest('/mgmt/shared/declarative-onboarding/info'); + +// if (doInfo.status === 200) { +// // for some reason DO responds with a list for version info... +// const text = `DO(${doInfo.data[0].version})`; +// const tip = `schemaCurrent: ${doInfo.data[0].schemaCurrent} `; +// utils.setDOBar(text, tip); +// returnInfo.push(text); +// } + +// //********** TS info **********/ +// const tsInfo: any = await this.makeRequest('/mgmt/shared/telemetry/info'); +// if (tsInfo.status === 200) { +// const text = `TS(${tsInfo.data.version})`; +// const tip = `nodeVersion: ${tsInfo.data.nodeVersion}\r\nschemaCurrent: ${tsInfo.data.schemaCurrent} `; +// utils.setTSBar(text, tip); +// returnInfo.push(text); +// } +// return returnInfo; +// }); +// this.termConnect(); +// return progress; +// } + +// /** +// * multi part upload to f5 +// * gets uploaded via: /mgmt/shared/file-transfer/uploads/ +// * found in remote dir: /var/config/rest/downloads/ +// * @param file full path/file location of source file +// */ +// async upload(file: string = '') { + +// /** +// * todo: add ability to provide buffer data to bypass the need for +// * a temp file +// * - 10.9.2020 - may not be the best route for large files... +// */ + +// // if auth token has expired, it should have been cleared, get new one +// if(!this._token){ +// await this.getToken(); +// } + +// return await multiPartUploadSDK(file, this.host, this.port, this._token.token); +// } + + +// /** +// * download file from connectd f5 +// * remote file location: /shared/images +// * remote api called: /mgmt/cm/autodeploy/software-image-downloads/ +// * @param file name to get +// * @param dest path/file name (./path/test.tar.gz) +// */ +// async download (file: string, dest: string) { + +// // if auth token has expired, it should have been cleared, get new one +// if(!this._token){ +// await this.getToken(); +// } + +// return await download (file, dest, this.host, this.port, this._token.token); +// } + + + +// /** +// * Make HTTP request +// * - utilizes device details/user/pass/token +// * set within the class +// * +// * @param uri request URI +// * @param options function options +// * +// * @returns request response +// */ +// async makeRequest(uri: string, options?: { +// method?: string; +// headers?: object; +// body?: object; +// contentType?: string; +// advancedReturn?: boolean; +// }): Promise { +// options = options || {}; + +// // if auth token has expired, it should have been cleared, get new one +// if(!this._token){ +// await this.getToken(); +// } + +// return await makeReqAXnew( +// this.host, +// uri, +// { +// method: options.method || 'GET', +// port: this.port, +// headers: Object.assign(options.headers || {}, { +// 'X-F5-Auth-Token': this._token.token, +// 'Content-Type': 'application/json' +// }), +// body: options.body || undefined, +// advancedReturn: options.advancedReturn || false +// } +// ); +// } + + +// /** +// * bigip auth token lifetime countdown +// * will clear auth token details when finished +// * prompting the next http call to get a new token +// */ +// private async tokenTimer() { + +// this._tmrBar = window.createStatusBarItem(StatusBarAlignment.Left, 50); +// this._tmrBar.tooltip = 'F5 AuthToken Timer'; +// this._tmrBar.color = 'silver'; + +// const makeVisible = workspace.getConfiguration().get('f5.showAuthTokenTimer'); +// if(makeVisible) { +// this._tmrBar.show(); +// } + +// // consider adding an icon, maybe even spinning +// //https://code.visualstudio.com/api/references/icons-in-labels + +// let intervalId = setInterval(() => { +// this._tmrBar.text = `${this._tokenTimeout}`; +// this._tokenTimeout--; +// if (this._tokenTimeout <= 0) { +// clearInterval(intervalId); +// this._tmrBar.hide(); +// logger.debug('authToken expired'); +// this._token = undefined; // clearing token details should get a new token +// this._tmrBar.dispose(); +// } else if (this._tokenTimeout <= 30){ +// // turn text color reddish/pink to indicate expiring token +// this._tmrBar.color = '#ED5A75'; +// } +// }, 1000); + +// } + + +// /** +// * clears auth token, connected status bars, and onDisconnect commands +// */ +// async disconnect() { + +// this._tokenTimeout = 0; // zero/expire authToken + +// // clear connected details status bars +// utils.setHostStatusBar(); +// utils.setHostnameBar(); +// utils.setFastBar(); +// utils.setAS3Bar(); +// utils.setDOBar(); +// utils.setTSBar(); + +// /** +// * // hide irules/iapps view +// * this should probably dispose of the view or at least clear it's contents? +// * - currently, this just hides the view with all data in it +// * next connect should refresh the data as needed, but there seems to +// * be a better way to do this. +// */ +// commands.executeCommand('setContext', 'f5.tcl', false); +// // ext.iRulesAble = false; + +// // show connect status bar +// ext.connectBar.show(); + +// this.termDisConnect(); +// } + + +// /** +// * clears password for currently connected device +// * to be called by http since it won't know current +// * device details +// */ +// async clearPassword() { +// await commands.executeCommand('f5.clearPassword', { label: this.device }); +// } + +// /** +// * issues terminal commands defined for "onConnect" +// */ +// private termConnect() { + +// // if we have configuration in the onConnect +// if (this._onConnect.length > 0) { + +// // if we don't already have a terminal, create one +// if (!this.terminal) { +// this.terminal = window.createTerminal('f5-cmd'); +// this.terminal.show(true); +// } + +// // loop through onConnect commands and issue them +// this._onConnect?.forEach((el: string) => { + +// // swap out variable as needed +// el = el.replace(/\${this.device}/, `${this.device}`); +// setTimeout( () => { +// this.terminal?.sendText(el); +// }, 500); +// }); +// }; + +// } + +// /** +// * issue terminal commands defined for "onDisonnect" +// */ +// private termDisConnect() { + +// // if we have onDisconnect commands +// if (this._onDisconnect) { + +// // if we don't already have a terminal, create one (very corner cases) +// if (!this.terminal) { +// this.terminal = window.createTerminal('f5-cmd'); +// this.terminal.show(true); +// } + +// // if _onDisconnect has a value, loop through each as terminal commands +// this._onDisconnect?.forEach((el: string) => { +// setTimeout( () => { +// this.terminal?.sendText(el); +// }, 500); +// }); +// } + +// // if we have a terminal, and we are disconnecting, delete terminal when done +// if (this.terminal) { +// setTimeout( () => { +// this.terminal?.dispose(); +// }, 1000); +// } +// } + +// } + diff --git a/src/utils/rpmMgmt.ts b/src/utils/rpmMgmt.ts index 45826a2..b2f2623 100644 --- a/src/utils/rpmMgmt.ts +++ b/src/utils/rpmMgmt.ts @@ -312,7 +312,7 @@ export async function rpmInstaller (rpm: string) { * example: https://api.github.com/repos/F5Networks/f5-appsvcs-templates/releases/27113449 * @returns full path/file of rpm */ -async function getRPMgit(assetUrl: string) { +export async function getRPMgit(assetUrl: string, dir?: string) { // get release asset information const resp = await axios(assetUrl); // logger.debug('Getting github asset details', resp); @@ -320,7 +320,8 @@ async function getRPMgit(assetUrl: string) { const extDir = ext.context.extensionPath; // todo: move cache directory to a real linux/windows temp directory... - const rpmDir = path.join(extDir, 'atc_ilx_rpm_cache'); + // if we got a directory assign it, if not, use cache + const rpmDir = dir ? dir : path.join(extDir, 'atc_ilx_rpm_cache'); if (!fs.existsSync(rpmDir)) { logger.debug('CREATING ATC ILX RPM CACHE DIRECTORY'); @@ -355,7 +356,10 @@ async function getRPMgit(assetUrl: string) { logger.debug('assets done downloading'); // get array item that has the installable rpm - const rpmAsset = assetSet.filter( (el: { name: string; }) => el.name.endsWith('.rpm')); + const rpmAsset = []; + // did we download an rpm or vsix? + rpmAsset.push(...assetSet.filter( (el: { name: string; }) => el.name.endsWith('.rpm'))); + rpmAsset.push(...assetSet.filter( (el: { name: string; }) => el.name.endsWith('.vsix'))); const assetFpath = path.join(rpmDir, rpmAsset[0].name); // return just rpm name @@ -368,7 +372,7 @@ async function getRPMgit(assetUrl: string) { * @param url atc github releases url * @returns asset name and download url as object */ -async function listGitReleases(url: string){ +export async function listGitReleases(url: string){ const resp = await axios.get(url); var mapEd; if(resp.status === 200) {