diff --git a/CHANGELOG.md b/CHANGELOG.md index 487eabafe..5339877c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to the "vscode-camelk" extension will be documented in this ## 0.0.6 -- TBD +- Added ability to deploy integrations with associated Kubernetes ConfigMaps or Secrets +- Added ability to create Kubernetes ConfigMaps or Secrets +- Began migrating away from direct Kubectl calls in favor of using the Kubernetes API component +- Changed 'Camel-K' to 'Apache Camel K' across the board for consistency +- Added tests for ConfigMap and Secret utilities +- Added Kotlin +- Moved menus to separate group to avoid cluttering up the Explorer context menu as much ## 0.0.5 diff --git a/README.md b/README.md index 1ca3389fd..9f878fd2c 100644 --- a/README.md +++ b/README.md @@ -3,82 +3,115 @@ [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)]() [![Gitter](https://img.shields.io/gitter/room/camel-tooling/Lobby.js.svg)](https://gitter.im/camel-tooling/Lobby) -# Visual Studio extension to support Camel-K +# Visual Studio extension to support Apache Camel K -This extension offers basic integration with Camel-K (https://github.com/apache/camel-k) on two fronts. +This extension offers basic integration with Apache Camel K (https://github.com/apache/camel-K) on two fronts. -First, Camel-K runs with a combination of the "kamel" runtime and either Minishift, Minikube, or GKE running locally on the development system. We utilize the "kamel" and "kubectl" executables to manage a few basic tasks listed further down. +First, Apache Camel K runs with a combination of the "kamel" runtime and either Minishift, Minikube, or GKE running locally on the development system. We utilize the "kamel" and "kubectl" executables to manage a few basic tasks listed further down. Second, we also have the capability of using Restful calls to a Proxy when provided the correct URL and port combination (defaulting to http://localhost:8000). The proxy offers the same functionality as if we were using the command-line executables. -To install Minikube and Camel-K, see [Installing MiniKube and Camel-K](configure-minikube-camelk.md). +To install Minikube and Apache Camel K, see [Installing MiniKube and Camel K](configure-minikube-camelk.md). ## Kubernetes tools in VS Code -The [Kubernetes Tools extension from Microsoft](https://marketplace.visualstudio.com/items?itemName=ms-kubernetes-tools.vscode-kubernetes-tools) offers a number of tools we can use with Minikube and Camel-K. With a local Minikube instance running, you can see your local clusters appear in the Kubernetes Activity view. +The [Kubernetes Tools extension from Microsoft](https://marketplace.visualstudio.com/items?itemName=ms-kubernetes-tools.vscode-kubernetes-tools) offers a number of tools we can use with Minikube and Apache Camel K. With a local Minikube instance running, you can see your local clusters appear in the Kubernetes Activity view. -![Kubernetes Activity with Camel-K](images/kubernetes-view-camelk.jpg) +![Kubernetes Activity with Camel K](images/kubernetes-view-camelk.jpg) With any node appearing in a Minikube cluster, you can easily follow the logs by right-clicking and selecting "Follow Logs" in the context menu. ![Kubernetes View pop-up menu](images/kubernetes-view-camelk-popup.jpg) -This opens a log for that pod in a new Terminal window. +This opens a log for the selected pod in a new Terminal window. ![Kubernetes View operator log](images/kubernetes-view-camelk-operator-log.jpg) -## Starting new Camel-K integrations +## Starting new Apache Camel K integrations -Once your Camel-K/Minikube environment is running and the vscode-camelk extension is installed, you can easily start a new Camel-K integration from a Java (*.java), Camel XML (Spring DSL) (*.xml), JavaScript (*.js) or Groovy (*.groovy) file. (Kotlin files may be supported in the future.) To do this, right-click on the Java, XML, JavaScript, or Groovy file, and select "Start Camel-K Integration." +Once your Apache Camel K/Minikube environment is running and the vscode-camelk extension is installed, you can easily start a new Apache Camel K integration from a Java (*.java), Camel XML (Spring DSL) (*.xml), JavaScript (*.js), Groovy (*.groovy), or Kotlin (*.kts) file. To do this, right-click on the integration file, and select "Start Apache Camel K Integration". -With [Language Support for Apache Camel](https://marketplace.visualstudio.com/items?itemName=camel-tooling.vscode-apache-camel) installed, you also get LSP support for Camel XML and Java routes: +With [Language Support for Apache Camel](https://marketplace.visualstudio.com/items?itemName=camel-tooling.vscode-apache-camel) installed, you also get LSP support for URIs and more in Camel XML, Java, Groovy, and other routes: ![Hello XML](images/kubernetes-view-camelk-hello-xml.jpg) -LSP support is also coming for other file types (such as Groovy). And we will investigate adding run support for *.class files. +## 'Start Apache Camel K Integration' menu results -## 'Start Camel-K Integration' menu results - -If Camel-K (Kamel) is in the system path, we can simply call the 'kamel' utility with appropriate options to run a particular file when the user wishes. For example, if I have a simple workspace with a Groovy file... +If the Apache Camel K executable (kamel.exe) is in the system path, we can simply call the utility with appropriate options to run a particular file when the user wishes. For example, if I have a simple workspace with a Groovy file... ![Run Menu](images/kubernetes-view-camelk-run-xml-menu.jpg) -That launches my 'kamel' process from an XML file in the directory of the file (i.e. `kamel run "filename"` or the equivalent Kubernetes rest call) or uses the Kubernetes Rest API to deploy the integration to the running Kubernetes system. +Once you click the Start Apache Camel-K Integration menu, a drop-down appears in the command palette with three choices: + +![Start types](images/camelk-start-integration-dropdown.jpg) + +You have three choices: + +* Basic - Apache Camel K Integration (no ConfigMap or Secret) +* ConfigMap - Apache Camel-K Integration with Kubernetes ConfigMap +* Secret - Apache Camel-K Integration with Kubernetes Secret + +We'll cover the Basic case here. When you select the "Basic" option, it runs my Apache Camel-K Groovy file as a new integration in the directory of the file (i.e. `kamel run "filename"` or the equivalent Kubernetes rest call) or uses the Kubernetes Rest API to deploy the integration to the running Kubernetes system. + +Once the integration is in a "running" state, it's ready to go! + +Note that the first time a new integration is published, it may take a few seconds to propagate through the system to a running state. Use the "Refresh" button when you hover over the Apache Camel K Integrations view to update the state of your currently deployed integrations. + +## Publishing new Kubernetes ConfigMaps or Secrets + +We have added two new menus when you right-click on a *.properties file in the Explorer view: + +* Create Kubernetes Config Map from File +* Create Kubernetes Secret from File + +In both cases, you are asked for the name of your new ConfigMap or Secret. The name must start with a letter and contain no spaces, but can use numbers or hyphens (i.e. "my-confg-map" is valid but "my config map" is not). Once given a valid name, the action will create your new ConfigMap or Secret that you can reference in your Apache Camel-K route. + +![Kubernetes ConfigMap List](images/kubernetes-secret-name-command.jpg) + +See [Configuration via ConfigMap or Secret](https://camel.apache.org/staging/camel-k/latest/configuration/configmap-secret.html) in the Apache Camel-K documentation for more details. + +## Running with Kubernetes ConfigMaps or Secrets + +If you select the "ConfigMap - Apache Camel-K Integration with Kubernetes ConfigMap" or "Secret - Apache Camel-K Integration with Kubernetes Secret" you will be presented with a list of the published ConfigMaps or Secrets in your current Kubernetes system. Select the one you want to use with your integration and it will run accordingly. + +![Kubernetes ConfigMap List](images/kubernetes-configmap-list.jpg) + +## Output Channels There are two types of "output channels" providing details for the extension. -* The "Camel-K" output channel (View->Output, select "Camel-K" from the drop-down in the view) offers details about events such as when the Camel-K Integrations view is refreshed, when new integrations are started, when running integrations are stopped, and when the log of a particular running integration is viewed. -* In that last case, the "Follow log for Camel-K Integration" menu, when invoked on a running integration in the Camel-K Integrations view, opens a new Output channel named for the running "pod" associated with that particular integration. This gives you access to the running Camel log for the selected integration. +* The "Apache Camel K" output channel (View->Output, select "Apache Camel K" from the drop-down in the view) offers details about events such as when the Apache Camel K Integrations view is refreshed, when new integrations are started, when running integrations are stopped, and when the log of a particular running integration is viewed. +* In that last case, the "Follow log for Apache Camel K Integration" menu, when invoked on a running integration in the Apache Camel K Integrations view, opens a new Output channel named for the running "pod" associated with that particular integration. This gives you access to the running Camel log for the selected integration. -## Stopping running Camel-K integrations +## Stopping running Apache Camel K integrations -Once an integration is running, it may be stopped in the "Camel-K Integrations" view by right-clicking on the integration and selecting "Remove Camel-K Integration." Stopping an integration removes its associated output channel. +Once an integration is running, it may be stopped in the "Apache Camel K Integrations" view by right-clicking on the integration and selecting "Remove Apache Camel K Integration." Stopping an integration removes its associated output channel. -"Stop Camel-K Integration" essentially calls `kamel delete '${filename}'` (or the equivalent call in the Kubernetes Rest API) to stop the running integration in the system. +"Remove Apache Camel K Integration" essentially calls `kamel delete '${filename}'` (or the equivalent call in the Kubernetes Rest API) to stop the running integration in the system. -## Camel-K Integrations view +## Apache Camel K Integrations view -The Camel-K Integrations view offers a list of the "integrations" registered with the current Camel-K context. If you right-click on a running integration, you can "Remove" an integration to stop them in the system or "Follow" the log to show the running log for your integration in a new Output channel. +The Apache Camel K Integrations view offers a list of the "integrations" registered with the current Apache Camel K context. If you right-click on a running integration, you can "Remove" an integration to stop them in the system or "Follow" the log to show the running log for your integration in a new Output channel. -![Camel-K integrations view Remove](images/camelk-integrations-view-remove-menu.jpg) +![Apache Camel K integrations view Remove](images/camelk-integrations-view-remove-menu.jpg) -Following a log opens a new Output channel named for the running Kubernetes pod where the integration is running. It updates as new data is added to it: +Following a log opens a new Output channel named for the running Kubernetes pod where the integration is running. It updates as new data is added: -![Camel-K integrations view Log](images/camelk-integrations-view-integrations-log.jpg) +![Apache Camel K integrations view Log](images/camelk-integrations-view-integrations-log.jpg) In addition, the view has a "Refresh" button that can be used to manually trigger a refresh of the list, but when you add/remove file-based integrations in the Explorer view, it should refresh automatically. Note: Refreshing the view sometimes is delayed as we wait for pods to start. You may need to give it a few seconds. If nothing happens, the Refresh button is a good option. -![Camel-K integrations view Refresh](images/camelk-integrations-view-refresh-action.jpg) +![Apache Camel K integrations view Refresh](images/camelk-integrations-view-refresh-action.jpg) Integrations in the list are decorated with a little dot indicating the status of the deployment. If green, the integration is in a "Running" state. If red, it is in other states such as "Building Kit" or "Error." And if you hover over the integration, the tooltip will show the status. -![Camel-K integrations view Status tooltip](images/camelk-integrations-view-status-tooltip.jpg) +![Apache Camel K integrations view Status tooltip](images/camelk-integrations-view-status-tooltip.jpg) -### Camel-K Status Bar Messages +### Apache Camel K Status Bar Messages -While events are occurring such as the Camel-K Integrations view is being refreshed or a new Integration is being deployed, a status bar message will appear to offer an indication. This can be disabled in the extension settings. +While events are occurring such as the Apache Camel K Integrations view is being refreshed or a new Integration is being deployed, a status bar message will appear to offer an indication. This can be disabled in the extension settings. The extension shows status messages when: @@ -88,25 +121,25 @@ The extension shows status messages when: * Starting to follow an integration log * Starting a local Kubernetes proxy instance -![Camel-K integrations Status Bar](images/camelk-integrations-status-bar.jpg) +![Apache Camel K integrations Status Bar](images/camelk-integrations-status-bar.jpg) -## Camel-K Extension Settings +## Apache Camel K Extension Settings -To access the new extension settings, go to File->Preferences->Settings, then select "Extensions" and finally "Camel-K Integration Settings." +To access the new extension settings, go to File->Preferences->Settings, then select "Extensions" and finally "Apache Camel K Tooling Extension Settings." -![Camel-K integrations view Settings](images/camelk-integrations-view-settings.jpg) +![Apache Camel K Extension Settings](images/camelk-integrations-view-settings.jpg) * Proxy Namespace - Currently this drop-down has two values: default and syndesis. * Proxy Port - Corresponds to the port of the Proxy URL to be used for accessing Kubernetes via Rest APIs. Defaults to 8000. * Proxy URL - This setting corresponds to the server proxy url for your Kubernetes service. It defaults to http://localhost, but can be altered to any appropriate service URL. It is combined with the port to construct URLs at runtime when starting the proxy and using proxy calls. (See [Use an HTTP Proxy to Access the Kubernetes API)[https://kubernetes.io/docs/tasks/access-kubernetes-api/http-proxy-access-api/] for details on how to create the local proxy (i.e. 'kubectl proxy –port=8000') and "Starting a local Kubernetes proxy" below.) -* Show Status Bar Messages - Indicates whether to show messages in the status bar to indicate when the system is updating, such as when the Camel-K Integrations view is being refreshed or a new Integration is being deployed. -* Use Proxy - This setting determines whether the Camel-K Integrations view retrieves the list of running integrations via the local 'kubectl' application or via the Kubernetes Rest API and calls through the Proxy URL/Namespace combination above. The ultimate URL becomes [proxyurl]/apis/camel.apache.org/v1alpha1/namespaces/[namespace]/integrations. +* Show Status Bar Messages - Indicates whether to show messages in the status bar to indicate when the system is updating, such as when the Camel K Integrations view is being refreshed or a new Integration is being deployed. +* Use Proxy - This setting determines whether the Camel K Integrations view retrieves the list of running integrations via the local 'kubectl' application or via the Kubernetes Rest API and calls through the Proxy URL/Namespace combination above. The ultimate URL becomes [proxyurl]/apis/camel.apache.org/v1alpha1/namespaces/[namespace]/integrations. ## Starting a local Kubernetes proxy (Only available with the Minikube executable installed, but useful for local development.) -We have created a new command available in the command palette (Ctrl+Shift+P or F1) called "Camel-K: Start the kubectl proxy server." This will start a new Kubernetes proxy using the Minikube executable ('kubectl proxy --port=8000) and refreshes the Camel-K Integrations view immediately. This command creates a local proxy at the URL composed of the Proxy URL and Proxy Port set in the settings. For example, with the defaults the proxy URL becomes http://localhost:8000. +We have created a new command available in the command palette (Ctrl+Shift+P or F1) called "Apache Camel K: Start the Kubernetes proxy server." This will start a new Kubernetes proxy using the Minikube executable ('kubectl proxy --port=8000) and refreshes the Apache Camel K Integrations view immediately. This command creates a local proxy at the URL composed of the Proxy URL and Proxy Port set in the settings. For example, with the defaults the proxy URL becomes http://localhost:8000. ## Known Issues diff --git a/images/camelk-integrations-status-bar.jpg b/images/camelk-integrations-status-bar.jpg index 57fa6251b..8d8826f77 100644 Binary files a/images/camelk-integrations-status-bar.jpg and b/images/camelk-integrations-status-bar.jpg differ diff --git a/images/camelk-integrations-view-integrations-log.jpg b/images/camelk-integrations-view-integrations-log.jpg index 4fee7d99a..f69246a2e 100644 Binary files a/images/camelk-integrations-view-integrations-log.jpg and b/images/camelk-integrations-view-integrations-log.jpg differ diff --git a/images/camelk-integrations-view-refresh-action.jpg b/images/camelk-integrations-view-refresh-action.jpg index eb4386cab..cff84a9a3 100644 Binary files a/images/camelk-integrations-view-refresh-action.jpg and b/images/camelk-integrations-view-refresh-action.jpg differ diff --git a/images/camelk-integrations-view-remove-menu.jpg b/images/camelk-integrations-view-remove-menu.jpg index 30300e004..00483cae2 100644 Binary files a/images/camelk-integrations-view-remove-menu.jpg and b/images/camelk-integrations-view-remove-menu.jpg differ diff --git a/images/camelk-integrations-view-settings.jpg b/images/camelk-integrations-view-settings.jpg index bb0987382..f84a81838 100644 Binary files a/images/camelk-integrations-view-settings.jpg and b/images/camelk-integrations-view-settings.jpg differ diff --git a/images/camelk-integrations-view-status-tooltip.jpg b/images/camelk-integrations-view-status-tooltip.jpg index eb6292e55..d68069d77 100644 Binary files a/images/camelk-integrations-view-status-tooltip.jpg and b/images/camelk-integrations-view-status-tooltip.jpg differ diff --git a/images/camelk-start-integration-dropdown.jpg b/images/camelk-start-integration-dropdown.jpg new file mode 100644 index 000000000..29b55e172 Binary files /dev/null and b/images/camelk-start-integration-dropdown.jpg differ diff --git a/images/kubernetes-configmap-list.jpg b/images/kubernetes-configmap-list.jpg new file mode 100644 index 000000000..e46ac2b10 Binary files /dev/null and b/images/kubernetes-configmap-list.jpg differ diff --git a/images/kubernetes-secret-name-command.jpg b/images/kubernetes-secret-name-command.jpg new file mode 100644 index 000000000..ca10499b6 Binary files /dev/null and b/images/kubernetes-secret-name-command.jpg differ diff --git a/images/kubernetes-view-camelk-hello-xml.jpg b/images/kubernetes-view-camelk-hello-xml.jpg index 7e673b8cb..c73124d83 100644 Binary files a/images/kubernetes-view-camelk-hello-xml.jpg and b/images/kubernetes-view-camelk-hello-xml.jpg differ diff --git a/images/kubernetes-view-camelk-run-xml-menu.jpg b/images/kubernetes-view-camelk-run-xml-menu.jpg index e23a8760b..5fbb290cd 100644 Binary files a/images/kubernetes-view-camelk-run-xml-menu.jpg and b/images/kubernetes-view-camelk-run-xml-menu.jpg differ diff --git a/package-lock.json b/package-lock.json index 05ef54914..3aa78e74b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1279,6 +1279,11 @@ "vscode-test": "^0.4.1" } }, + "vscode-kubernetes-tools-api": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vscode-kubernetes-tools-api/-/vscode-kubernetes-tools-api-1.0.0.tgz", + "integrity": "sha512-f9nmtYMZ32Dsmr4r0LoJ/JRl//gMsXqjYujOFQJg/dbIIFQMrUKOL+8+EWAz1h8rqgvMsj1NatSIThUFNlL65A==" + }, "vscode-test": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.1.tgz", diff --git a/package.json b/package.json index 1abb5d88d..d2d37ab01 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-camelk", - "displayName": "Tooling for Apache Camel-K", - "description": "VS Code extensions to support Camel-K functionality", + "displayName": "Tooling for Apache Camel K", + "description": "VS Code support for Apache Camel K functionality", "license": "Apache-2.0", "version": "0.0.6", "publisher": "redhat", @@ -25,6 +25,7 @@ "homepage": "https://github.com/camel-tooling/vscode-camelk", "keywords": [ "Camel", + "Camel K", "Camel-K", "camelk", "kamel", @@ -43,24 +44,24 @@ "contributes": { "configuration": [ { - "title": "Camel-K Integration Settings", + "title": "Apache Camel K Tooling Extension Settings", "properties": { "camelk.integrations.useProxy": { "type": "boolean", "default": false, - "description": "Always use proxy: If true, always use the proxy URL instead of Minikube", + "description": "Always use proxy: If true, always use the proxy URL instead of kubectl", "scope": "window" }, "camelk.integrations.proxyURL": { "type": "string", "default": "http://localhost", - "description": "Proxy configuration: URL to the running Kube server (to be combined with port)", + "description": "Proxy configuration: URL to the running Kubernetes server (to be combined with port)", "scope": "window" }, "camelk.integrations.proxyPort": { "type": "number", "default": 8000, - "description": "Proxy configuration: Port to the running Kube server (combined with proxy URL)", + "description": "Proxy configuration: Port to the running Kubernetes server (combined with proxy URL)", "scope": "window" }, "camelk.integrations.proxyNamespace": { @@ -70,7 +71,7 @@ "syndesis" ], "default": "default", - "description": "Proxy namespace: Namespace to use for accessing the Camel-K integrations", + "description": "Proxy namespace: Namespace to use for accessing Apache Camel K integrations", "scope": "window" }, "camelk.integrations.showStatusBarMessages": { @@ -85,11 +86,11 @@ "commands": [ { "command": "camelk.startintegration", - "title": "Start Camel-K Integration" + "title": "Start Apache Camel K Integration" }, { "command": "camelk.integrations.refresh", - "title": "Refresh Integration List", + "title": "Refresh Apache Camel K Integration List", "icon": { "dark": "resources/dark/refresh.svg", "light": "resources/light/refresh.svg" @@ -97,23 +98,59 @@ }, { "command": "camelk.integrations.remove", - "title": "Remove Camel-K Integration" + "title": "Remove Apache Camel K Integration" }, { "command": "camelk.integrations.log", - "title": "Follow log for Camel-K Integration" + "title": "Follow log for running Apache Camel K Integration" }, { "command": "camelk.integrations.startproxy", - "title": "Camel-K: Start the kubectl proxy server" + "title": "Apache Camel K: Start the Kubernetes proxy server" + }, + { + "command": "camelk.integrations.createconfigmapfromfile", + "title": "Create Kubernetes Config Map From File" + }, + { + "command": "camelk.integrations.createconfigmapfromfolder", + "title": "Create Kubernetes Config Map From all Files in Folder" + }, + { + "command": "camelk.integrations.createsecretfromfile", + "title": "Create Kubernetes Secret From File" + }, + { + "command": "camelk.integrations.createsecretfromfolder", + "title": "Create Kubernetes Secret From all Files in Folder" } ], "menus": { "explorer/context": [ { "command": "camelk.startintegration", - "when": "resourceExtname =~ /\\.(groovy|java|xml|js)$/", - "group": "navigation" + "when": "resourceExtname =~ /\\.(groovy|java|xml|js|kts)$/", + "group": "camelk.group" + }, + { + "command": "camelk.integrations.createconfigmapfromfile", + "when": "resourceExtname =~ /\\.(properties)$/", + "group": "camelk.group" + }, + { + "command": "camelk.integrations.createconfigmapfromfolder", + "when": "explorerResourceIsFolder", + "group": "camelk.group" + }, + { + "command": "camelk.integrations.createsecretfromfile", + "when": "resourceExtname =~ /\\.(properties)$/", + "group": "camelk.group" + }, + { + "command": "camelk.integrations.createsecretfromfolder", + "when": "explorerResourceIsFolder", + "group": "camelk.group" } ], "view/title": [ @@ -140,7 +177,7 @@ "explorer": [ { "id": "camelk.integrations", - "name": "Camel-K Integrations" + "name": "Apache Camel K Integrations" } ] } @@ -174,6 +211,7 @@ "child_process": "^1.0.2", "path": "^0.12.7", "request": "^2.88.0", - "request-promise": "^4.2.4" + "request-promise": "^4.2.4", + "vscode-kubernetes-tools-api": "^1.0.0" } } diff --git a/src/CamelKNodeProvider.ts b/src/CamelKNodeProvider.ts index b4b99e707..101dded2f 100644 --- a/src/CamelKNodeProvider.ts +++ b/src/CamelKNodeProvider.ts @@ -43,7 +43,7 @@ export class CamelKNodeProvider implements vscode.TreeDataProvider { this.treeNodes = []; } - // set up so we don't pollute test runs with camel-k integrations + // set up so we don't pollute test runs with camel k integrations public setRetrieveIntegrations(flag:boolean) { this.retrieveIntegrations = flag; } @@ -82,70 +82,72 @@ export class CamelKNodeProvider implements vscode.TreeDataProvider { // trigger a refresh event in VSCode public async refresh(): Promise { - let oldCount = this.treeNodes.length; - extension.setStatusLineMessage(`Refreshing Camel-K Integrations view...`); - this.resetList(); - let inaccessible = false; - if (this.retrieveIntegrations) { - let retryTries = 1; - let numRetries = 10; - while (retryTries < numRetries && !inaccessible) { - this.resetList(); + return new Promise( async (resolve, reject) => { + let oldCount = this.treeNodes.length; + extension.setStatusLineMessage(`Refreshing Apache Camel K Integrations view...`); + this.resetList(); + let inaccessible = false; + if (this.retrieveIntegrations) { + let retryTries = 1; + let numRetries = 10; + while (retryTries < numRetries && !inaccessible) { + this.resetList(); - if (!this.useProxy) { - await utils.pingKamel() - .then( async () => { - await this.getIntegrationsFromCamelK().then((output) => { - this.processIntegrationList(output); - }).catch((error) => { - let errMsg : string = error; - if (errMsg.toLowerCase().trim().startsWith('error:')) { - utils.shareMessage(extension.mainOutputChannel, `Refreshing Camel-K Integrations view using kubectl failed. ${error}`); - inaccessible = true; - } - Promise.reject(); + if (!this.useProxy) { + await utils.pingKamel() + .then( async () => { + await this.getIntegrationsFromCamelK().then((output) => { + this.processIntegrationList(output); + }).catch((error) => { + let errMsg : string = error; + if (errMsg.toLowerCase().trim().startsWith('error:')) { + utils.shareMessage(extension.mainOutputChannel, `Refreshing Apache Camel K Integrations view using kubectl failed. ${error}`); + inaccessible = true; + } + reject(); + return; + }); + }).catch( (error) => { + utils.shareMessage(extension.mainOutputChannel, `Refreshing Apache Camel K Integrations view using kubectl failed. ${error}`); + inaccessible = true; + reject(); return; }); - }).catch( (error) => { - utils.shareMessage(extension.mainOutputChannel, `Refreshing Camel-K Integrations view using kubectl failed. ${error}`); - inaccessible = true; - Promise.reject(); - return; - }); - } else { - await utils.pingKubernetes().then( async () => { - await this.getIntegrationsFromCamelKRest().then((output) => { - this.processIntegrationListFromJSON(output); - }).catch((error) => { - utils.shareMessage(extension.mainOutputChannel, `Refreshing Camel-K Integrations view using kubernetes Rest APIs failed. ${error}`); + } else { + await utils.pingKubernetes().then( async () => { + await this.getIntegrationsFromCamelKRest().then((output) => { + this.processIntegrationListFromJSON(output); + }).catch((error) => { + utils.shareMessage(extension.mainOutputChannel, `Refreshing Apache Camel K Integrations view using kubernetes Rest APIs failed. ${error}`); + inaccessible = true; + reject(); + return; + }); + }).catch( (error) => { + utils.shareMessage(extension.mainOutputChannel, `Refreshing Apache Camel K Integrations view using kubernetes Rest APIs failed. ${error}`); inaccessible = true; - Promise.reject(); + reject(); return; }); - }).catch( (error) => { - utils.shareMessage(extension.mainOutputChannel, `Refreshing Camel-K Integrations view using kubernetes Rest APIs failed. ${error}`); - inaccessible = true; - Promise.reject(); - return; - }); - } - if (inaccessible) { - break; - } - let newCount = this.treeNodes.length; - if (newCount !== oldCount) { - break; + } + if (inaccessible) { + break; + } + let newCount = this.treeNodes.length; + if (newCount !== oldCount) { + break; + } + retryTries++; } - retryTries++; } - } - extension.hideStatusLine(); - this._onDidChangeTreeData.fire(); - Promise.resolve(); - let newCount = this.treeNodes.length; - if (newCount === 0 && !inaccessible) { - utils.shareMessage(extension.mainOutputChannel, "Refreshing Camel-K Integrations view succeeded, no published integrations available."); - } + extension.hideStatusLine(); + this._onDidChangeTreeData.fire(); + resolve(); + let newCount = this.treeNodes.length; + if (newCount === 0 && !inaccessible) { + utils.shareMessage(extension.mainOutputChannel, "Refreshing Apache Camel K Integrations view succeeded, no published integrations available."); + } + }); } getTreeItem(node: TreeNode): vscode.TreeItem { @@ -206,7 +208,7 @@ export class CamelKNodeProvider implements vscode.TreeDataProvider { } } - // retrieve the list of integrations running in camel-k using the kube proxy and rest API + // retrieve the list of integrations running in camel k using the kube proxy and rest API getIntegrationsFromCamelKRest(): Promise { return new Promise( async (resolve, reject) => { let proxyURL = utils.createCamelKRestURL(); @@ -232,7 +234,7 @@ export class CamelKNodeProvider implements vscode.TreeDataProvider { }); } - // actually retrieve the list of integrations running in camel-k using kubectl + // actually retrieve the list of integrations running in camel k using kubectl getIntegrationsFromCamelK(): Promise { return new Promise( (resolve, reject) => { let commandString = 'kubectl get integration'; diff --git a/src/ConfigMapAndSecrets.ts b/src/ConfigMapAndSecrets.ts new file mode 100644 index 000000000..f4c7f490b --- /dev/null +++ b/src/ConfigMapAndSecrets.ts @@ -0,0 +1,234 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +import * as vscode from 'vscode'; +import * as k8s from 'vscode-kubernetes-tools-api'; +import * as extension from './extension'; +import * as utils from './CamelKJSONUtils'; + +export const validNameRegex = /^[A-Za-z][A-Za-z0-9\-]*(?:[A-Za-z0-9]$){1}/; + +export function registerCommands() { + + // create the integration view action -- create new configmap from file or folder + vscode.commands.registerCommand('camelk.integrations.createconfigmapfromfile', async (uri:vscode.Uri) => { + await createConfigMapFromUri(uri); + }); + vscode.commands.registerCommand('camelk.integrations.createconfigmapfromfolder', async (uri:vscode.Uri) => { + await createConfigMapFromUri(uri); + }); + + // create the integration view action -- create new secret from file or folder + vscode.commands.registerCommand('camelk.integrations.createsecretfromfile', async (uri:vscode.Uri) => { + await createSecretFromUri(uri); + }); + vscode.commands.registerCommand('camelk.integrations.createsecretfromfolder', async (uri:vscode.Uri) => { + await createSecretFromUri(uri); + }); +} + +async function createConfigMapFromUri(uri:vscode.Uri) { + await createConfigMapFromFilenameOrFolder(uri) + .then ( (output) => { + extension.setStatusLineMessage(`Received... ${output}`); + }).catch( (error) => { + utils.shareMessage(extension.mainOutputChannel, ("Error encountered while creating Kubernetes ConfigMap: " + error)); + }); + extension.hideStatusLine(); +} + +async function createSecretFromUri(uri:vscode.Uri) { + await createSecretFromFilenameOrFolder(uri) + .then ( (output) => { + extension.setStatusLineMessage(`Received... ${output}`); + }).catch( (error) => { + utils.shareMessage(extension.mainOutputChannel, ("Error encountered while creating Kubernetes Secret: " + error)); + }); + extension.hideStatusLine(); +} + +function validateName(text : string) { + return !validNameRegex.test(text) ? 'Name must be at least two characters long, start with a letter, and only include a-z, A-Z, and hyphens' : null; +} + +async function createConfigMapFromFilenameOrFolder(uri:vscode.Uri) : Promise { + return new Promise( async (resolve, reject) => { + const kubeIsReady = await isKubernetesAvailable(); + if (kubeIsReady && kubeIsReady === true) { + await vscode.window.showInputBox({ + prompt: 'Kubernetes ConfigMap Name', + placeHolder: 'Provide the unique identifier for this Kubernetes ConfigMap.', + value: 'new-configmap', + ignoreFocusOut: true, + validateInput: validateName + }).then( (configMapName) => { + if (configMapName) { + createConfigMap(configMapName, uri) + .then( (output) => { + vscode.window.showInformationMessage(`Successfully created new Kubernetes ConfigMap named "${configMapName}"`); + resolve(output); + }) + .catch(err => { + vscode.window.showErrorMessage(`Problem encountered creating new Kubernetes ConfigMap named "${configMapName}": ${err}`); + reject(err); + }); + } else { + reject("No name given for Kubernetes ConfigMap."); + } + }); + } else { + vscode.window.showInformationMessage(`Kubernetes extensions still coming up, please wait a moment and try again.`); + reject("Kubernetes not available."); + } + }); +} + +async function createSecretFromFilenameOrFolder(uri:vscode.Uri) : Promise { + return new Promise( async (resolve, reject) => { + const kubeIsReady = await isKubernetesAvailable(); + if (kubeIsReady && kubeIsReady === true) { + let secretName = await vscode.window.showInputBox({ + prompt: 'Kubernetes Secret Name', + placeHolder: 'Provide the unique identifier for this Kubernetes secret.', + value: 'new-secret', + validateInput: validateName + }); + if (secretName) { + createSecret(secretName, uri) + .then( (output) => { + vscode.window.showInformationMessage(`Successfully created new Kubernetes Secret named "${secretName}"`); + resolve(output); + }) + .catch(err => { + vscode.window.showErrorMessage(`Problem encountered creating new Kubernetes Secret named "${secretName}": ${err}`); + reject(err); + }); + } else { + reject("No name given for secret."); + } + } else { + vscode.window.showInformationMessage(`Kubernetes extensions still coming up, please wait a moment and try again.`); + reject("Kubernetes not available."); + } + }); +} + +async function isKubernetesAvailable(): Promise { + return new Promise( async (resolve) => { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl && kubectl.available) { + resolve(true); + } else { + resolve(false); + } + }); +} + +async function createConfigMap(name:string, filename: vscode.Uri) : Promise { + return new Promise( async (resolve, reject) => { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl.available) { + let nameStr = ` --from-file="${filename.fsPath}"`; + const result = await kubectl.api.invokeCommand(`create configmap ${name} ${nameStr}`); + if (!result || result.code !== 0) { + const error = result ? result.stderr : 'Unable to invoke kubectl'; + reject(new Error(error)); + return; + } + if (result && result.code === 0) { + resolve (result.stdout); + return; + } + } else { + reject('Kubernetes not available'); + } + }); +} + +async function createSecret(name:string, foldername: vscode.Uri) : Promise { + return new Promise( async (resolve, reject) => { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl.available) { + let nameStr = ` --from-file="${foldername.fsPath}"`; + const result = await kubectl.api.invokeCommand(`create secret generic ${name} ${nameStr}`); + if (!result || result.code !== 0) { + const error = result ? result.stderr : 'Unable to invoke kubectl'; + reject(new Error(error)); + return; + } + if (result && result.code === 0) { + resolve (result.stdout); + return; + } + } else { + reject('Kubernetes not available'); + } + }); +} + +async function getNamedListFromKubernetes( itemType : string): Promise { + return new Promise( async (resolve, reject) => { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl.available) { + const result = await kubectl.api.invokeCommand(`get ${itemType}`); + if (!result || result.code !== 0) { + const error = result ? result.stderr : `Unable to invoke kubectl to retrieve ${itemType}`; + reject(new Error(error)); + } else if (result) { + const splitResults = result.stdout; + const itemList : string[] = parseShellResult(splitResults); + resolve(itemList); + } + } else { + reject('Kubernetes not available'); + } + }); +} + +export async function getConfigMaps(): Promise { + return getNamedListFromKubernetes('configmap'); +} + +export async function getSecrets(): Promise { + return getNamedListFromKubernetes('secret'); +} + +export function parseShellResult(output: string) : string[] { + let processedList : string[] = ['']; + if (output) { + let lines = output.split('\n'); + for (let entry of lines) { + let line = entry.split(' '); + let cleanLine = []; + for (var i=0; i < line.length; i++) { + if (line[i].trim().length === 0) { + continue; + } + cleanLine.push(line[i].trim()); + } + let firstString : string = cleanLine[0]; + if (firstString === undefined || firstString.toUpperCase().startsWith('NAME') || firstString.trim().length === 0) { + continue; + } + + let itemName = cleanLine[0]; + processedList.push(itemName); + } + } + return processedList; +} \ No newline at end of file diff --git a/src/IntegrationUtils.ts b/src/IntegrationUtils.ts new file mode 100644 index 000000000..07faf93e0 --- /dev/null +++ b/src/IntegrationUtils.ts @@ -0,0 +1,130 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +import * as vscode from 'vscode'; +import * as extension from './extension'; +import * as utils from './CamelKJSONUtils'; +import * as path from 'path'; +import * as child_process from 'child_process'; +import { getConfigMaps, getSecrets } from './ConfigMapAndSecrets'; +import * as k8s from 'vscode-kubernetes-tools-api'; + +const basicIntegration : string = 'Basic - Apache Camel K Integration (no ConfigMap or Secret)'; +const configMapIntegration : string = 'ConfigMap - Apache Camel K Integration with Kubernetes ConfigMap'; +const secretIntegration : string = 'Secret - Apache Camel K Integration with Kubernetes Secret'; + +export async function startIntegration(context: vscode.Uri): Promise { + return new Promise ( async (resolve, reject) => { + await vscode.window.showQuickPick([ basicIntegration, configMapIntegration, secretIntegration ], + { + placeHolder: 'Select the type of Apache Camel K Integration' + }).then( async (choice) => { + let selectedConfigMap = undefined; + let selectedSecret = undefined; + + switch (choice) { + case configMapIntegration: + await getConfigMaps().then( async (configMaps) => { + selectedConfigMap = await vscode.window.showQuickPick(configMaps, { + placeHolder: 'Select an available Kubernetes ConfigMap or ESC to cancel' + }); + }); + break; + case secretIntegration: + await getSecrets().then( async (secrets) => { + selectedSecret = await vscode.window.showQuickPick(secrets, { + placeHolder: 'Select an available Kubernetes Secret or ESC to cancel' + }); + }); + break; + case basicIntegration: + // do nothing with config-map or secret + break; + } + + if (!choice) { + reject(new Error('No integration selection made.')); + return; + } + + await createNewIntegration(context, selectedConfigMap, selectedSecret) + .then( success => { + if (!success) { + reject(false); + } + resolve(true); + }) + .catch(err => { + reject(err); + }); + }); + }); +} + +// use command-line "kamel" utility to start a new integration +function createNewIntegration(integrationFileUri: vscode.Uri, configmap? : string, secret? : string): Promise { + return new Promise( async (resolve, reject) => { + let filename = integrationFileUri.fsPath; + let foldername = path.dirname(filename); + let absoluteRoot = path.parse(filename).base; + let rootName = absoluteRoot.split('.', 1)[0]; + let integrationName = utils.toKebabCase(rootName); + utils.shareMessage(extension.mainOutputChannel, `Deploying file ${absoluteRoot} as integration ${integrationName}`); + await extension.removeOutputChannelForIntegrationViaKubectl(integrationName) + .catch( (error) => { + // this is not a hard stop, it just means there was no output channel to close + console.error(error); + }); + + let commandString = 'kamel run'; + if (configmap && configmap.trim().length > 0) { + commandString += ` --configmap=${configmap}`; + } + if (secret && secret.trim().length > 0) { + commandString += ` --secret=${secret}`; + } + commandString += ' "' + absoluteRoot + '"'; + console.log(`commandString = ${commandString}`); + let runKubectl = child_process.exec(commandString, { cwd : foldername}); + runKubectl.stdout.on('data', function (data) { + resolve(true); + return; + }); + runKubectl.stderr.on('data', function (data) { + utils.shareMessage(extension.mainOutputChannel, `Error deploying ${integrationName}: ${data}`); + reject(false); + return; + }); + }); +} + +export async function isCamelKAvailable(): Promise { + return new Promise( async (resolve) => { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl.available) { + const result = await kubectl.api.invokeCommand('api-versions'); + if (!result || result.code !== 0) { + resolve(false); + } else if (result) { + let foundCamel : boolean = result.stdout.includes("camel.apache.org/v1alpha1"); + resolve(foundCamel); + } + } + resolve(false); + }); +} diff --git a/src/extension.ts b/src/extension.ts index 8dad71daa..140caf6a5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,6 +23,8 @@ import { CamelKNodeProvider, TreeNode } from './CamelKNodeProvider'; import * as utils from './CamelKJSONUtils'; import * as rp from 'request-promise'; import {platform} from 'os'; +import * as configmapsandsecrets from './ConfigMapAndSecrets'; +import * as integrationutils from './IntegrationUtils'; export let mainOutputChannel: vscode.OutputChannel; export let myStatusBarItem: vscode.StatusBarItem; @@ -34,7 +36,7 @@ let curlOption : string = '-c'; let proxyPort : number; let showStatusBar : boolean; -// This extension offers basic integration with Camel-K (https://github.com/apache/camel-k) on two fronts. +// This extension offers basic integration with Camel K (https://github.com/apache/camel-k) on two fronts. export function activate(context: vscode.ExtensionContext) { @@ -81,7 +83,7 @@ export function activate(context: vscode.ExtensionContext) { proxyPort = proxyPortTemp; }); - mainOutputChannel = vscode.window.createOutputChannel("Camel-K"); + mainOutputChannel = vscode.window.createOutputChannel("Apache Camel K"); mainOutputChannel.show(); myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); @@ -96,7 +98,7 @@ export function activate(context: vscode.ExtensionContext) { // create the integration view action -- remove vscode.commands.registerCommand('camelk.integrations.remove', async (node: TreeNode) => { if (node && node.label) { - setStatusLineMessage(`Removing Camel-K Integration...`); + setStatusLineMessage(`Removing Apache Camel K Integration...`); let integrationName : string = node.label; if (useProxy) { utils.shareMessage(mainOutputChannel, 'Removing ' + integrationName + ' via Kubernetes Rest Delete'); @@ -128,7 +130,7 @@ export function activate(context: vscode.ExtensionContext) { // create the integration view action -- start log vscode.commands.registerCommand('camelk.integrations.log', async (node: TreeNode) => { if (node && node.label) { - setStatusLineMessage(`Retrieving Camel-K Integration log...`); + setStatusLineMessage(`Retrieving log for running Apache Camel K Integration...`); let integrationName : string = node.label; if (useProxy) { utils.shareMessage(mainOutputChannel, 'Connecting to the log for ' + integrationName + ' via Kubernetes Rest'); @@ -201,6 +203,9 @@ export function activate(context: vscode.ExtensionContext) { let startIntegration = vscode.commands.registerCommand('camelk.startintegration', async (uri:vscode.Uri) => { await runTheFile(uri);}); context.subscriptions.push(startIntegration); + // add commands to create config-map and secret objects from .properties files + configmapsandsecrets.registerCommands(); + // populate the initial tree camelKIntegrationsProvider.refresh(); } @@ -216,7 +221,6 @@ export function hideStatusLine() { if (myStatusBarItem) { myStatusBarItem.hide(); } - } // find an output channel by name in the map and remove it @@ -266,6 +270,8 @@ function findThePODNameForIntegrationFromJSON(json : Object, integrationName : s } } reject(new Error()); + } else { + reject("JSON not returned from Kubernetes Rest call"); } }); } @@ -359,7 +365,7 @@ async function stopIntegrationViaRest(integrationName: string) : Promise{ // start an integration from a file function startIntegration(context: vscode.Uri): Promise { return new Promise ( async (resolve, reject) => { - setStatusLineMessage(`Starting new Camel-K Integration...`); + setStatusLineMessage(`Starting new Apache Camel K Integration...`); if (useProxy) { utils.shareMessage(mainOutputChannel, "Starting new integration via Kubernetes rest API"); createNewIntegrationViaRest(context) @@ -380,7 +386,7 @@ function startIntegration(context: vscode.Uri): Promise { }); } else { utils.shareMessage(mainOutputChannel, "Starting new integration via Kamel executable."); - createNewIntegration(context) + await integrationutils.startIntegration(context) .then( success => { if (!success) { vscode.window.showErrorMessage("Unable to call Kamel."); @@ -445,7 +451,7 @@ async function removeOutputChannelForIntegrationViaRest(integrationName:string) } // remove the output channel for a running integration via kubectl executable -async function removeOutputChannelForIntegrationViaKubectl(integrationName:string) { +export async function removeOutputChannelForIntegrationViaKubectl(integrationName:string) { await getIntegrationsFromKubectl(integrationName).then( (output) => { let podName = processIntegrationList(output); if (podName) { @@ -454,35 +460,6 @@ async function removeOutputChannelForIntegrationViaKubectl(integrationName:strin }); } -// use command-line "kamel" utility to start a new integration -function createNewIntegration(context: vscode.Uri): Promise { - return new Promise( async (resolve, reject) => { - let filename = context.fsPath; - let foldername = path.dirname(filename); - let absoluteRoot = path.parse(filename).base; - let rootName = absoluteRoot.split('.', 1)[0]; - let integrationName = utils.toKebabCase(rootName); - utils.shareMessage(mainOutputChannel, `Deploying file ${absoluteRoot} as integration ${integrationName}`); - await removeOutputChannelForIntegrationViaKubectl(integrationName) - .catch( (error) => { - // this is not a hard stop, it just means there was no output channel to close - console.error(error); - }); - - let commandString = 'kamel run'; - commandString += ' "' + absoluteRoot + '"'; - console.log(`commandString = ${commandString}`); - let runKubectl = child_process.exec(commandString, { cwd : foldername}); - runKubectl.stdout.on('data', function (data) { - resolve(true); - }); - runKubectl.stderr.on('data', function (data) { - utils.shareMessage(mainOutputChannel, `Error deploying ${integrationName}: ${data}`); - reject(false); - }); - }); -} - // this method is called when your extension is deactivated export function deactivate() { if (mainOutputChannel) { @@ -498,7 +475,7 @@ export function deactivate() { } } -// retrieve the list of integrations running in camel-k starting with the integration name +// retrieve the list of integrations running in camel k starting with the integration name function getIntegrationsFromKubectl(integrationName : string): Promise { return new Promise( (resolve, reject) => { let commandString = 'kubectl get pods | grep ' + integrationName; diff --git a/src/test/camelkjsonutils.test.ts b/src/test/camelkjsonutils.test.ts index 36987bf1a..1090d34d7 100644 --- a/src/test/camelkjsonutils.test.ts +++ b/src/test/camelkjsonutils.test.ts @@ -22,18 +22,22 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as nock from 'nock'; -suite("ensure camelk utilities work as expected", async function() { +suite("ensure camelk utilities work as expected", function() { - test("should be able to stringify existing file", async function() { + test("should be able to stringify existing file", function(done) { let testFilePath = path.join(__dirname, '../../src/test/helloworld.groovy'); utils.stringifyFileContents(testFilePath) .then((text) => { console.log("file results = " + text); assert.ok(text.length > 0); - }).catch(() => assert.fail()); + done(); + }).catch((err) => { + assert.fail(); + done(err); + }); }); - test("should be able to create deploy descriptor for incoming camel file", async function() { + test("should be able to create deploy descriptor for incoming camel file", function(done) { let testFilePath = path.join(__dirname, '../../src/test/helloworld.groovy'); let fileContents:string; utils.stringifyFileContents(testFilePath).then((text) => { @@ -42,98 +46,113 @@ suite("ensure camelk utilities work as expected", async function() { .then((output) => { console.log("deployment output = " + output); assert.ok(output.length > 0); + done(); }); }); }); - test("should be able to construct usable rest URL", async function() { - + test("should be able to construct usable rest URL", function(done) { // have to do some gymnastics to clear the settings for some reason let proxyUrl = vscode.workspace.getConfiguration().get('camelk.integrations.proxyURL'); let proxyPort = vscode.workspace.getConfiguration().get('camelk.integrations.proxyPort') as number; let namespace = vscode.workspace.getConfiguration().get('camelk.integrations.proxyNamespace'); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', 'http://localhost', true); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', 9000, true); // use non-default value - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyNamespace', 'default', true); - - let urlstring = utils.createCamelKRestURL(); - console.log("url output = " + urlstring); - assert.ok(urlstring === "http://localhost:9000/apis/camel.apache.org/v1alpha1/namespaces/default/integrations"); - + vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', 'http://localhost', true) + .then( () => // use non-default value + vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', 9000, true) + .then( () => vscode.workspace.getConfiguration().update('camelk.integrations.proxyNamespace', 'default', true) + .then ( () => { + let urlstring = utils.createCamelKRestURL(); + console.log("url output = " + urlstring); + assert.strictEqual(urlstring, "http://localhost:9000/apis/camel.apache.org/v1alpha1/namespaces/default/integrations"); + }) + ) + ) + // and set them back at the end - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', proxyUrl, true); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', proxyPort, true); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyNamespace', namespace, true); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', proxyUrl, true); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', proxyPort, true); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyNamespace', namespace, true); + done(); }); - test("should be able to construct usable proxy URL", async function() { + test("should be able to construct usable proxy URL", function(done) { // have to do some gymnastics to clear the settings for some reason let proxyUrl = vscode.workspace.getConfiguration().get('camelk.integrations.proxyURL'); let proxyPort = vscode.workspace.getConfiguration().get('camelk.integrations.proxyPort') as number; - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', 'http://localhost', true); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', 9000, true); // use non-default value - - let urlstring = utils.createBaseProxyURL(); - console.log("url output = " + urlstring); - assert.ok(urlstring === "http://localhost:9000"); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', 'http://localhost', true) + .then( () => vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', 9000, true) + .then( () => { + let urlstring = utils.createBaseProxyURL(); + console.log("url output = " + urlstring); + assert.strictEqual(urlstring, "http://localhost:9000"); + }) + ); // and set them back at the end - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', proxyUrl, true); - await vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', proxyPort, true); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyURL', proxyUrl, true); + vscode.workspace.getConfiguration().update('camelk.integrations.proxyPort', proxyPort, true); + + done(); }); - test("should be able to ping accessible server", async function() { - await utils.pingTheURL("http://www.google.com").then( + test("should be able to ping accessible server", function(done) { + utils.pingTheURL("http://www.google.com").then( (result) => { console.log("ping output = " + result); - assert.ok(result === true); + assert.equal(result, true); + done(); } ); }); - test("should be able to fail ping of inaccessible server", async function() { - await utils.pingTheURL("http://www.googleinaccesible.invalidurl").then( + test("should be able to fail ping of inaccessible server", function(done) { + utils.pingTheURL("http://www.googleinaccesible.invalidurl").then( (result) => { assert.fail("Should not have made it here"); + done(result); } ).catch( (error) => { console.log("ping output = " + error); assert.ok(error); + done(); }); }); - test("should be able to ping kubernetes", async function() { + test("should be able to ping kubernetes", function(done) { let proxyURL = utils.createCamelKRestURL(); // use nock to mock the http request nock(proxyURL).get('').reply(200, {}); - await utils.pingKubernetes().then( (rtn) => { + utils.pingKubernetes().then( (rtn) => { assert.equal(rtn, proxyURL); + done(); }); nock.cleanAll(); }); - test("should be able to mock when kubernetes is not available", async function() { + test("should be able to mock when kubernetes is not available", function(done) { let proxyURL = utils.createCamelKRestURL(); // use nock to mock the http request nock(proxyURL).get('').reply(404, {}); - await utils.pingKubernetes().then( (rtn) => { + utils.pingKubernetes().then( (rtn) => { assert.fail("should not have been accessible"); + done(rtn); }).catch( (error) => { assert.ok(error, "valid failure here"); + done(); }); nock.cleanAll(); }); - test("test kebab case utility", async function() { + test("test kebab case utility", function(done) { // based loosely on https://github.com/apache/camel-k/blob/master/pkg/util/kubernetes/sanitize_test.go @@ -144,6 +163,8 @@ suite("ensure camelk utilities work as expected", async function() { assert.equal(utils.toKebabCase('-foo-bar-'), 'foo-bar'); assert.equal(utils.toKebabCase('1foo-bar2'), '1foo-bar2'); assert.equal(utils.toKebabCase('foo-bar-1'), 'foo-bar-1'); + + done(); }); }); \ No newline at end of file diff --git a/src/test/configmapandsecrets.test.ts b/src/test/configmapandsecrets.test.ts new file mode 100644 index 000000000..0ceca50a5 --- /dev/null +++ b/src/test/configmapandsecrets.test.ts @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +import * as configmapandsecrets from '../ConfigMapAndSecrets'; +import * as assert from 'assert'; + +suite("ensure utility methods in configmap and secrets code works as expected", function() { + + function runSetOfNames(inputs: string[], expectedResult : boolean) { + for (let i = 0; i < inputs.length; i++) { + let testString = inputs[i]; + let result = configmapandsecrets.validNameRegex.test(testString); + assert.strictEqual(result, expectedResult, + `Testing name ${testString}. Should be ${expectedResult}. Came back as ${result}`); + } + } + + test("validate name regex working as expected for valid names", function(done) { + const validNames : string[] = [ + 'ab', + 'a-b', + 'a1b', + 'a1' + ]; + runSetOfNames(validNames, true); + done(); + }); + + test("validate name regex working as expected for invalid names", function(done) { + const invalidNames : string[] = [ + ' a', + 'a b', + '1a', + 'a ' + ]; + runSetOfNames(invalidNames, false); + done(); + }); + + test("make sure the console parser works as expected to retrieve list of named items", function(done) { + const data = + "NAME DATA AGE\n" + + "something 1 90m\n" + + "something-else 1 92m"; + const expectedResult : string[] = ['', 'something','something-else']; + let result : string[] = configmapandsecrets.parseShellResult(data); + assert.deepEqual(result, expectedResult, `Did not get expected list of names from console shell results`); + done(); + }); +}); diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index d3add4676..2f855862e 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -19,18 +19,20 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; -suite("ensure camelk extension exists and is accessible", async function() { +suite("ensure camelk extension exists and is accessible", function() { const extensionId = 'redhat.vscode-camelk'; - test('vscode-camelk extension should be present', () => { + test('vscode-camelk extension should be present', function(done) { assert.ok(vscode.extensions.getExtension(extensionId)); + done(); }); - test('vscode-camelk extension should activate', async function () { + test('vscode-camelk extension should activate', function (done) { let extension = vscode.extensions.getExtension(extensionId); if (extension !== null && extension !== undefined) { - return extension.activate().then((api) => { + extension.activate().then((api) => { assert.ok(true); + done(); }); } }); diff --git a/src/test/gocamelkkubernetes.test.ts b/src/test/gocamelkkubernetes.test.ts index f1f024b20..13dc22bf5 100644 --- a/src/test/gocamelkkubernetes.test.ts +++ b/src/test/gocamelkkubernetes.test.ts @@ -31,12 +31,12 @@ import * as child_process from 'child_process'; // this is not the best solution, but it offers a way to ensure that if things change upstream at least we know when the tests fail -suite("ensure that the upstream kubernetes.go sanitize in camel-k have not changed since we checked last", async function() { +suite("ensure that the upstream kubernetes.go sanitize in camel-k have not changed since we checked last", function() { var fileName : string = 'sanitize.go'; var url = 'https://raw.githubusercontent.com/apache/camel-k/master/pkg/util/kubernetes/sanitize.go'; - test("test to see if the sanitize.go file has changed since we stashed it", async function() { + test("test to see if the sanitize.go file has changed since we stashed it", function(done) { var goPath = retrieveSanitizeFileFromUpstream(fileName, url); let stashedFile = path.join(__dirname, '../../src/test/sanitize.go.saved'); @@ -44,7 +44,9 @@ suite("ensure that the upstream kubernetes.go sanitize in camel-k have not chang var str2 = fs.readFileSync(stashedFile, 'utf-8'); str2 = replaceCarriageReturns(str1); + assert.equal(fs.existsSync(goPath), true); assert.strictEqual(str1, str2); + done(); }); }); @@ -60,7 +62,6 @@ function retrieveSanitizeFileFromUpstream(fileName: string, url: string) { console.log(goPath); console.log(commandString); child_process.execSync(commandString, { cwd: os.tmpdir() }); - assert.ok(fs.existsSync(goPath)); return goPath; } diff --git a/src/test/integrationExplorer.test.ts b/src/test/integrationExplorer.test.ts index bef98b534..daa8b3171 100644 --- a/src/test/integrationExplorer.test.ts +++ b/src/test/integrationExplorer.test.ts @@ -38,24 +38,28 @@ suite('Camel-k Integrations View', () => { sandbox.restore(); }); - test('adding a single child should trigger a refresh', async () => { + test('adding a single child should trigger a refresh', function(done) { integrationExplorer.resetList(); const refreshStub = sandbox.stub(integrationExplorer, 'refresh'); - var children = await integrationExplorer.getChildren(); - const newNode = new TreeNode("string", "mockIntegration", "running", vscode.TreeItemCollapsibleState.None); - integrationExplorer.addChild(children, newNode, false); - expect(children.length).equals(1); - expect(children[0].label).equals("mockIntegration"); - expect(refreshStub).calledOnce; + integrationExplorer.getChildren().then( (children) => { + const newNode = new TreeNode("string", "mockIntegration", "running", vscode.TreeItemCollapsibleState.None); + integrationExplorer.addChild(children, newNode, false); + expect(children.length).equals(1); + expect(children[0].label).equals("mockIntegration"); + expect(refreshStub).calledOnce; + done(); + }); }); - test('adding and removing a child should trigger refresh twice', async () => { + test('adding and removing a child should trigger refresh twice', function(done) { integrationExplorer.resetList(); const refreshStub = sandbox.stub(integrationExplorer, 'refresh'); - var children = await integrationExplorer.getChildren(); - const newNode = new TreeNode("string", "mockIntegration", "running", vscode.TreeItemCollapsibleState.None); - integrationExplorer.addChild(children, newNode); - integrationExplorer.removeChild(children, newNode); - expect(refreshStub).calledTwice; + integrationExplorer.getChildren().then( (children) => { + const newNode = new TreeNode("string", "mockIntegration", "running", vscode.TreeItemCollapsibleState.None); + integrationExplorer.addChild(children, newNode); + integrationExplorer.removeChild(children, newNode); + expect(refreshStub).calledTwice; + done(); + }); }); }); \ No newline at end of file