├── .eslintrc.js ├── .gitignore ├── .secrets.baseline ├── .travis.yml ├── .vscodeignore ├── CONTRIBUTING.md ├── DEVELOPMENT.md ├── LICENSE.txt ├── README.md ├── assets ├── icon.png ├── slack.png └── stack_overflow.png ├── cla ├── README.md ├── cla-corporate.pdf └── cla-individual.pdf ├── package-lock.json ├── package.json ├── scripts ├── detect_secrets └── publish ├── src ├── commands │ ├── iam │ │ ├── index.ts │ │ └── serviceId.ts │ ├── plugin │ │ ├── index.ts │ │ ├── install.ts │ │ └── updateUninstall.ts │ └── resource │ │ ├── index.ts │ │ └── serviceAlias.ts ├── consts.ts ├── extension.ts ├── ibmcloud │ ├── cf.ts │ ├── iam.ts │ ├── plugin.ts │ ├── resource.ts │ └── target.ts └── util │ ├── CommandDetection.ts │ ├── IBMCloudTerminal.ts │ ├── LoginManager.ts │ ├── PromptingCommand.ts │ └── SystemCommand.ts ├── test ├── extension.test.ts ├── index.ts └── runTest.ts ├── tsconfig.json └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: [ 7 | '@typescript-eslint', 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | rules: { 14 | 'semi': [2, "always"], 15 | '@typescript-eslint/no-unused-vars': 0, 16 | '@typescript-eslint/no-explicit-any': 0, 17 | '@typescript-eslint/explicit-module-boundary-types': 0, 18 | '@typescript-eslint/no-non-null-assertion': 0, 19 | '@typescript-eslint/no-empty-function': 0, 20 | '@typescript-eslint/no-this-alias': 'warn', 21 | '@typescript-eslint/no-var-requires': 'warn' 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # TypeScript ignore 40 | 41 | node_modules/ 42 | built/* 43 | tests/cases/rwc/* 44 | tests/cases/test262/* 45 | tests/cases/perf/* 46 | !tests/cases/webharness/compilerToString.js 47 | test-args.txt 48 | ~*.docx 49 | \#*\# 50 | .\#* 51 | tests/baselines/local/* 52 | tests/baselines/local.old/* 53 | tests/services/baselines/local/* 54 | tests/baselines/prototyping/local/* 55 | tests/baselines/rwc/* 56 | tests/baselines/test262/* 57 | tests/baselines/reference/projectOutput/* 58 | tests/baselines/local/projectOutput/* 59 | tests/baselines/reference/testresults.tap 60 | tests/services/baselines/prototyping/local/* 61 | tests/services/browser/typescriptServices.js 62 | scripts/authors.js 63 | scripts/configureNightly.js 64 | scripts/processDiagnosticMessages.d.ts 65 | scripts/processDiagnosticMessages.js 66 | scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js 67 | src/harness/*.js 68 | src/compiler/diagnosticInformationMap.generated.ts 69 | src/compiler/diagnosticMessages.generated.json 70 | rwc-report.html 71 | *.swp 72 | build.json 73 | *.actual 74 | tests/webTestServer.js 75 | tests/webTestServer.js.map 76 | tests/webhost/*.d.ts 77 | tests/webhost/webtsc.js 78 | tests/cases/**/*.js 79 | tests/cases/**/*.js.map 80 | *.config 81 | scripts/debug.bat 82 | scripts/run.bat 83 | scripts/word2md.js 84 | scripts/buildProtocol.js 85 | scripts/ior.js 86 | scripts/buildProtocol.js 87 | scripts/*.js.map 88 | scripts/typings/ 89 | coverage/ 90 | internal/ 91 | **/.DS_Store 92 | .settings 93 | **/.vs 94 | **/.vscode 95 | !**/.vscode/tasks.json 96 | !tests/cases/projects/projectOption/**/node_modules 97 | !tests/cases/projects/NodeModulesSearch/**/* 98 | !tests/baselines/reference/project/nodeModules*/**/* 99 | .idea 100 | yarn.lock 101 | out/* 102 | **/.vscode-test 103 | *.vsix 104 | test/workspace 105 | -------------------------------------------------------------------------------- /.secrets.baseline: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": { 3 | "files": "^.secrets.baseline$|package-lock.json", 4 | "lines": null 5 | }, 6 | "generated_at": "2022-04-27T20:31:30Z", 7 | "plugins_used": [ 8 | { 9 | "name": "AWSKeyDetector" 10 | }, 11 | { 12 | "name": "ArtifactoryDetector" 13 | }, 14 | { 15 | "name": "AzureStorageKeyDetector" 16 | }, 17 | { 18 | "base64_limit": 4.5, 19 | "name": "Base64HighEntropyString" 20 | }, 21 | { 22 | "name": "BasicAuthDetector" 23 | }, 24 | { 25 | "name": "BoxDetector" 26 | }, 27 | { 28 | "name": "CloudantDetector" 29 | }, 30 | { 31 | "ghe_instance": "github.ibm.com", 32 | "name": "GheDetector" 33 | }, 34 | { 35 | "name": "GitHubTokenDetector" 36 | }, 37 | { 38 | "hex_limit": 3, 39 | "name": "HexHighEntropyString" 40 | }, 41 | { 42 | "name": "IbmCloudIamDetector" 43 | }, 44 | { 45 | "name": "IbmCosHmacDetector" 46 | }, 47 | { 48 | "name": "JwtTokenDetector" 49 | }, 50 | { 51 | "keyword_exclude": null, 52 | "name": "KeywordDetector" 53 | }, 54 | { 55 | "name": "MailchimpDetector" 56 | }, 57 | { 58 | "name": "NpmDetector" 59 | }, 60 | { 61 | "name": "PrivateKeyDetector" 62 | }, 63 | { 64 | "name": "SlackDetector" 65 | }, 66 | { 67 | "name": "SoftlayerDetector" 68 | }, 69 | { 70 | "name": "SquareOAuthDetector" 71 | }, 72 | { 73 | "name": "StripeDetector" 74 | }, 75 | { 76 | "name": "TwilioKeyDetector" 77 | } 78 | ], 79 | "results": {}, 80 | "version": "0.13.1+ibm.48.dss", 81 | "word_list": { 82 | "file": null, 83 | "hash": null 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: bionic 3 | 4 | addons: 5 | apt: 6 | packages: 7 | - python3 8 | - python3-pip 9 | - python3-setuptools 10 | 11 | language: node_js 12 | 13 | node_js: 14 | - 16 15 | 16 | os: 17 | - linux 18 | 19 | before_install: 20 | - sudo apt-get update && sudo apt-get upgrade -y openssl 21 | 22 | install: 23 | # Required to install detect-secrets 24 | - sudo chmod o+rwx /usr/lib/python3/dist-packages/ 25 | - python3 -m pip install -U pip 26 | - pip3 install --upgrade "git+https://github.com/ibm/detect-secrets.git@master#egg=detect-secrets" 27 | - npm install 28 | 29 | script: 30 | - npm run detect_secrets 31 | - npm run lint 32 | - npm run build 33 | 34 | 35 | deploy: 36 | provider: script 37 | script: "npm run vscode:publish" 38 | skip_cleanup: true 39 | on: 40 | tags: true 41 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | scripts/** 6 | src/** 7 | **/*.map 8 | .gitignore 9 | tsconfig.json 10 | .eslintrc.js 11 | vsc-extension-quickstart.md 12 | cla/** 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bluemix Dev Extension for VS Code 2 | 3 | We welcome contributions, and request you follow these guidelines. 4 | 5 | - [Raising issues](#raising-issues) 6 | - [Contributor License Agreement](#contributor-license-agreement) 7 | - [Coding Standards](#coding-standards) 8 | 9 | 10 | ## Raising issues 11 | 12 | Please raise any bug reports on the [issue tracker](https://github.com/IBM-Bluemix/ibm-developer-extension-vscode/issues). Be sure to 13 | search the list to see if your issue has already been raised. 14 | 15 | A good bug report is one that make it easy for us to understand what you were 16 | trying to do and what went wrong. Provide as much context as possible so we can try to recreate the issue. 17 | 18 | ### Contributor License Agreement 19 | 20 | In order for us to accept pull-requests, the contributor must first complete 21 | a Contributor License Agreement (CLA). Please see our [CLA folder](./cla) for more information. 22 | 23 | This clarifies the intellectual property license granted with any contribution. It is for your protection as a 24 | Contributor as well as the protection of IBM and its customers; it does not 25 | change your rights to use your own Contributions for any other purpose. 26 | 27 | ### Coding Conventions 28 | 29 | This project follows standard TypeScript language [coding conventions](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines) 30 | 31 | Please note: 32 | 33 | - all files must have the Apache license in the header. 34 | - all PRs must pass TypeScript linting checks 35 | - all PRs must have passing builds -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## Dev Environment Configuration 2 | 3 | * Use [VS Code](https://code.visualstudio.com/) to develop VS Code extensions. 4 | * Make sure you have a IBM Cloud CLI generated application. 5 | * Once you've copied the project source code locally, be sure to run `npm install` from a terminal to download Node.js dependencies and configure your dev environment (auto configures pre-commit hooks). 6 | * Add launch commands in `.vscode/launch.json` for extension launch and testing: 7 | (You can also do this from the debug sidebar's gear icon, or select "Add Configuration" from the debug target drop down menu.) 8 | 9 | ``` 10 | // A launch configuration that compiles the extension and then opens it inside a new window 11 | { 12 | "version": "0.2.0", 13 | "configurations": [ 14 | { 15 | "name": "Launch Extension", 16 | "type": "extensionHost", 17 | "request": "launch", 18 | "runtimeExecutable": "${execPath}", 19 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 20 | "stopOnEntry": false, 21 | "sourceMaps": true, 22 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 23 | "preLaunchTask": "npm: compile" 24 | }, 25 | { 26 | "name": "Launch Tests", 27 | "type": "extensionHost", 28 | "request": "launch", 29 | "runtimeExecutable": "${execPath}", 30 | "args": ["/path/to/generated-starterkit/", --extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 31 | "stopOnEntry": false, 32 | "sourceMaps": true, 33 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 34 | "preLaunchTask": "npm: compile" 35 | } 36 | ] 37 | } 38 | ``` 39 | * Install `vsce` (to be able to package `.vsix` builds) 40 | ``` 41 | npm install -g vsce 42 | ``` 43 | * Install these VS Code extensions: 44 | * [TSLint Extension for VSCode](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) 45 | 46 | 47 | 48 | ## Packaging a build 49 | 50 | To distribute a build, you have to use the [`vsce` tool from Microsoft](https://code.visualstudio.com/docs/extensions/publish-extension). You can package a build locally to produce a `.vsix` file using `vsce package`. 51 | 52 | You can *only* submit to the VS Code marketplace using the `vsce publish` command. 53 | 54 | 55 | ## Installing a local/test build (`.vsix` file): 56 | 57 | 1. Package or Download a release from https://github.com/IBM-Bluemix/ibm-developer-extension-vscode/releases 58 | 1. From within the VS Code editor, open the extensions browser. 59 | 1. Click on the "`...`" menu. 60 | 1. Select the `Install from VSIX` option, and select the .vsix file that you downloaded. 61 | 1. You're all set! 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/IBM-Cloud/ibm-developer-extension-vscode.svg?branch=master)](https://travis-ci.org/IBM-Cloud/ibm-developer-extension-vscode) 2 | [![License](https://img.shields.io/badge/license-Apache%202.0-green.svg?style=flat)](https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/master/LICENSE.txt) 3 | [![Version](https://img.shields.io/visual-studio-marketplace/v/IBM.ibm-developer)](https://marketplace.visualstudio.com/items?itemName=IBM.ibm-developer) 4 | [![Installs](https://img.shields.io/visual-studio-marketplace/i/IBM.ibm-developer)](https://marketplace.visualstudio.com/items?itemName=IBM.ibm-developer) 5 | [![Ratings](https://img.shields.io/visual-studio-marketplace/r/IBM.ibm-developer)](https://marketplace.visualstudio.com/items?itemName=IBM.ibm-developer) 6 | 7 | # IBM Cloud CLI Extension for VS Code 8 | 9 | This extension provides capabilities for the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli/index.html) from directly within the VS Code editor. Use the VS Code command palette to quickly access `ibmcloud` commands, without the need to leave the editor's context. 10 | 11 | ## Changelog 12 | - v1.0.1 13 | - Remove references to dev commands in README 14 | - v1.0.0 15 | - BREAKING CHANGE: Removed dev commands (list, build, deploy, debug, diag, shell, status, run, stop, console, view, test) 16 | - v0.3.0 17 | - Removed deprecated cf commands 18 | - v0.2.0 19 | - Added plugin commands (install, uninstall, update) 20 | - Added iam commands (oauth-tokens, service-id, service-ids) 21 | - Added additional resource commands (service-binding, service-bindings, service-alias, service-aliases) 22 | - Fixed autodetect missing cli/plugin binaries 23 | - Added user option to install missing plugin and rerun previous failed command 24 | - v0.1.0 25 | - Added basic account commands (list, show, users) 26 | - Added view api endpoint command 27 | - Added list regions command 28 | - Added view target command 29 | - Added view service instances command 30 | - Improved UX for deploy command 31 | - BREAKING CHANGE: `deploy` command only supports IBM Cloud Kubernetes Service 32 | - Removed old YouTube tutorials from README 33 | - Improved development flow instructions in README 34 | - Removed CloudFoundry workflow from README 35 | - v0.0.16 36 | - Improved performance when displaying CLI logs in Output Channel 37 | - Used correct command ext identifier when calling cf logs commands 38 | - *v0.0.15* 39 | - Updated to IBM Cloud 2.6.0 40 | - Rebranded extension to IBM Cloud CLI 41 | - Fixed user input box not showing after first usage 42 | - *v0.0.14* 43 | - Added `bx dev console` command 44 | - Added check for hostname and domain in cli-config.yml for CF deployment (no longer required when project is created) 45 | - *v0.0.13* 46 | - Updated IBM Cloud icon 47 | - *v0.0.12* 48 | - Added support for `bx dev shell` command 49 | - Removed support for the `sdkgen` cli 50 | - Improved cli version detection with minimum versioning and forced upgrade paths to support new cli features 51 | - Added support for "caller" arguments for the `dev` cli 52 | - *v0.0.11* 53 | - Added support for Kubernetes/Helm deployment 54 | - Added support for `bx dev console` 55 | - *v0.0.10* 56 | - Updated badges in README for VS Code marketplace compliance. 57 | - Fixed "killed terminal" bug in login/logout commands 58 | - *v0.0.9* - Updated usage/getting started in README 59 | - *v0.0.8* - first public release 60 | 61 | ## Usage 62 | 63 | Easily invoke commands from the IBM Cloud CLI from directly inside of the VS Code editor: 64 | 65 | - Open the VS Code command palette (`F1` or `CMD-Shift-P`) 66 | - Use the `ibmcloud login` command to log in to IBM Cloud (using your IBM Cloud credentials) 67 | 68 | ### Steps to get started: 69 | 70 | - Create a project or open an existing project. Examples on how to create a project are listed below: 71 | - [Create and deploy a Node.js Express app by using IBM Cloud Schematics](https://cloud.ibm.com/docs/apps?topic=apps-tutorial-node-webapp) 72 | - [Create and deploy a Java Spring app by using IBM Cloud Schematics](https://cloud.ibm.com/docs/apps?topic=apps-tutorial-spring-webapp) 73 | - [Create and deploy a Java Liberty app by using IBM Cloud Schematics](https://cloud.ibm.com/docs/apps?topic=apps-tutorial-liberty-webapp) 74 | - [Create and deploy an app using Code Engine](https://cloud.ibm.com/docs/apps?topic=apps-tutorial-cd-code-engine) 75 | - [Create and deploy a secure app with Devops](https://cloud.ibm.com/docs/apps?topic=apps-tutorial-cd-devsecops) 76 | - [Develop a Kubernetes app with Helm](https://www.ibm.com/cloud/architecture/tutorials/use-develop-kubernetes-app-with-helm-toolchain) 77 | - Open the *project’s folder* in VS Code 78 | - Press Ctrl-K+Ctrl-O or navigate to File -> Open Folder to select folder 79 | 80 | ### Supported CLI/plugins: 81 | 82 | - `ibmcloud login/logout` - IBM Cloud User Authentication 83 | - `ibmcloud ks` - IBM Cloud Kubernetes Service CLI 84 | - `ibmcloud api` - View IBM Cloud API endpoint 85 | - `ibmcloud regions` - View IBM Cloud regions 86 | - `ibmcloud account` - View IBM Cloud accounts and users 87 | - `ibmcloud resource` - View IBM Cloud Service Instances, Service Bindings, and Service Aliases 88 | - `ibmcloud target` - View targeted IBM Cloud org, space, account, and resource group 89 | - `ibmcloud plugin` - Install, uninstall, and update IBM Cloud CLI plugins 90 | - `ibmcloud iam` - Display Oauth tokens and IBM Cloud Service IDs 91 | 92 | ## Requirements/Dependencies 93 | 94 | * [IBM Cloud CLI](https://cloud.ibm.com/docs/cli/index.html) 95 | * [Docker](https://www.docker.com/) - required by `ibmcloud dev` containers 96 | 97 | ## Contributing 98 | 99 | All improvements to the IBM Cloud CLI Extension for VS Code are very welcome! Here's how to get started ... 100 | 101 | Fork this repository. 102 | $ git clone https://github.com/IBM-Cloud/ibm-developer-extension-vscode.git 103 | 104 | Start making your changes, then send us a pull request. 105 | 106 | You can find more info on contributing in our [contributing guidelines](./CONTRIBUTING.md). 107 | 108 | You can find more info about the development environment and configuration in our [development guidelines](./DEVELOPMENT.md) 109 | 110 | ## ⚠️ Bugs / Issues / Defects 111 | 112 | Find a bug? [Let us know here](https://github.com/IBM-Cloud/ibm-developer-extension-vscode/issues) 113 | 114 | ### ![Slack](assets/slack.png) Connect on Slack 115 | Please provide your experience, questions and feedback in the #ask-your-question Slack channel. Apply for access or login here: http://ibm.biz/cli-feedback 116 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/7952a31ddcc503022015058901349e14b6b3bff3/assets/icon.png -------------------------------------------------------------------------------- /assets/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/7952a31ddcc503022015058901349e14b6b3bff3/assets/slack.png -------------------------------------------------------------------------------- /assets/stack_overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/7952a31ddcc503022015058901349e14b6b3bff3/assets/stack_overflow.png -------------------------------------------------------------------------------- /cla/README.md: -------------------------------------------------------------------------------- 1 | Contributor License Agreement (CLA) 2 | 3 | To contribute to the Bluemix Dev Extension for VS Code project, it is required to follow these steps: 4 | 5 | * Print out the [individual CLA form](./cla-individual.pdf) if you're contributing as an individual, or [corporate CLA form](./cla-corporate.pdf) if you're contributing as part of your job. 6 | * Scan or take a photo of the form 7 | * Email to the address listed in the CLA form 8 | 9 | You are only required to do this once, and then you are free to contribute to the Bluemix Dev Extension for VS Code project. -------------------------------------------------------------------------------- /cla/cla-corporate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/7952a31ddcc503022015058901349e14b6b3bff3/cla/cla-corporate.pdf -------------------------------------------------------------------------------- /cla/cla-individual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/ibm-developer-extension-vscode/7952a31ddcc503022015058901349e14b6b3bff3/cla/cla-individual.pdf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibm-developer", 3 | "displayName": "IBM Cloud CLI", 4 | "description": "Extension for VS Code editor to enable IBM Cloud CLI capabilities from within the editing environment.", 5 | "version": "1.0.1", 6 | "publisher": "IBM", 7 | "license": "Apache 2.0 (see LICENSE.txt)", 8 | "bugs": { 9 | "url": "https://github.com/IBM-Cloud/ibm-developer-extension-vscode/issues" 10 | }, 11 | "homepage": "https://github.com/IBM-Cloud/ibm-developer-extension-vscode/blob/master/README.md", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/IBM-Cloud/ibm-developer-extension-vscode" 15 | }, 16 | "engineStrict": true, 17 | "engines": { 18 | "vscode": "^1.64.0", 19 | "node": ">=14.0.1 <14.6.0" 20 | }, 21 | "categories": [ 22 | "Other" 23 | ], 24 | "keywords": [ 25 | "IBM", 26 | "Development", 27 | "Tools", 28 | "CLI", 29 | "Bluemix", 30 | "Cloud" 31 | ], 32 | "icon": "assets/icon.png", 33 | "activationEvents": [ 34 | "onCommand:extension.ibmcloud.api", 35 | "onCommand:extension.ibmcloud.regions", 36 | "onCommand:extension.ibmcloud.target", 37 | "onCommand:extension.ibmcloud.login", 38 | "onCommand:extension.ibmcloud.login.sso", 39 | "onCommand:extension.ibmcloud.logout", 40 | "onCommand:extension.ibmcloud.cli-update", 41 | "onCommand:extension.ibmcloud.cli-install", 42 | "onCommand:extension.ibmcloud.ks.cluster.get", 43 | "onCommand:extension.ibmcloud.ks.cluster.rm", 44 | "onCommand:extension.ibmcloud.ks.clusters", 45 | "onCommand:extension.ibmcloud.ks.init", 46 | "onCommand:extension.ibmcloud.ks.worker.add", 47 | "onCommand:extension.ibmcloud.ks.worker.get", 48 | "onCommand:extension.ibmcloud.ks.worker.reboot", 49 | "onCommand:extension.ibmcloud.ks.worker.reload", 50 | "onCommand:extension.ibmcloud.ks.worker.rm", 51 | "onCommand:extension.ibmcloud.ks.workers", 52 | "onCommand:extension.ibmcloud.account.list", 53 | "onCommand:extension.ibmcloud.account.show", 54 | "onCommand:extension.ibmcloud.account.users", 55 | "onCommand:extension.ibmcloud.resource.service-instances", 56 | "onCommand:extension.ibmcloud.plugin.install", 57 | "onCommand:extension.ibmcloud.plugin.uninstall", 58 | "onCommand:extension.ibmcloud.plugin.update", 59 | "onCommand:extension.ibmcloud.iam.oauth-tokens", 60 | "onCommand:extension.ibmcloud.iam.service-id", 61 | "onCommand:extension.ibmcloud.iam.service-ids", 62 | "onCommand:extension.ibmcloud.resource.service-binding.get", 63 | "onCommand:extension.ibmcloud.resource.service-binding.list", 64 | "onCommand:extension.ibmcloud.resource.service-alias.get", 65 | "onCommand:extension.ibmcloud.resource.service-alias.list" 66 | ], 67 | "main": "./out/src/extension", 68 | "contributes": { 69 | "commands": [ 70 | { 71 | "command": "extension.ibmcloud.api", 72 | "title": "ibmcloud api" 73 | }, 74 | { 75 | "command": "extension.ibmcloud.regions", 76 | "title": "ibmcloud regions" 77 | }, 78 | { 79 | "command": "extension.ibmcloud.target", 80 | "title": "ibmcloud target" 81 | }, 82 | { 83 | "command": "extension.ibmcloud.login", 84 | "title": "ibmcloud login" 85 | }, 86 | { 87 | "command": "extension.ibmcloud.login.sso", 88 | "title": "ibmcloud login --sso" 89 | }, 90 | { 91 | "command": "extension.ibmcloud.logout", 92 | "title": "ibmcloud logout" 93 | }, 94 | { 95 | "command": "extension.ibmcloud.cli-update", 96 | "title": "ibmcloud plugin update --all -r 'IBM Cloud' (update cli extensions)" 97 | }, 98 | { 99 | "command": "extension.ibmcloud.cli-install", 100 | "title": "ibmcloud plugin install --all -r 'IBM Cloud' -f (install cli extensions)" 101 | }, 102 | { 103 | "command": "extension.ibmcloud.ks.cluster.get", 104 | "title": "ibmcloud ks cluster get" 105 | }, 106 | { 107 | "command": "extension.ibmcloud.ks.cluster.rm", 108 | "title": "ibmcloud ks cluster rm" 109 | }, 110 | { 111 | "command": "extension.ibmcloud.ks.clusters", 112 | "title": "ibmcloud ks clusters" 113 | }, 114 | { 115 | "command": "extension.ibmcloud.ks.init", 116 | "title": "ibmcloud ks init" 117 | }, 118 | { 119 | "command": "extension.ibmcloud.ks.worker.add", 120 | "title": "ibmcloud ks worker add" 121 | }, 122 | { 123 | "command": "extension.ibmcloud.ks.worker.get", 124 | "title": "ibmcloud ks worker get" 125 | }, 126 | { 127 | "command": "extension.ibmcloud.ks.worker.reboot", 128 | "title": "ibmcloud ks worker reboot" 129 | }, 130 | { 131 | "command": "extension.ibmcloud.ks.worker.reload", 132 | "title": "ibmcloud ks worker reload" 133 | }, 134 | { 135 | "command": "extension.ibmcloud.ks.worker.rm", 136 | "title": "ibmcloud ks worker rm" 137 | }, 138 | { 139 | "command": "extension.ibmcloud.ks.workers", 140 | "title": "ibmcloud ks workers" 141 | }, 142 | { 143 | "command": "extension.ibmcloud.account.list", 144 | "title": "ibmcloud account list" 145 | }, 146 | { 147 | "command": "extension.ibmcloud.account.show", 148 | "title": "ibmcloud account show" 149 | }, 150 | { 151 | "command": "extension.ibmcloud.account.users", 152 | "title": "ibmcloud account users" 153 | }, 154 | { 155 | "command": "extension.ibmcloud.resource.service-instances", 156 | "title": "ibmcloud resource service-instances" 157 | }, 158 | { 159 | "command": "extension.ibmcloud.plugin.install", 160 | "title": "ibmcloud plugin install" 161 | }, 162 | { 163 | "command": "extension.ibmcloud.plugin.uninstall", 164 | "title": "ibmcloud plugin uninstall" 165 | }, 166 | { 167 | "command": "extension.ibmcloud.plugin.update", 168 | "title": "ibmcloud plugin update" 169 | }, 170 | { 171 | "command": "extension.ibmcloud.iam.oauth-tokens", 172 | "title": "ibmcloud iam oauth-tokens" 173 | }, 174 | { 175 | "command": "extension.ibmcloud.iam.service-id", 176 | "title": "ibmcloud iam service-id" 177 | }, 178 | { 179 | "command": "extension.ibmcloud.iam.service-ids", 180 | "title": "ibmcloud iam service-ids" 181 | }, 182 | { 183 | "command": "extension.ibmcloud.resource.service-binding.get", 184 | "title": "ibmcloud resource service-binding" 185 | }, 186 | { 187 | "command": "extension.ibmcloud.resource.service-binding.list", 188 | "title": "ibmcloud resource service-bindings" 189 | }, 190 | { 191 | "command": "extension.ibmcloud.resource.service-alias.get", 192 | "title": "ibmcloud resource service-alias" 193 | }, 194 | { 195 | "command": "extension.ibmcloud.resource.service-alias.list", 196 | "title": "ibmcloud resource service-aliases" 197 | } 198 | ] 199 | }, 200 | "scripts": { 201 | "vscode:prepublish": "npm run build", 202 | "build:dev": "npm run clean && webpack --mode development", 203 | "build": "npm run clean && webpack --mode production --devtool hidden-source-map", 204 | "clean": "rm -rf out", 205 | "compile": "tsc -p ./", 206 | "watch": "webpack --mode development --watch", 207 | "pretest": "npm run compile", 208 | "test": "node ./out/test/runTest.js", 209 | "lint": "eslint . --ext .ts,.tsx", 210 | "lint:fix": "eslint . --ext .ts,.tsx --fix", 211 | "detect_secrets": "./scripts/detect_secrets", 212 | "vscode:publish": "./scripts/publish" 213 | }, 214 | "devDependencies": { 215 | "@types/chai": "^4.3.1", 216 | "@types/mocha": "^10.0.1", 217 | "@types/node": "^14.0.1", 218 | "@types/semver": "^7.3.9", 219 | "@types/sinon": "^10.0.11", 220 | "@types/vscode": "^1.64.0", 221 | "@typescript-eslint/eslint-plugin": "^5.19.0", 222 | "@typescript-eslint/parser": "^5.19.0", 223 | "@vscode/test-electron": "^2.1.3", 224 | "chai": "^4.3.6", 225 | "eslint": "^8.13.0", 226 | "mocha": "^10.1.0", 227 | "pre-commit": "^1.2.2", 228 | "sinon": "^13.0.1", 229 | "ts-loader": "^9.2.8", 230 | "typescript": "^4.5.5", 231 | "webpack": "^5.76.0", 232 | "webpack-cli": "^4.9.2" 233 | }, 234 | "dependencies": { 235 | "js-yaml": "^3.13.1", 236 | "ps-tree": "^1.1.0", 237 | "semver": "^7.5.2", 238 | "stream-browserify": "^3.0.0" 239 | }, 240 | "pre-commit": [ 241 | "lint" 242 | ], 243 | "ibm": { 244 | "cli": { 245 | "command": "ibmcloud", 246 | "version": "2.18.0", 247 | "url": "https://cloud.ibm.com/docs/cli" 248 | }, 249 | "plugins": [ 250 | { 251 | "displayName": "container-service[kubernetes-service]", 252 | "command": "ks", 253 | "version": "1.0.353", 254 | "url": "https://cloud.ibm.com/docs/containers?topic=containers-kubernetes-service-cli" 255 | } 256 | ] 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /scripts/detect_secrets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import subprocess 3 | import json 4 | 5 | print(subprocess.run(['detect-secrets', 'scan', '--update', '.secrets.baseline'])) 6 | 7 | found_secrets = [] 8 | 9 | with open('.secrets.baseline', 'r') as f: 10 | baseline = json.loads(f.read()) 11 | for file, secrets in baseline['results'].items(): 12 | for secret in secrets: 13 | if secret.get('is_secret', True): 14 | found_secrets.append((file, secret)) 15 | 16 | if found_secrets: 17 | print('Secrets were found in the source code!') 18 | print('If these contain false positives, they can be marked as such with the `detect-secrets audit .secrets.baseline` command and committing the updated baseline file into the application repo.') 19 | print('Read more about the tool at https://github.com/ibm/detect-secrets\n\n') 20 | print('FOUND SECRETS:') 21 | for secret in found_secrets: 22 | print('File: ' + secret[0] + ' Line: ' + str(secret[1]['line_number']) + ' Type: ' + secret[1]['type']) 23 | print('failure') 24 | exit(1) 25 | else: 26 | print('NO SECRETS FOUND') 27 | print('success') 28 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Install vsce if not in PATH 6 | if [[ -z $(command -v vsce) ]]; then 7 | npm install -g vsce 8 | fi 9 | 10 | vsce publish 11 | -------------------------------------------------------------------------------- /src/commands/iam/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { ServiceIdCommand } from './serviceId'; 20 | 21 | export { ServiceIdCommand }; 22 | -------------------------------------------------------------------------------- /src/commands/iam/serviceId.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { getServiceIds, ServiceId } from "../../ibmcloud/iam"; 20 | import { PromptingCommand } from "../../util/PromptingCommand"; 21 | 22 | export class ServiceIdCommand extends PromptingCommand { 23 | 24 | async execute(): Promise { 25 | 26 | try { 27 | const serviceIds = await getServiceIds(); 28 | this.inputs[0].pickerOptions = serviceIds.map((serviceId: ServiceId) => serviceId.name); 29 | } catch (e) { 30 | console.error('Could not provide picker options for service id list'); 31 | console.error(e); 32 | } 33 | 34 | return super.execute(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/plugin/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { PluginInstallCommand } from './install'; 20 | import { PluginUpdateUninstallCommand } from './updateUninstall'; 21 | 22 | export { PluginInstallCommand, PluginUpdateUninstallCommand }; 23 | -------------------------------------------------------------------------------- /src/commands/plugin/install.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { PromptingCommand, PromptInput } from '../../util/PromptingCommand'; 20 | import { getInstalledPlugins, getRepoPlugins } from '../../ibmcloud/plugin'; 21 | 22 | export class PluginInstallCommand extends PromptingCommand { 23 | 24 | async execute(): Promise { 25 | 26 | this.inputs = [ 27 | new PromptInput('Specify a plugin to install') 28 | ]; 29 | 30 | try { 31 | const installedPlugins = await getInstalledPlugins(); 32 | const allPlugins = await getRepoPlugins(); 33 | 34 | // Provide only the plugins that have NOT been installed to user to select from 35 | this.inputs[0].pickerOptions = allPlugins.filter((name: string) => installedPlugins.indexOf(name) == -1); 36 | } catch (e) { 37 | console.error('Could not provide picker options for plugin list'); 38 | console.error(e); 39 | } 40 | 41 | return super.execute(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/plugin/updateUninstall.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { window } from 'vscode'; 20 | import { PromptingCommand } from '../../util/PromptingCommand'; 21 | import { getInstalledPlugins } from '../../ibmcloud/plugin'; 22 | 23 | export class PluginUpdateUninstallCommand extends PromptingCommand { 24 | 25 | async execute(): Promise { 26 | 27 | try { 28 | const installedPlugins = await getInstalledPlugins(); 29 | if (installedPlugins.length === 0) { 30 | this.displayWarning('No plugins are installed. Please install a plugin before trying again.'); 31 | return; 32 | } 33 | 34 | this.inputs[0].pickerOptions = installedPlugins; 35 | } catch (e) { 36 | console.error('Could not provide picker options for plugin list'); 37 | console.error(e); 38 | } 39 | 40 | return super.execute(); 41 | } 42 | 43 | /* 44 | * display warning message 45 | */ 46 | displayWarning(message: string) { 47 | window.showWarningMessage(message); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/resource/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { ServiceAliasCommand } from "./serviceAlias"; 20 | 21 | export { ServiceAliasCommand }; 22 | -------------------------------------------------------------------------------- /src/commands/resource/serviceAlias.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { window } from 'vscode'; 20 | import { getServiceAliases, Resource } from '../../ibmcloud/resource'; 21 | import { isCF, targetCF } from '../../ibmcloud/target'; 22 | import { PromptingCommand } from '../../util/PromptingCommand'; 23 | 24 | export class ServiceAliasCommand extends PromptingCommand { 25 | 26 | async execute(): Promise { 27 | try { 28 | if (!await isCF()) { 29 | await targetCF(); 30 | } 31 | 32 | const serviceAliases = await getServiceAliases(); 33 | if (serviceAliases.length === 0) { 34 | this.displayWarning('No service alias could be found. Please create a service alias and try again'); 35 | return; 36 | } 37 | 38 | // NOTE: Currently we only provide picker options for service-aliases 39 | // since we can't get a consumable format from CLI to get cf apps 40 | // so user must enter the app name manually 41 | this.inputs[0].pickerOptions = serviceAliases.map((serviceAlias: Resource) => serviceAlias.name); 42 | 43 | } catch (e) { 44 | console.error('Could not picker options for service aliases list'); 45 | console.error(e); 46 | } 47 | 48 | return super.execute(); 49 | } 50 | 51 | /* 52 | * display warning message 53 | */ 54 | displayWarning(message: string) { 55 | window.showWarningMessage(message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | export const IBMCloud = 'IBM Cloud'; 20 | export const CONFIRM_YES = 'Yes'; 21 | export const CONFIRM_NO = 'No'; 22 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { commands, window, ExtensionContext } from 'vscode'; 20 | import { LoginManager } from './util/LoginManager'; 21 | import { PromptingCommand, PromptInput } from './util/PromptingCommand'; 22 | import { SystemCommand } from './util/SystemCommand'; 23 | import * as semver from 'semver'; 24 | import * as packageJson from '../package.json'; 25 | import { PluginInstallCommand, PluginUpdateUninstallCommand } from './commands/plugin'; 26 | import { ServiceIdCommand } from './commands/iam'; 27 | import { ServiceAliasCommand } from './commands/resource'; 28 | 29 | 30 | const outputChannel = window.createOutputChannel('IBMCloud'); 31 | let checkedVersions = false; 32 | 33 | /* 34 | * activate method is called when your extension is activated 35 | * your extension is activated the very first time the command is executed 36 | */ 37 | export function activate(context: ExtensionContext) { 38 | console.log('Congratulations, your extension "com-ibm-cloud" is now active!'); 39 | 40 | checkVersions(); 41 | 42 | LoginManager.registerCommand(context, 'extension.ibmcloud.login'); 43 | LoginManager.registerCommand(context, 'extension.ibmcloud.login.sso'); 44 | registerCommand(context, 'extension.ibmcloud.logout', { cmd: 'ibmcloud', args: ['logout'] }, outputChannel); 45 | registerCommand(context, 'extension.ibmcloud.cli-update', { cmd: 'ibmcloud', args: ['plugin', 'update', '--all', '-r', 'IBM Cloud'] }, outputChannel); 46 | registerCommand(context, 'extension.ibmcloud.cli-install', { cmd: 'ibmcloud', args: ['plugin', 'install', '--all', '-r', 'IBM Cloud', '-f'] }, outputChannel); 47 | registerCommand(context, 'extension.ibmcloud.api', { cmd: 'ibmcloud', args: ['api'] }, outputChannel); 48 | registerCommand(context, 'extension.ibmcloud.regions', { cmd: 'ibmcloud', args: ['regions'] }, outputChannel); 49 | registerCommand(context, 'extension.ibmcloud.target', { cmd: 'ibmcloud', args: ['target'] }, outputChannel); 50 | 51 | // IBM Cloud DEV commands ************************************* 52 | registerCommand(context, 'extension.ibmcloud.dev.list', { cmd: 'ibmcloud', args: ['dev', 'list', '--caller-vscode'] }, outputChannel, true); 53 | registerCommand(context, 'extension.ibmcloud.dev.build', { cmd: 'ibmcloud', args: ['dev', 'build', '--caller-vscode', '--debug'] }, outputChannel, false); 54 | registerCommand(context, 'extension.ibmcloud.dev.build.release', { cmd: 'ibmcloud', args: ['dev', 'build', '--caller-vscode'] }, outputChannel, false); 55 | registerCommand(context, 'extension.ibmcloud.dev.debug', { cmd: 'ibmcloud', args: ['dev', 'debug', '--caller-vscode'] }, outputChannel); 56 | registerCommand(context, 'extension.ibmcloud.dev.deploy', { cmd: 'ibmcloud', args: ['dev', 'deploy', '--target', 'container', '--caller-vscode'] }, outputChannel, false, SystemCommand, true); 57 | registerCommand(context, 'extension.ibmcloud.dev.diag', { cmd: 'ibmcloud', args: ['dev', 'diag', '--caller-vscode'] }, outputChannel, true); 58 | registerCommand(context, 'extension.ibmcloud.dev.run', { cmd: 'ibmcloud', args: ['dev', 'run', '--caller-vscode'] }, outputChannel); 59 | registerCommand(context, 'extension.ibmcloud.dev.status', { cmd: 'ibmcloud', args: ['dev', 'status', '--caller-vscode'] }, outputChannel); 60 | registerCommand(context, 'extension.ibmcloud.dev.stop', { cmd: 'ibmcloud', args: ['dev', 'stop', '--caller-vscode'] }, outputChannel); 61 | registerCommand(context, 'extension.ibmcloud.dev.test', { cmd: 'ibmcloud', args: ['dev', 'test', '--caller-vscode'] }, outputChannel); 62 | registerCommand(context, 'extension.ibmcloud.dev.console', { cmd: 'ibmcloud', args: ['dev', 'console', '--caller-vscode'] }, outputChannel); 63 | registerCommand(context, 'extension.ibmcloud.dev.view', { cmd: 'ibmcloud', args: ['dev', 'view', '--caller-vscode'] }, outputChannel, true, SystemCommand, true); 64 | registerPromptingCommand(context, 'extension.ibmcloud.dev.console.app', { cmd: 'ibmcloud', args: ['dev', 'console', '--caller-vscode'] }, outputChannel, [new PromptInput('Specify a project name')]); 65 | registerCommand(context, 'extension.ibmcloud.dev.shell', { cmd: 'ibmcloud', args: ['dev', 'shell', '--caller-vscode'] }, outputChannel, false, SystemCommand, true); 66 | registerCommand(context, 'extension.ibmcloud.dev.shell.run', { cmd: 'ibmcloud', args: ['dev', 'shell', 'run', '--caller-vscode'] }, outputChannel, false, SystemCommand, true); 67 | registerCommand(context, 'extension.ibmcloud.dev.shell.tools', { cmd: 'ibmcloud', args: ['dev', 'shell', 'tools', '--caller-vscode'] }, outputChannel, false, SystemCommand, true); 68 | 69 | // IBM Cloud CS commands ************************************* 70 | registerPromptingCommand(context, 'extension.ibmcloud.ks.cluster.get', { cmd: 'ibmcloud', args: ['ks', 'cluster', 'get'] }, outputChannel, [new PromptInput('Specify a cluster name or id', '--cluster')]); 71 | registerPromptingCommand(context, 'extension.ibmcloud.ks.cluster.rm', { cmd: 'ibmcloud', args: ['ks', 'cluster', 'rm'] }, outputChannel, [new PromptInput('Specify a cluster name or id')], ['-f']); 72 | registerCommand(context, 'extension.ibmcloud.ks.clusters', { cmd: 'ibmcloud', args: ['ks', 'clusters'] }, outputChannel); 73 | registerCommand(context, 'extension.ibmcloud.ks.init', { cmd: 'ibmcloud', args: ['ks', 'init'] }, outputChannel); 74 | registerPromptingCommand(context, 'extension.ibmcloud.ks.worker.get', { cmd: 'ibmcloud', args: ['ks', 'worker', 'get'] }, outputChannel, [new PromptInput('Specify cluster name or id', '--cluster'), new PromptInput('Specify worker id', '--worker')]); 75 | registerPromptingCommand(context, 'extension.ibmcloud.ks.worker.add', { cmd: 'ibmcloud', args: ['ks', 'worker', 'add'] }, outputChannel, [new PromptInput('Specify cluster name or id', '--cluster')], ['1']); 76 | registerPromptingCommand(context, 'extension.ibmcloud.ks.worker.reboot', { cmd: 'ibmcloud', args: ['ks', 'worker', 'reboot'] }, outputChannel, [new PromptInput('Specify a cluster name or id', '--cluster'), new PromptInput('Specify a worker id', '--worker')], ['-f']); 77 | registerPromptingCommand(context, 'extension.ibmcloud.ks.worker.reload', { cmd: 'ibmcloud', args: ['ks', 'worker', 'reload'] }, outputChannel, [new PromptInput('Specify a cluster name or id', '--cluster'), new PromptInput('Specify a worker id', '--worker')], ['-f']); 78 | registerPromptingCommand(context, 'extension.ibmcloud.ks.worker.rm', { cmd: 'ibmcloud', args: ['ks', 'worker', 'rm'] }, outputChannel, [new PromptInput('Specify a cluster name or id', '--cluster'), new PromptInput('Specify a worker id')], ['-f']); 79 | registerPromptingCommand(context, 'extension.ibmcloud.ks.workers', { cmd: 'ibmcloud', args: ['ks', 'workers'] }, outputChannel, [new PromptInput('Specify a cluster name or id', '--cluster')]); 80 | 81 | // IBM Cloud ACCOUNT commands ************************************* 82 | registerCommand(context, 'extension.ibmcloud.account.list', { cmd: 'ibmcloud', args: ['account', 'list'] }, outputChannel, true); 83 | registerCommand(context, 'extension.ibmcloud.account.show', { cmd: 'ibmcloud', args: ['account', 'show'] }, outputChannel, true); 84 | registerCommand(context, 'extension.ibmcloud.account.users', { cmd: 'ibmcloud', args: ['account', 'users'] }, outputChannel, true); 85 | 86 | // IBM Cloud RESOURCE commands ************************************* 87 | registerCommand(context, 'extension.ibmcloud.resource.service-instances', { cmd: 'ibmcloud', args: ['resource', 'service-instances'] }, outputChannel); 88 | registerPromptingCommand(context, 'extension.ibmcloud.resource.service-binding.list', { cmd: 'ibmcloud', args: ['resource', 'service-bindings'] }, outputChannel, [new PromptInput('Specify service alias')], [], false, ServiceAliasCommand); 89 | registerPromptingCommand(context, 'extension.ibmcloud.resource.service-binding.get', { cmd: 'ibmcloud', args: ['resource', 'service-binding'] }, outputChannel, [new PromptInput('Specify service alias'), new PromptInput('Specify Cloud Foundry app name')], [], false, ServiceAliasCommand); 90 | registerCommand(context, 'extension.ibmcloud.resource.service-alias.list', { cmd: 'ibmcloud', args: ['resource', 'service-aliases'] }, outputChannel); 91 | registerPromptingCommand(context, 'extension.ibmcloud.resource.service-alias.get', { cmd: 'ibmcloud', args: ['resource', 'service-alias'] }, outputChannel, [new PromptInput('Specify service alias')], [], false, ServiceAliasCommand); 92 | 93 | // IBM Cloud IAM commands ************************************* 94 | registerCommand(context, 'extension.ibmcloud.iam.oauth-tokens', { cmd: 'ibmcloud', args: ['iam', 'oauth-tokens'] }, outputChannel); 95 | registerCommand(context, 'extension.ibmcloud.iam.service-ids', { cmd: 'ibmcloud', args: ['iam', 'service-ids'] }, outputChannel); 96 | registerPromptingCommand(context, 'extension.ibmcloud.iam.service-id', { cmd: 'ibmcloud', args: ['iam', 'service-id'] }, outputChannel, [new PromptInput('Specify a service ID')], [], false, ServiceIdCommand); 97 | 98 | 99 | // IBM Cloud PLUGIN commands ************************************* 100 | registerPromptingCommand(context, 'extension.ibmcloud.plugin.install', { cmd: 'ibmcloud', args: ['plugin', 'install'] }, outputChannel, [], [], false, PluginInstallCommand); 101 | registerPromptingCommand(context, 'extension.ibmcloud.plugin.update', { cmd: 'ibmcloud', args: ['plugin', 'update'] }, outputChannel, [new PromptInput('Specify a plugin to update')], [], false, PluginUpdateUninstallCommand); 102 | registerPromptingCommand(context, 'extension.ibmcloud.plugin.uninstall', { cmd: 'ibmcloud', args: ['plugin', 'uninstall'] }, outputChannel, [new PromptInput('Specify a plugin to uninstall')], [], false, PluginUpdateUninstallCommand); 103 | } 104 | 105 | 106 | /* 107 | * Helper utility to register system commands 108 | */ 109 | function registerCommand(context: ExtensionContext, key: string, opt, outputChannel, sanitizeOutput = false, CommandClass = SystemCommand, useTerminal = false) { 110 | const disposable = commands.registerCommand(key, () => { 111 | const command = new CommandClass(opt.cmd, opt.args, outputChannel, sanitizeOutput); 112 | command.useTerminal = useTerminal; 113 | return new Promise((resolve) => { 114 | resolve(executeCommand(command)); 115 | }); 116 | }); 117 | context.subscriptions.push(disposable); 118 | } 119 | 120 | 121 | /* 122 | * Helper utility to register prompting system commands 123 | */ 124 | function registerPromptingCommand(context: ExtensionContext, key: string, opt, outputChannel, inputs: PromptInput[], additionalArgs: string[] = [], sanitizeOutput = false, PromptingClass = PromptingCommand) { 125 | const disposable = commands.registerCommand(key, () => { 126 | const command = new PromptingClass(opt.cmd, opt.args, outputChannel, inputs, additionalArgs, sanitizeOutput); 127 | return new Promise((resolve) => { 128 | resolve(executeCommand(command)); 129 | }); 130 | }); 131 | context.subscriptions.push(disposable); 132 | } 133 | 134 | 135 | function executeCommand(command: SystemCommand): Promise { 136 | return command.execute(); 137 | } 138 | 139 | 140 | /* 141 | * Checks the version of the IBM Cloud CLI and notifies the user if cli or recommended plugins are out of date 142 | */ 143 | function checkVersions(): Promise { 144 | 145 | return new Promise((resolve) => { 146 | 147 | // only run this once per session of vscode 148 | if (checkedVersions !== true) { 149 | checkedVersions = true; 150 | 151 | // first check the main cli version 152 | const command = new SystemCommand('ibmcloud', ['--version']); 153 | command.execute() 154 | .then(function() { 155 | if (command.stdout !== undefined) { 156 | 157 | // parse version from ibmcloud --version command output 158 | const split = command.stdout.split('+'); 159 | const detail = split[0].split('version'); 160 | const version = semver.clean(detail[detail.length - 1]); 161 | 162 | if (semver.gt(packageJson.ibm.cli.version, version)) { 163 | const message = `\n\nThe recommended minimum IBM Cloud CLI version is ${packageJson.ibm.cli.version}.\nYour system is currently running ${version}.\nA newer version of the IBM Cloud CLI is available for download at: ${packageJson.ibm.cli.url}`; 164 | outputChannel.append(message); 165 | } 166 | } 167 | }) 168 | .then(function() { 169 | 170 | // next check the plugin versions 171 | // list all plugins with version using `ibmcloud plugin list command` 172 | const pluginsListCommand = new SystemCommand('ibmcloud', ['plugin', 'list']); 173 | pluginsListCommand.execute() 174 | .then(function() { 175 | if (pluginsListCommand.stdout !== undefined) { 176 | const lines = pluginsListCommand.stdout.split('\n'); 177 | 178 | // skip first three lines (heading lines) 179 | for (let x = 3; x < lines.length; x++) { 180 | if (lines.length > 0) { 181 | 182 | if (lines.length > 0) { 183 | const line = lines[x]; 184 | const displayName = line.substr(0, 20).trim(); 185 | const pluginVersion = line.substr(20).trim(); 186 | const cleanVersion = semver.clean(pluginVersion); 187 | 188 | if (cleanVersion !== null) { 189 | for (const plugin of packageJson.ibm.plugins) { 190 | // loop over plugins and find match based on name 191 | if (displayName.search(plugin.displayName) >= 0) { 192 | if (semver.gt(plugin.version, cleanVersion)) { 193 | const message = `\n\nThe recommended minimum version for the IBM Cloud '${plugin.displayName}' CLI plugin is ${plugin.version}.\nYour system is currently running ${cleanVersion}.\nYou can update using the 'ibmcloud plugin update' command or visit ${plugin.url}`; 194 | outputChannel.append(message); 195 | break; 196 | } 197 | } 198 | } 199 | } 200 | } 201 | } 202 | } 203 | } 204 | resolve(); 205 | }); 206 | 207 | }); 208 | } else { 209 | resolve(); 210 | } 211 | }); 212 | } 213 | 214 | /* this method is called when your extension is deactivated 215 | */ 216 | export function deactivate() { } //eslint-disable-line @typescript-eslint/no-empty-function 217 | -------------------------------------------------------------------------------- /src/ibmcloud/cf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { Resource } from "./resource"; 20 | 21 | export interface CF { 22 | readonly org: Resource 23 | readonly space: Resource 24 | } 25 | -------------------------------------------------------------------------------- /src/ibmcloud/iam.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { SystemCommand } from '../util/SystemCommand'; 20 | 21 | export interface ServiceId { 22 | readonly id: string 23 | readonly name: string 24 | } 25 | 26 | /** 27 | * Retrieve service ids 28 | * @returns {Promise} 29 | */ 30 | export async function getServiceIds(): Promise> { 31 | const getIds = new SystemCommand('ibmcloud', ['iam', 'service-ids', '--output', 'json']); 32 | await getIds.execute(); 33 | 34 | if (getIds.stderr) { 35 | throw new Error(getIds.stderr); 36 | } 37 | 38 | const serviceIds: Array = JSON.parse(getIds.stdout); 39 | 40 | return serviceIds; 41 | } 42 | -------------------------------------------------------------------------------- /src/ibmcloud/plugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { SystemCommand } from '../util/SystemCommand'; 20 | import * as semver from 'semver'; 21 | import * as packageJson from '../../package.json'; 22 | import { IBMCloud } from '../consts'; 23 | import { OutputChannel } from 'vscode'; 24 | 25 | export interface PluginVersion { 26 | version: string 27 | } 28 | 29 | export interface RepoPlugin { 30 | readonly name: string 31 | readonly versions: Array 32 | } 33 | 34 | export interface PluginMetadata { 35 | readonly Name: string 36 | } 37 | 38 | 39 | /** 40 | * Retrieve all plugins in a given repository description 41 | * @param {string} repoName The repository where the plugins are stored (Default: "IBM Cloud") 42 | * @returns {Promise>} 43 | */ 44 | export async function getRepoPlugins(repoName: string = IBMCloud): Promise> { 45 | const allList = new SystemCommand('ibmcloud', ['plugin', 'repo-plugins', '--output', 'json']); 46 | await allList.execute(); 47 | 48 | if (allList.stderr) { 49 | throw new Error(allList.stderr); 50 | } 51 | 52 | const repoPlugins: Array = JSON.parse(allList.stdout)[repoName]; 53 | return repoPlugins.map((plugin: RepoPlugin) => plugin.name); 54 | } 55 | 56 | /** 57 | * Retrieve all installed plugins 58 | * @returns {Promise>} 59 | */ 60 | export async function getInstalledPlugins(): Promise> { 61 | const installedList = new SystemCommand('ibmcloud', ['plugin', 'list', '--output', 'json']); 62 | await installedList.execute(); 63 | if (installedList.stderr) { 64 | throw new Error(installedList.stderr); 65 | } 66 | const pluginMetas: Array = JSON.parse(installedList.stdout); 67 | 68 | return pluginMetas.map((meta: PluginMetadata) => meta.Name); 69 | } 70 | 71 | /** 72 | * Retrieve a list of available versions for a given plugin in a repository. Sorted in descending order 73 | * @param {string} pluginName The name of the plugin 74 | * @param {string} repoName The name of the repo (Default: "IBM Cloud") 75 | * @returns {Promise>} 76 | */ 77 | export async function getPluginVersions(pluginName: string, repoName: string = IBMCloud): Promise> { 78 | const repoPluginCmd = new SystemCommand('ibmcloud', ['plugin', 'repo-plugin', pluginName, '-r', repoName, '--output', 'json']); 79 | await repoPluginCmd.execute(); 80 | if (repoPluginCmd.stderr) { 81 | throw new Error(repoPluginCmd.stderr); 82 | } 83 | 84 | const repoPluginInfo: RepoPlugin = JSON.parse(repoPluginCmd.stdout); 85 | try { 86 | repoPluginInfo.versions.sort((a: PluginVersion, b: PluginVersion) => { 87 | return semver.lt(semver.clean(a.version), semver.clean(b.version)) ? 1 : 0; 88 | }); 89 | } catch (e) { 90 | console.log(`Failed to parse repo-plugin metadata for plugin ${pluginName}`); 91 | throw new Error(e); 92 | } 93 | 94 | return repoPluginInfo.versions; 95 | } 96 | 97 | /** 98 | * Attempt to install a plugin when running a plugin command fails to run because of missing plugin. 99 | * installPlugin will determine the plugin to install by finding matching plugin name/alias with the command that was executed 100 | * 101 | * @example Install missing container-service plugin when running ibmcloud ks clusters 102 | * // await installPlugin(ks) 103 | * @param {string} pluginCmd the excuted command 104 | * @param {OutputChannel} [outputChannel] - the optional outputChannel to write stdout to during installation 105 | */ 106 | export async function installPlugin(pluginCmd: string, outputChannel: OutputChannel = undefined): Promise { 107 | const namesAndAliases: Array = []; 108 | const meta = packageJson.ibm.plugins.find((plugin: any) => plugin.command == pluginCmd); 109 | if (meta) { 110 | namesAndAliases.push(meta.displayName); 111 | } 112 | 113 | // check to see if plugin is available to install 114 | const repoPlugins = await getRepoPlugins(); 115 | const pluginName = repoPlugins.find((p: string) => namesAndAliases.indexOf(p) > -1); 116 | 117 | // attempt to install the plugin if we have determined the installation name 118 | if (pluginName) { 119 | const cmd = new SystemCommand('ibmcloud', ['plugin', 'install', pluginName], outputChannel); 120 | const status = await cmd.execute(); 121 | if (cmd.stderr) { 122 | throw new Error(cmd.stderr); 123 | } 124 | return status; 125 | } 126 | 127 | throw new Error(`Could not determine plugin to install from command ${pluginCmd}`); 128 | } 129 | 130 | /** 131 | * Uninstall a given plugin 132 | * @param {string} pluginName The name of the plugin 133 | * @returns {Promise} 134 | */ 135 | export async function uninstallPlugin(pluginName: string): Promise { 136 | if (! await isPluginInstalled(pluginName)) { 137 | throw new Error(`${pluginName} is not installed`); 138 | } 139 | 140 | const cmd = new SystemCommand('ibmcloud', ['plugin', 'uninstall', pluginName]); 141 | const status = await cmd.execute(); 142 | 143 | return status; 144 | } 145 | 146 | /** 147 | * True if given plugin is installed 148 | * @param {string} pluginName The name of the plugin 149 | * @returns {Promise} 150 | */ 151 | export async function isPluginInstalled(pluginName: string): Promise { 152 | 153 | const plugins = await getInstalledPlugins(); 154 | const found = plugins.find((name: string) => pluginName == name); 155 | 156 | return !!found; 157 | } 158 | -------------------------------------------------------------------------------- /src/ibmcloud/resource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { SystemCommand } from "../util/SystemCommand"; 20 | 21 | export interface Resource { 22 | readonly guid: string 23 | readonly name: string 24 | } 25 | 26 | export async function getServiceAliases(): Promise> { 27 | const cmd = new SystemCommand('ibmcloud', ['resource', 'service-aliases', '--output', 'json']); 28 | await cmd.execute(); 29 | if (cmd.stderr) { 30 | throw new Error(cmd.stderr); 31 | } 32 | 33 | const resources: Array = JSON.parse(cmd.stdout); 34 | 35 | return resources; 36 | } 37 | -------------------------------------------------------------------------------- /src/ibmcloud/target.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { SystemCommand } from '../util/SystemCommand'; 20 | import { CF } from './cf'; 21 | import { window } from 'vscode'; 22 | 23 | export interface Target { 24 | readonly cf?: CF 25 | } 26 | 27 | /** 28 | * Return true if CF is targeted 29 | * @returns {Promise} 30 | */ 31 | export async function isCF(): Promise { 32 | const targetCmd = new SystemCommand('ibmcloud', ['target', '--output', 'json']); 33 | await targetCmd.execute(); 34 | 35 | if (targetCmd.stderr) { 36 | throw new Error(targetCmd.stderr); 37 | } 38 | 39 | const target: Target = JSON.parse(targetCmd.stdout); 40 | 41 | return !!target.cf; 42 | } 43 | 44 | /** 45 | * Select and target the CF endpoint, org and space for an account 46 | * @returns {Promise} 47 | */ 48 | export async function targetCF(): Promise { 49 | // NOTE: Running command via terminal in case the user needs to select from multiple orgs/spaces 50 | const targetCFCmd = new SystemCommand('ibmcloud', ['target', '--cf']); 51 | await targetCFCmd.executeWithTerminal(true); 52 | } 53 | -------------------------------------------------------------------------------- /src/util/CommandDetection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import {window} from 'vscode'; 20 | 21 | /* 22 | * Used to determine whether a command or plugin is installed 23 | * Only should be called by SystemCommand error handler 24 | */ 25 | export class CommandDetection { 26 | 27 | static ERR_NONE = 0; 28 | static ERR_UNKNOWN = 1; 29 | static ERR_COMMAND_NOT_FOUND = 2; 30 | static ERR_PLUGIN_NOT_FOUND = 3; 31 | 32 | /* 33 | * Determine error condition from SystemCommand 34 | * @param {number} the exit code of the ChildProcess instance 35 | * @param {string} stderr from the ChildProcess 36 | */ 37 | public static determineErrorCondition(code: number, stderr: string): number { 38 | if (code < 0) { 39 | return CommandDetection.ERR_COMMAND_NOT_FOUND; 40 | } else if (stderr.search('not a registered command') >= 0) { 41 | return CommandDetection.ERR_PLUGIN_NOT_FOUND; 42 | } 43 | return CommandDetection.ERR_NONE; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/util/IBMCloudTerminal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { window, Disposable, Terminal } from 'vscode'; 20 | 21 | /* 22 | * Singleton instance of Terinal wth IBMCloud identifier 23 | * for use globally within this extension 24 | */ 25 | export class IBMCloudTerminal { 26 | 27 | private static _terminal: Terminal; 28 | private static windowListener: Disposable; 29 | private static TERMINAL_NAME = 'IBMCloud'; 30 | 31 | /* 32 | * @returns {Terminal} instance 33 | */ 34 | public static get instance(): Terminal { 35 | 36 | if (this._terminal === undefined) { 37 | this._terminal = window.createTerminal(IBMCloudTerminal.TERMINAL_NAME, '', []); 38 | this.initWindowListener(); 39 | } 40 | this._terminal.show(); 41 | return this._terminal; 42 | } 43 | 44 | /* 45 | * initialize a listener on the window object to detect whenever a terminal is closed. 46 | * this way we can create a new one if its closed 47 | */ 48 | static initWindowListener() { 49 | if (this.windowListener === undefined) { 50 | this.windowListener = window.onDidCloseTerminal((e: Terminal) => { 51 | if (e.name === IBMCloudTerminal.TERMINAL_NAME) { 52 | this._terminal = undefined; 53 | } 54 | }); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/util/LoginManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import {commands, window, ExtensionContext} from 'vscode'; 20 | import {IBMCloudTerminal} from './IBMCloudTerminal'; 21 | 22 | /* 23 | * Managers interaction with integrated terminal to handle login/logout of ibmcloud cli 24 | */ 25 | export class LoginManager { 26 | 27 | /* 28 | * register login/logout command, with respect to this manager's terminal 29 | * @param {ExtensionContext} the vscode.ExtensionContext from extension activation 30 | * @param {string} event name/key for the extension action 31 | */ 32 | static registerCommand(context: ExtensionContext, key: string) { 33 | const disposable = commands.registerCommand(key, () => { 34 | 35 | const terminalArgs = ['ibmcloud', 'login']; 36 | if (key === 'extension.ibmcloud.login') { 37 | // add nothing for now 38 | } else if (key === 'extension.ibmcloud.login.sso') { 39 | terminalArgs.push('--sso'); 40 | } 41 | 42 | const terminal = IBMCloudTerminal.instance; 43 | terminal.sendText(`${terminalArgs.join(' ')}\n`); 44 | terminal.show(false); 45 | 46 | window.showInformationMessage('Complete your login in the Terminal panel below.'); 47 | }); 48 | context.subscriptions.push(disposable); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/util/PromptingCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { window, OutputChannel, QuickPickOptions } from 'vscode'; 20 | import { SystemCommand } from './SystemCommand'; 21 | 22 | /* 23 | * Class for specifying a command with prompt for input as parameters for PromptingCommand class 24 | */ 25 | export class PromptInput { 26 | 27 | prompt = ''; 28 | prefixArgument = undefined; 29 | pickerOptions: string[] = undefined; 30 | 31 | constructor(public _prompt: string, public _prefixArgument: string = undefined, pickerOptions: string[] = undefined) { 32 | this.prompt = _prompt; 33 | this.prefixArgument = _prefixArgument; 34 | this.pickerOptions = pickerOptions; 35 | } 36 | } 37 | 38 | /* 39 | * Class for invoking system commands with prompt(s) for input 40 | */ 41 | export class PromptingCommand extends SystemCommand { 42 | 43 | inputs: PromptInput[] = []; 44 | index = 0; 45 | originalArgs: any[] = []; 46 | additionalArgs: any[] = []; 47 | 48 | /* 49 | * Constructor 50 | * @param {string} command to be executed 51 | * @param {string[]} array of additional arguments 52 | * @param {OutputChannel} output channel to display system process output 53 | * @param {PromptInput[]} array of input definitions for vscode prompts 54 | * @param {string[]} additional arguments to append at the end of system call 55 | */ 56 | constructor(public command: string, public args: string[], public _outputChannel: OutputChannel, inputs: PromptInput[], additionalArgs: string[] = [], sanitizeOutput = false) { 57 | super(command, args, _outputChannel, sanitizeOutput); 58 | this.originalArgs = args.slice(0); 59 | 60 | this.inputs = inputs; 61 | this.additionalArgs = additionalArgs; 62 | } 63 | 64 | /* 65 | * Execute the commmand 66 | */ 67 | execute(): Promise { 68 | // duplicate the array, don't copy the instance 69 | this.args = this.originalArgs.slice(0); 70 | this.index = 0; 71 | return this.requestInput(); 72 | } 73 | 74 | /* 75 | * Prompt for input from the user 76 | */ 77 | requestInput(): Promise { 78 | 79 | const self = this; 80 | const input = this.inputs[this.index]; 81 | 82 | return new Promise((resolve, reject) => { 83 | 84 | const handler = (val) => { 85 | if (val !== undefined && val.length > 0) { 86 | if (input.prefixArgument !== undefined) { 87 | self.args.push(input.prefixArgument); 88 | } 89 | self.args.push(val); 90 | self.index++; 91 | if (self.index < self.inputs.length) { 92 | self.requestInput(); 93 | } 94 | else { 95 | // put any additional arguments on the end, like a -f 96 | self.args = self.args.concat(self.additionalArgs); 97 | super.execute() 98 | .then((value: any) => { 99 | resolve(value); 100 | }, (reason: any) => { 101 | reject(reason); 102 | }); 103 | } 104 | } 105 | else { 106 | this.output(`\n'${this.command} ${this.args.join(' ')}' action canceled: no user input at prompt.`); 107 | } 108 | }; 109 | 110 | let invocation; 111 | if (input.pickerOptions !== undefined && input.pickerOptions.length > 0) { 112 | invocation = window.showQuickPick(input.pickerOptions, { placeHolder: input.prompt }); 113 | } else { 114 | invocation = window.showInputBox({ prompt: input.prompt }); 115 | } 116 | invocation.then(handler); 117 | }); 118 | } 119 | 120 | 121 | /* 122 | * Destroy references in this class to prevent memory leaks 123 | */ 124 | destroy() { 125 | super.destroy(); 126 | this.inputs = undefined; 127 | this.additionalArgs = undefined; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/util/SystemCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | 'use strict'; 18 | 19 | import { window, workspace, OutputChannel } from 'vscode'; 20 | import { IBMCloudTerminal } from './IBMCloudTerminal'; 21 | import { CommandDetection } from './CommandDetection'; 22 | import { installPlugin } from '../ibmcloud/plugin'; 23 | import { CONFIRM_NO, CONFIRM_YES } from '../consts'; 24 | 25 | const spawn = require('child_process').spawn; 26 | const psTree = require('ps-tree'); 27 | 28 | 29 | /* 30 | * Class to execute system commands 31 | */ 32 | export class SystemCommand { 33 | command: string; 34 | args: string[]; 35 | invocation: any; 36 | outputChannel: OutputChannel; 37 | sanitizeOutput = false; 38 | stdout = ''; 39 | stderr = ''; 40 | 41 | 42 | // TECH DEBT: Two methods exist for invoking system commands 43 | // 1: invoke internally and display stdout and stderr in output panel (CURRENT IMPLEMENTATION) 44 | // 2: invoke within embedded terminal 45 | // 46 | // There are pros and cons to each approach. 47 | // Terminal feels really nice, but there are concurrency issues. EX: if you run ibmcloud dev debug, 48 | // it takes over the terminal, and you can't invoke anything else until that process exits 49 | // 50 | // Toggle between the two using this static var 51 | public useTerminal = false; 52 | 53 | 54 | /* 55 | * Constructor 56 | * @param {string} command to be executed 57 | * @param {string[]} array of additional arguments 58 | * @param {OutputChannel} output channel to display system process output 59 | */ 60 | constructor(public _command: string, public _args: string[] = [], public _outputChannel: OutputChannel = undefined, sanitizeOutput = false) { 61 | this.command = _command; 62 | this.args = _args; 63 | this.outputChannel = _outputChannel; 64 | this.sanitizeOutput = sanitizeOutput; 65 | 66 | if (this.outputChannel) { 67 | // NOTE: Do not perserve focus (`preserveFocus`) when showing output channel to allow 68 | // other UI components a chance to be in focus such as window.showInputBox and window.showQuickPick 69 | this.outputChannel.show(true); 70 | } 71 | } 72 | 73 | /* 74 | * Is the command active 75 | * @returns {boolean} Returns active status 76 | */ 77 | isActive() { 78 | return !!this.command; 79 | } 80 | 81 | /* 82 | * Destroy references in this class to prevent memory leaks 83 | */ 84 | destroy() { 85 | this.command = undefined; 86 | this.args = undefined; 87 | this.outputChannel = undefined; 88 | this.invocation = undefined; 89 | this.stdout = undefined; 90 | this.stderr = undefined; 91 | } 92 | 93 | /* 94 | * Execute the commmand 95 | */ 96 | execute(): Promise { 97 | if (this.useTerminal) 98 | return this.executeWithTerminal(); 99 | else 100 | return this.executeWithOutputChannel(); 101 | } 102 | 103 | /* 104 | * Execution implementation with VS Code's embedded terminal 105 | */ 106 | executeWithTerminal(preserveFocus?: boolean): Promise { 107 | return new Promise((resolve, reject) => { 108 | const terminal = IBMCloudTerminal.instance; 109 | terminal.sendText(`${this.command} ${this.args.join(' ')}\n`); 110 | terminal.show(preserveFocus); 111 | resolve('OK: sent to terminal'); 112 | }); 113 | } 114 | 115 | /* 116 | * Execution implementation with spawned process & output channels 117 | */ 118 | executeWithOutputChannel(): Promise { 119 | const self = this; 120 | const promise = new Promise((resolve, reject) => { 121 | 122 | // only check if we're in a folder if not running a unit test 123 | // (output channel will not be defined in unit test) 124 | if (workspace.rootPath === undefined && this.outputChannel !== undefined) { 125 | const message = 'Please select your project\'s working directory.'; 126 | this.output(`\nERROR: ${message}`); 127 | window.showErrorMessage(message); 128 | return; 129 | } 130 | 131 | this.output(`\n\n> ${this.command} ${this.args.join(' ')}\n`); 132 | 133 | this.stdout = ''; 134 | this.stderr = ''; 135 | 136 | const opt: any = { 137 | cwd: workspace.rootPath, 138 | env: { 139 | ...process.env, IBMCLOUD_COLOR: false 140 | } 141 | }; 142 | 143 | this.invocation = spawn(this.command, this.args, opt); 144 | 145 | let buffer; 146 | 147 | this.invocation.stdout.on('data', (data) => { 148 | 149 | // if we're sanitizing the output, keep it all in a buffer until complete 150 | // otherwise just write it out 151 | if (this.sanitizeOutput) { 152 | this.output('.'); 153 | 154 | if (buffer === undefined) { 155 | buffer = data; 156 | } 157 | else { 158 | const oldBuffer = buffer; 159 | buffer = new (buffer.constructor)(oldBuffer.length + data.length); 160 | buffer; 161 | for (let x = 0; x < oldBuffer.length; x++) { 162 | buffer[x] = oldBuffer[x]; 163 | } 164 | for (let y = 0; y < data.length; y++) { 165 | buffer[oldBuffer.length + y] = data[y]; 166 | } 167 | } 168 | } 169 | else { 170 | this.output(data.toString()); 171 | this.stdout += data.toString(); 172 | } 173 | }); 174 | 175 | this.invocation.stderr.on('data', (data) => { 176 | this.output(`${data}`); 177 | this.stderr += data.toString(); 178 | }); 179 | 180 | this.invocation.on('close', (code, signal) => { 181 | this.output(`\n`); 182 | 183 | if (this.sanitizeOutput && buffer) { 184 | const stringOutput = this.sanitizeBuffer(buffer).toString(); 185 | this.output(stringOutput); 186 | this.stdout = stringOutput; 187 | buffer = undefined; 188 | } 189 | 190 | const condition = CommandDetection.determineErrorCondition(code, this.stderr); 191 | 192 | if (condition === CommandDetection.ERR_COMMAND_NOT_FOUND || condition === CommandDetection.ERR_PLUGIN_NOT_FOUND) { 193 | let errorDetail = ''; 194 | const pluginCmd = this.args[0]; 195 | const output = this.outputChannel; 196 | 197 | // NOTE: Since we are using callback promises we need to use `bind` method to ensure that 198 | // we are using the original `this` context (via `self`) that called executeOuputChannel 199 | const rerunCmd = this.executeWithOutputChannel.bind(self); 200 | 201 | switch (condition) { 202 | case CommandDetection.ERR_COMMAND_NOT_FOUND: 203 | errorDetail = `Unable to locate '${this.command}' command.`; 204 | break; 205 | case CommandDetection.ERR_PLUGIN_NOT_FOUND: 206 | errorDetail = `Unable to locate '${this.command}' '${this.args[0]}' plugin. Would you like to install '${this.args[0]}' and rerun the previous command?`; 207 | // ask user if we should install the missing plugin and rerun the previous command 208 | window.showQuickPick([CONFIRM_YES, CONFIRM_NO], { title: errorDetail }) 209 | .then((res: string) => { 210 | if (res === CONFIRM_YES) { 211 | installPlugin(pluginCmd, output) 212 | .then((status: number) => { 213 | if (status === 0) { 214 | resolve(rerunCmd()); 215 | } 216 | resolve(status); 217 | }); 218 | } 219 | }); 220 | 221 | break; 222 | } 223 | 224 | // Only print addition error messages for non plugin errors 225 | if (condition != CommandDetection.ERR_PLUGIN_NOT_FOUND) { 226 | errorDetail += '\nFor additional detail, please see https://cloud.ibm.com/docs/cli'; 227 | window.showErrorMessage(errorDetail); 228 | this.output(errorDetail); 229 | 230 | // add this at the end of the promise chain, so it gets called last, for certain. 231 | // this allows us to fulfill other steps in the promise chain, and clean everything up last 232 | promise.finally(function() { 233 | self.destroy(); 234 | }); 235 | } 236 | } 237 | resolve(code); 238 | 239 | }); 240 | 241 | this.invocation.on('error', (error) => { 242 | // do something with the error? 243 | // for now, ignore and let the 'close' event 244 | // take care of things with negative status code 245 | }); 246 | }); 247 | 248 | return promise; 249 | } 250 | 251 | 252 | /* 253 | * Sanitize the ChildProcess stdio output. 254 | * This takes into account ascii backspace and del characters 255 | * that the VSCode output channel doesn't handle by default. 256 | * @param {Buffer} unsanitized stdoio buffer 257 | * @returns {Buffer} sanitized buffer 258 | */ 259 | private sanitizeBuffer(buffer) { 260 | 261 | // below contains a workaround for 16 bit integers being represented as 8 bit integers 262 | // from the Node ChildProcess stdio stream, which is causing errors on special unicode characters 263 | // as seen in 'ibmcloud dev list' - it will probably also happen in other places that 264 | // special characters are also used for cli loading animations 265 | 266 | let newLen = 0; 267 | for (let x = 0; x < buffer.length; x++) { 268 | const char = buffer[x]; 269 | if (char === 8 || char === 127) { 270 | newLen--; 271 | newLen = Math.max(newLen, 0); 272 | } 273 | else { 274 | // workaround described above 275 | if (x > 0 && char === 160 && buffer[x - 1] === 226) { 276 | newLen -= 1; 277 | } 278 | else 279 | newLen++; 280 | } 281 | } 282 | 283 | const outBuffer = new (buffer.constructor)(newLen); 284 | let i = 0; 285 | const lastNewline = 0; 286 | for (let x = 0; x < buffer.length; x++) { 287 | const char = buffer[x]; 288 | if (char === 8 || char === 127) { // backspace and delete 289 | i--; 290 | } else { 291 | // workaround described above 292 | if (x > 0 && char === 160 && buffer[x - 1] === 226) { 293 | i -= 2; 294 | } 295 | outBuffer[i] = char; 296 | i++; 297 | } 298 | i = Math.max(i, 0); 299 | } 300 | 301 | return outBuffer; 302 | } 303 | 304 | /* 305 | * Display output in targeted output channel 306 | */ 307 | output(data: any) { 308 | if (this.outputChannel) { 309 | this.outputChannel.append(data); 310 | } 311 | } 312 | 313 | /* 314 | * Kill the process (spawned process only) 315 | */ 316 | kill() { 317 | if (this.invocation) { 318 | const self = this; 319 | const signal = 'SIGKILL'; 320 | psTree(self.invocation.pid, function(err, children) { 321 | [self.invocation.pid].concat( 322 | children.map(function(p) { 323 | return p.PID; 324 | }), 325 | ).forEach(function(tpid) { 326 | try { 327 | self.output(`killing ${tpid}\n`); 328 | process.kill(tpid, signal); 329 | } 330 | catch (ex) { 331 | console.log(ex); 332 | } 333 | }); 334 | }); 335 | 336 | if (this.outputChannel) { 337 | this.outputChannel.show(); 338 | } 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2022 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // The module 'assert' provides assertion methods from node 18 | import { assert } from 'chai'; 19 | import * as sinon from 'sinon'; 20 | 21 | // You can import and use all API from the 'vscode' module 22 | // as well as import your extension to test it 23 | import * as vscode from 'vscode'; 24 | import { IBMCloudTerminal } from '../src/util/IBMCloudTerminal'; 25 | import { SystemCommand } from '../src/util/SystemCommand'; 26 | import * as resource from '../src/ibmcloud/resource'; 27 | import * as packageJson from '../package.json'; 28 | import { getServiceIds } from '../src/ibmcloud/iam'; 29 | import * as plugin from '../src/ibmcloud/plugin'; 30 | import { CONFIRM_NO, CONFIRM_YES } from '../src/consts'; 31 | 32 | // Defines a Mocha test suite to group tests of similar kind together 33 | function logStub(cmd: string, outputChannel: sinon.SinonStub) { 34 | console.log(`\n================================(${cmd})================================`); 35 | outputChannel.args.forEach((arg: any) => { 36 | if (Array.isArray(arg) && arg.length > 0) console.log(arg[0]); 37 | }); 38 | console.log(`\n================================(${cmd})================================`); 39 | } 40 | 41 | describe('Extension Tests', function() { 42 | describe('System Commands', function() { 43 | 44 | context('when executing a system command', function() { 45 | 46 | it('should run successfully when executing a valid command', async function() { 47 | const command = new SystemCommand('ls', ['-l', '-a']); 48 | const statusCode = await command.execute(); 49 | assert.equal(0, statusCode); 50 | }); 51 | 52 | it('should fail when executing an invalid command', async function() { 53 | const command = new SystemCommand('foo'); 54 | const statusCode = await command.execute(); 55 | assert.notEqual(0, statusCode); 56 | }); 57 | }); 58 | }); 59 | 60 | describe('IBM Cloud CLI Commands', function() { 61 | // REMOVEME: Do not commit line below 62 | this.timeout(80000); 63 | const extensionName = `${packageJson.publisher}.${packageJson.name}`; 64 | let extension: any; 65 | let outputChannel: sinon.SinonStub; 66 | let showQuickPick: sinon.SinonStub; 67 | let showInputBox: sinon.SinonStub; 68 | let sendTerminalText: sinon.SinonSpy; 69 | let showWarningMessage: sinon.SinonSpy; 70 | const sandbox = sinon.createSandbox(); 71 | 72 | before(async function() { 73 | extension = vscode.extensions.getExtension(extensionName); 74 | if (extension) { 75 | await extension.activate(); 76 | } else { 77 | assert.fail('Could not find extension'); 78 | } 79 | 80 | // mock vscode methods 81 | outputChannel = sandbox.stub(SystemCommand.prototype, 'output'); 82 | showQuickPick = sandbox.stub(vscode.window, 'showQuickPick'); 83 | showInputBox = sandbox.stub(vscode.window, 'showInputBox'); 84 | showWarningMessage = sandbox.stub(vscode.window, 'showWarningMessage'); 85 | sendTerminalText = sandbox.stub(IBMCloudTerminal.instance, 'sendText'); 86 | }); 87 | 88 | afterEach(async function() { 89 | sandbox.reset(); 90 | }); 91 | 92 | after(async function() { 93 | sandbox.restore(); 94 | }); 95 | 96 | context('when not logged in', function() { 97 | before(async () => { 98 | try { 99 | const logoutCmd = new SystemCommand('ibmcloud', ['logout']); 100 | const statusCode = await logoutCmd.execute(); 101 | assert.equal(0, statusCode); 102 | } catch (e) { 103 | assert.fail(e); 104 | } 105 | }); 106 | }); 107 | 108 | context('when already logged in', function() { 109 | 110 | before(async function() { 111 | try { 112 | const loginCmd = new SystemCommand('ibmcloud', ['login', '-r', 'us-south', '-a', 'https://cloud.ibm.com']); 113 | let statusCode = await loginCmd.execute(); 114 | logStub('ibmcloud login', outputChannel); 115 | assert.equal(0, statusCode); 116 | 117 | const orgsCmd = new SystemCommand('ibmcloud', ['account', 'orgs', '--output', 'json']); 118 | statusCode = await orgsCmd.execute(); 119 | logStub('ibmcloud account orgs --output json', outputChannel); 120 | assert.equal(0, statusCode); 121 | const orgs = JSON.parse(orgsCmd.stdout); 122 | assert.isArray(orgs); 123 | assert.isAbove(orgs.length, 0); 124 | const targetedOrg = orgs[0].OrgName; 125 | 126 | const spaceCmd = new SystemCommand('ibmcloud', ['account', 'spaces', '-o', targetedOrg, '--output', 'json']); 127 | statusCode = await spaceCmd.execute(); 128 | logStub(`ibmcloud account spaces -o ${targetedOrg} --output json`, outputChannel); 129 | assert.equal(0, statusCode); 130 | const spaces = JSON.parse(spaceCmd.stdout); 131 | assert.isArray(spaces); 132 | assert.isAbove(spaces.length, 0); 133 | const targetedSpace = spaces[0].name; 134 | 135 | const targetCFCmd = new SystemCommand('ibmcloud', ['target', '-o', targetedOrg, '-s', targetedSpace]); 136 | statusCode = await targetCFCmd.execute(); 137 | logStub(`ibmcloud target -o ${targetedOrg} -s ${targetedSpace}`, outputChannel); 138 | assert.equal(0, statusCode); 139 | } catch (e) { 140 | assert.fail(e); 141 | } 142 | }); 143 | 144 | it('should return the api endpoint', async function() { 145 | await vscode.commands.executeCommand('extension.ibmcloud.api'); 146 | logStub('ibmcloud api', outputChannel); 147 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud api/))).callCount, 1); 148 | assert.isAbove(outputChannel.withArgs(sinon.match(new RegExp(/API endpoint: https:\/\/(?.*)*cloud.ibm.com/))).callCount, 0); 149 | }); 150 | 151 | // NOTE: install/updating through tests are unstable. 152 | // Disabling until a better solution can be found 153 | context.skip('missing plugin', function() { 154 | let pluginName: string; 155 | let installPlugin: sinon.SinonStub; 156 | let executeWithOutputChannel: sinon.SinonSpy; 157 | 158 | before(async function() { 159 | pluginName = 'container-service'; 160 | installPlugin = sinon.stub(plugin, 'installPlugin'); 161 | executeWithOutputChannel = sinon.spy(SystemCommand.prototype, 'executeWithOutputChannel'); 162 | try { 163 | if (await plugin.isPluginInstalled(pluginName)) { 164 | await plugin.uninstallPlugin(pluginName); 165 | } 166 | } catch (e) { 167 | assert.fail(e); 168 | } 169 | }); 170 | 171 | afterEach(function() { 172 | installPlugin.reset(); 173 | executeWithOutputChannel.resetHistory(); 174 | }); 175 | 176 | after(function() { 177 | installPlugin.restore(); 178 | executeWithOutputChannel.restore(); 179 | }); 180 | 181 | it('should attempt to install container-service plugin and rerun list clusters command', async function() { 182 | installPlugin.resolves(0); 183 | showQuickPick.resolves(CONFIRM_YES); 184 | await vscode.commands.executeCommand('extension.ibmcloud.ks.clusters'); 185 | assert.isAbove(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud ks clusters/))).callCount, 1); 186 | logStub('ibmcloud ks clusters', outputChannel); 187 | assert.isTrue(installPlugin.called); 188 | //NOTE: called by `ks clusters`, `plugin install`, and reran `ks clusters cmds respectively 189 | assert.isTrue(executeWithOutputChannel.calledThrice); 190 | }); 191 | 192 | it('should not install container-service and rerun command', async function() { 193 | installPlugin.resolves(0); 194 | showQuickPick.resolves(CONFIRM_NO); 195 | await vscode.commands.executeCommand('extension.ibmcloud.ks.clusters'); 196 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud ks clusters/))).callCount, 1); 197 | logStub('ibmcloud ks clusters', outputChannel); 198 | assert.isFalse(installPlugin.called); 199 | // NOTE: called only once for `ks clusters` 200 | assert.isTrue(executeWithOutputChannel.calledOnce); 201 | }); 202 | }); 203 | 204 | it('should print list of regions', async function() { 205 | await vscode.commands.executeCommand('extension.ibmcloud.regions'); 206 | logStub('ibmcloud regions', outputChannel); 207 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud regions/))).callCount, 1); 208 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/Listing regions.../))).callCount, 1); 209 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/(Name)\s+(Show name)/))).callCount, 1); 210 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/(us-south)\s+(Dallas)/))).callCount, 1); 211 | }); 212 | 213 | it('should display current target', async function() { 214 | await vscode.commands.executeCommand('extension.ibmcloud.target'); 215 | logStub('ibmcloud target', outputChannel); 216 | assert.isTrue(outputChannel.calledWith(sinon.match(/ibmcloud target/))); 217 | assert.isTrue(outputChannel.calledWith(sinon.match(/API endpoint/))); 218 | }); 219 | 220 | context('ibmcloud account', function() { 221 | 222 | it('should display a list of accounts in a table', async function() { 223 | await vscode.commands.executeCommand('extension.ibmcloud.account.list'); 224 | logStub('ibmcloud account list', outputChannel); 225 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud account list/))).callCount, 1); 226 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/(Account GUID)\s+(IMS ID)\s+(Name)\s+(State)\s+(Owner User)\s+(ID)/))).callCount, 1); 227 | }); 228 | 229 | it('should display the current account\'s information', async function() { 230 | await vscode.commands.executeCommand('extension.ibmcloud.account.show'); 231 | logStub('ibmcloud account show', outputChannel); 232 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud account show/))).callCount, 1); 233 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/OK/))).callCount, 1); 234 | }); 235 | 236 | it('should display the users in account', async function() { 237 | await vscode.commands.executeCommand('extension.ibmcloud.account.users'); 238 | logStub('ibmcloud account users', outputChannel); 239 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud account users/))).callCount, 1); 240 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/Getting users under account (?.*)\.\.\./))).callCount, 1); 241 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/OK/))).callCount, 1); 242 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/(User ID)\s+(State)/))).callCount, 1); 243 | }); 244 | }); 245 | 246 | context('ibmcloud resource', function() { 247 | 248 | it('should return list of service instances', async function() { 249 | await vscode.commands.executeCommand('extension.ibmcloud.resource.service-instances'); 250 | logStub('ibmcloud resource service-instances', outputChannel); 251 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud resource service-instances/))).callCount, 1); 252 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/Retrieving instances with type service_instance in all resource groups in all locations under account (?.*)/))).callCount, 1); 253 | }); 254 | 255 | // NOTE: install/updating through tests are unstable. 256 | // Disabling until a better solution can be found 257 | context.skip('ibmcloud plugin', function() { 258 | let plugins: Array; 259 | 260 | beforeEach(async function() { 261 | plugins = ['cloudant']; 262 | }); 263 | afterEach(async function() { 264 | plugins = []; 265 | }); 266 | 267 | it('should be able to install a plugin from official IBM Cloud repo', async function() { 268 | showQuickPick.resolves(plugins); 269 | await vscode.commands.executeCommand('extension.ibmcloud.plugin.install'); 270 | logStub('ibmcloud plugin install', outputChannel); 271 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud plugin install ${plugins[0]}`))).callCount, 1); 272 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`Plug-in '${plugins[0]} (?.*)' was successfully installed`))).callCount, 1); 273 | }); 274 | 275 | it('should update all plugins/extensions', async function() { 276 | await vscode.commands.executeCommand('extension.ibmcloud.cli-update'); 277 | logStub('ibmcloud update', outputChannel); 278 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/ibmcloud plugin update/))).callCount, 1); 279 | }); 280 | 281 | it('should be able to install all plugins from official IBM Cloud repo', async function() { 282 | // NOTE: This is a long running command so we should increase timeout in this test case 283 | this.timeout(120000); 284 | showQuickPick.resolves(plugins); 285 | await vscode.commands.executeCommand('extension.ibmcloud.cli-install'); 286 | logStub('ibmcloud plugin install --all -r IBM Cloud -f', outputChannel); 287 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud plugin install --all -r IBM Cloud -f`))).callCount, 1); 288 | }); 289 | 290 | context('older version of plugin installed', function() { 291 | let oldestPluginVersion: plugin.PluginVersion; 292 | 293 | before(async function() { 294 | plugins = ['cloudant']; 295 | let pluginVersions: Array; 296 | try { 297 | pluginVersions = await plugin.getPluginVersions(plugins[0]); 298 | } catch (e) { 299 | assert.fail(e); 300 | } 301 | 302 | oldestPluginVersion = pluginVersions[pluginVersions.length - 1]; 303 | 304 | const pluginInstallCmd = new SystemCommand('ibmcloud', ['plugin', 'install', plugins[0], 305 | '-v', oldestPluginVersion.version, '-f', '-q']); 306 | const statusCode = await pluginInstallCmd.execute(); 307 | assert.equal(statusCode, 0); 308 | 309 | }); 310 | 311 | it('should be able to update plugin to latest version', async function() { 312 | showQuickPick.resolves(plugins); 313 | await vscode.commands.executeCommand('extension.ibmcloud.plugin.update'); 314 | logStub('ibmcloud plugin update', outputChannel); 315 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud plugin update ${plugins[0]}`))).callCount, 1); 316 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`Checking upgrades for plug-in '${plugins[0]}`))).callCount, 1); 317 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`Update '${plugins[0]} ${oldestPluginVersion.version}'`))).callCount, 1); 318 | assert.equal(outputChannel.withArgs(sinon.match('The plug-in was successfully upgraded.')).callCount, 1); 319 | }); 320 | }); 321 | 322 | it('should be able to uninstall plugin', async function() { 323 | showQuickPick.resolves(plugins); 324 | await vscode.commands.executeCommand('extension.ibmcloud.plugin.uninstall'); 325 | logStub('ibmcloud plugin uninstall', outputChannel); 326 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud plugin uninstall ${plugins[0]}`))).callCount, 1); 327 | assert.equal(outputChannel.withArgs(sinon.match(`Uninstalling plug-in '${plugins[0]}'...`)).callCount, 1); 328 | assert.equal(outputChannel.withArgs(sinon.match(`Plug-in '${plugins[0]}' was successfully uninstalled.`)).callCount, 1); 329 | }); 330 | 331 | }); 332 | 333 | context('ibmcloud service-binding', function() { 334 | let getServiceAliases: sinon.SinonStub; 335 | let serviceAliases: Array; 336 | 337 | before(() => { 338 | getServiceAliases = sinon.stub(resource, 'getServiceAliases'); 339 | serviceAliases = [{ name: 'cloudant_alias_bdd', guid: 'guid' }]; 340 | }); 341 | afterEach(() => { 342 | getServiceAliases.reset(); 343 | }); 344 | after(() => { 345 | getServiceAliases.restore(); 346 | }); 347 | 348 | it.skip('should return details for a service-binding', async function() { 349 | // TODO(me): Figure out why this test stalls and never finishes 350 | const cfApp = 'bdd_cf_go_app'; 351 | getServiceAliases.resolves(serviceAliases); 352 | showQuickPick.resolves(serviceAliases[0].name); 353 | showInputBox.resolves(cfApp); 354 | 355 | await vscode.commands.executeCommand('extension.ibmcloud.resource.service-binding.get'); 356 | logStub(`ibmcloud resource service-binding ${serviceAliases[0].name} ${cfApp}`, outputChannel); 357 | 358 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud resource service-binding ${serviceAliases[0]} ${cfApp}`))).callCount, 1); 359 | }); 360 | 361 | it('should return list of service-bindings', async function() { 362 | getServiceAliases.resolves(serviceAliases); 363 | showQuickPick.resolves(serviceAliases[0].name); 364 | await vscode.commands.executeCommand('extension.ibmcloud.resource.service-binding.list'); 365 | logStub(`ibmcloud resource service-bindings ${serviceAliases[0].name}`, outputChannel); 366 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(`>\\s+ibmcloud resource service-bindings ${serviceAliases[0]}`))).callCount, 1); 367 | assert.isTrue(sendTerminalText.notCalled); 368 | }); 369 | 370 | it('should return a warning message if no service aliases exist in account', async function() { 371 | getServiceAliases.resolves([]); 372 | await vscode.commands.executeCommand('extension.ibmcloud.resource.service-binding.list'); 373 | assert.isTrue(showWarningMessage.calledWith('No service alias could be found. Please create a service alias and try again'), 'Expected warning messsage to be printed'); 374 | assert.isFalse(outputChannel.calledWith(sinon.match(new RegExp(`>\\s+ibmcloud resource service-bindings ${serviceAliases[0]}`)))); 375 | }); 376 | }); 377 | }); 378 | 379 | context('ibmcloud iam', function() { 380 | let serviceIdName: string; 381 | before(async function() { 382 | try { 383 | const serviceIds = await getServiceIds(); 384 | serviceIdName = serviceIds[0].name; 385 | } catch (e) { 386 | assert.fail(e); 387 | } 388 | 389 | }); 390 | 391 | it('should return oauth-tokens', async function() { 392 | await vscode.commands.executeCommand('extension.ibmcloud.iam.oauth-tokens'); 393 | logStub('ibmcloud iam oauth-tokens', outputChannel); 394 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud iam oauth-tokens/))).callCount, 1); 395 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/IAM token:\s+Bearer (?.*)/))).callCount, 1); 396 | }); 397 | 398 | it('should return details for a list of service ids', async function() { 399 | await vscode.commands.executeCommand('extension.ibmcloud.iam.service-ids'); 400 | logStub('ibmcloud iam service-ids', outputChannel); 401 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud iam service-ids/))).callCount, 1); 402 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/Getting all services IDs bound to current account as (?.*).../))).callCount, 1); 403 | assert.equal(outputChannel.withArgs(sinon.match('OK')).callCount, 1); 404 | }); 405 | 406 | it('should return details for a service id', async function() { 407 | showQuickPick.resolves([serviceIdName]); 408 | await vscode.commands.executeCommand('extension.ibmcloud.iam.service-id'); 409 | logStub('ibmcloud iam service-id', outputChannel); 410 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/>\s+ibmcloud iam service-id (?.*)/))).callCount, 1); 411 | assert.equal(outputChannel.withArgs(sinon.match(new RegExp(/Getting service ID (?.*) as (?.*).../))).callCount, 1); 412 | }); 413 | }); 414 | }); 415 | }); 416 | }); 417 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | import * as glob from 'glob'; 18 | import * as Mocha from 'mocha'; 19 | import * as path from 'path'; 20 | 21 | export function run(testsRoot: string, cb: (error: any, failures?: number) => void): void { 22 | // Create the mocha test 23 | const mocha = new Mocha({ 24 | ui: 'bdd', 25 | bail: true 26 | }); 27 | 28 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 29 | if (err) { 30 | return cb(err); 31 | } 32 | 33 | // Add files to the test suite 34 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 35 | 36 | try { 37 | // Run the mocha test 38 | mocha.run((failures) => { 39 | cb(null, failures); 40 | }); 41 | } catch (err) { 42 | console.error(err); 43 | cb(err); 44 | } 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test/runTest.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2023 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import * as path from 'path'; 17 | import { stat, mkdirSync } from 'fs'; 18 | 19 | import { downloadAndUnzipVSCode, runTests } from '@vscode/test-electron'; 20 | 21 | async function dirExists(dir: string) { 22 | return new Promise((resolve) => { 23 | stat(dir, (err, file) => { 24 | if (err) { 25 | resolve(false); 26 | return; 27 | } 28 | resolve(file.isDirectory()); 29 | }); 30 | }); 31 | } 32 | 33 | async function go() { 34 | try { 35 | const extensionDevelopmentPath = path.resolve(__dirname, '../..'); 36 | const extensionTestsPath = path.resolve(__dirname, './'); 37 | const testWorkspace = path.resolve(__dirname, '../../test/workspace'); 38 | 39 | // Create workspace if directory does not already exist 40 | const workspaceExists = await dirExists(testWorkspace); 41 | if (!workspaceExists) { 42 | console.log('Could not find workspace. Creating workspace folder...'); 43 | mkdirSync(testWorkspace); 44 | console.log(`Workspace folder ${testWorkspace} was created`); 45 | 46 | } 47 | 48 | const vscodeExecutablePath = await downloadAndUnzipVSCode(); 49 | await runTests({ 50 | vscodeExecutablePath, 51 | extensionDevelopmentPath, 52 | extensionTestsPath, 53 | launchArgs: [ 54 | testWorkspace, 55 | // This disables all extensions except the one being testing 56 | '--disable-extensions', 57 | ], 58 | }); 59 | } catch (err) { 60 | console.error(err); 61 | console.error('Failed to run tests'); 62 | process.exit(1); 63 | } 64 | } 65 | 66 | go(); 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "resolveJsonModule": true, 5 | "target": "es6", 6 | "outDir": "out", 7 | "lib": [ 8 | "es6" 9 | ], 10 | "sourceMap": true, 11 | "rootDir": "." 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | ".vscode-test" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | 8 | /** 9 | * @type {import('webpack').Configuration} 10 | * */ 11 | const config = { 12 | target: 'node', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target 13 | 14 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 15 | output: { 16 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 17 | path: path.resolve(__dirname, 'out', 'src'), 18 | filename: 'extension.js', 19 | libraryTarget: 'commonjs2', 20 | devtoolModuleFilenameTemplate: '../[resource-path]' 21 | }, 22 | devtool: 'source-map', 23 | externals: { 24 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'], 29 | fallback: { 30 | 'buffer': require.resolve('buffer') , 31 | 'stream': require.resolve('stream-browserify') 32 | // Webpack 5 no longer polyfills Node.js core modules automatically. 33 | // see https://webpack.js.org/configuration/resolve/#resolvefallback 34 | // for the list of Node.js core module polyfills. 35 | } 36 | }, 37 | module: { 38 | rules: [ 39 | { 40 | test: /\.ts$/, 41 | exclude: /node_modules/, 42 | use: [ 43 | { 44 | loader: 'ts-loader', 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | }; 51 | module.exports = config; 52 | --------------------------------------------------------------------------------