diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 148bfd47a..88195071b 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -52,14 +52,14 @@ jobs: if: (success() || failure()) && runner.os == 'Linux' name: Start cluster with: - version: v0.11.1 + version: v0.20.0 - name: Configure cluster if: (success() || failure()) && runner.os == 'Linux' run: | curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/install.sh | bash -s v0.19.1 kubectl create -f https://operatorhub.io/install/service-binding-operator.yaml - kubectl create -f https://operatorhub.io/install/stable/cloud-native-postgresql.yaml + kubectl create -f https://operatorhub.io/install/cloudnative-pg.yaml nb=0 echo -n "Waiting for operator to show up " while [ "$nb" != "2" ] diff --git a/build/esbuild.mjs b/build/esbuild.mjs index 61f80b937..5e60959b6 100644 --- a/build/esbuild.mjs +++ b/build/esbuild.mjs @@ -28,7 +28,6 @@ const webviews = [ 'feedback', 'serverless-function', 'serverless-manage-repository', - 'add-service-binding', 'openshift-terminal', ]; diff --git a/package.json b/package.json index 2dea8055b..318e2541e 100644 --- a/package.json +++ b/package.json @@ -525,11 +525,6 @@ "title": "Start Dev (manually trigger rebuild)", "category": "OpenShift" }, - { - "command": "openshift.component.binding.add", - "title": "Bind Service", - "category": "OpenShift" - }, { "command": "openshift.component.exitDevMode", "title": "Stop Dev", @@ -1371,10 +1366,6 @@ "command": "openshift.component.openInBrowser", "when": "false" }, - { - "command": "openshift.component.binding.add", - "when": "false" - }, { "command": "openshift.Serverless.openFunction", "when": "false" @@ -1839,11 +1830,6 @@ "when": "view == openshiftComponentsView && viewItem =~ /openshift\\.component.*\\.dep-nrn.*/ || viewItem =~ /openshift\\.component.*\\.dep-run.*/", "group": "c2@1" }, - { - "command": "openshift.component.binding.add", - "when": "view == openshiftComponentsView && viewItem =~ /openshift\\.component.*\\.dev-nrn.*/", - "group": "c2@2" - }, { "command": "openshift.component.showDevTerminal", "when": "view == openshiftComponentsView && viewItem =~ /openshift\\.component.*\\.dev-run.*/", diff --git a/src/helm/helm.ts b/src/helm/helm.ts index fd01fc17f..28e5b4cdc 100644 --- a/src/helm/helm.ts +++ b/src/helm/helm.ts @@ -27,7 +27,11 @@ export type HelmRelease = { */ export async function getHelmReleases(): Promise { const res = await CliChannel.getInstance().executeTool(HelmCommands.listHelmReleases(), undefined, false); - return JSON.parse(res.stdout) as HelmRelease[]; + try { + return JSON.parse(res.stdout) as HelmRelease[]; + } catch { + return []; + } } /** diff --git a/src/oc/ocWrapper.ts b/src/oc/ocWrapper.ts index dbbd7e72d..4f7d68dbd 100644 --- a/src/oc/ocWrapper.ts +++ b/src/oc/ocWrapper.ts @@ -86,9 +86,12 @@ export class Oc { public async getAllKubernetesObjects(namespace?: string, executionContext?: ExecutionContext): Promise { const result = await CliChannel.getInstance().executeTool( - Oc.getKubernetesObjectCommand('all', namespace), - undefined, true, executionContext); - return JSON.parse(result.stdout).items; + Oc.getKubernetesObjectCommand('all', namespace), undefined, true, executionContext); + try { + return JSON.parse(result.stdout).items; + } catch { + return []; + } } /** diff --git a/src/odo/odoWrapper.ts b/src/odo/odoWrapper.ts index f8d66c3a3..452fc046c 100644 --- a/src/odo/odoWrapper.ts +++ b/src/odo/odoWrapper.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See LICENSE file in the project root for license information. *-----------------------------------------------------------------------------------------------*/ -import { KubernetesObject } from '@kubernetes/client-node'; import { Uri, WorkspaceFolder, workspace } from 'vscode'; import { CommandOption, CommandText } from '../base/command'; import * as cliInstance from '../cli'; @@ -12,7 +11,6 @@ import { ChildProcessUtil, CliExitData } from '../util/childProcessUtil'; import { VsCommandError } from '../vscommand'; import { Command } from './command'; import { ComponentDescription } from './componentTypeDescription'; -import { BindableService } from './odoTypes'; /** * Wraps the `odo` cli tool. @@ -189,62 +187,4 @@ export class Odo { componentPath, ); } - - /** - * Bind a component to a bindable service by modifying the devfile - * - * Resolves when the binding it created. - * - * @param contextPath the path to the component - * @param serviceNamespace the namespace the the service is in - * @param serviceName the name of the service to bind to - * @param bindingName the name of the service binding - */ - public async addBinding( - contextPath: string, - serviceNamespace: string, - serviceName: string, - bindingName: string, - ) { - await this.execute( - new CommandText('odo', 'add binding', [ - new CommandOption('--service-namespace', serviceNamespace, false), - new CommandOption('--service', serviceName, false), - new CommandOption('--name', bindingName, false), - ]), - contextPath, - true, - ); - } - - /** - * Returns a list of all the bindable services on the cluster. - * - * @returns a list of all the bindable services on the cluster - */ - public async getBindableServices(): Promise { - const data: CliExitData = await this.execute( - new CommandText('odo', 'list service', [new CommandOption('-o json')]), - ); - let responseObj; - try { - responseObj = JSON.parse(data.stdout); - } catch { - throw new Error(JSON.parse(data.stderr).message); - } - if (!responseObj.bindableServices) { - return []; - } - return (responseObj.bindableServices as BindableService[]) // - .map((obj) => { - return { - kind: obj.kind, - apiVersion: obj.apiVersion, - metadata: { - namespace: obj.namespace, - name: obj.name, - }, - } as KubernetesObject; - }); - } } diff --git a/src/openshift/component.ts b/src/openshift/component.ts index 87aaad047..d78dcdb2c 100644 --- a/src/openshift/component.ts +++ b/src/openshift/component.ts @@ -12,12 +12,10 @@ import { Command } from '../odo/command'; import { CommandProvider } from '../odo/componentTypeDescription'; import { Odo } from '../odo/odoWrapper'; import { ComponentWorkspaceFolder } from '../odo/workspace'; -import sendTelemetry from '../telemetry'; import { ChildProcessUtil, CliExitData } from '../util/childProcessUtil'; import { Progress } from '../util/progress'; import * as fs from '../util/utils'; import { vsCommand, VsCommandError } from '../vscommand'; -import AddServiceBindingViewLoader, { ServiceBindingFormResponse } from '../webview/add-service-binding/addServiceBindingViewLoader'; import CreateComponentLoader from '../webview/create-component/createComponentLoader'; import { OpenShiftTerminalApi, OpenShiftTerminalManager } from '../webview/openshift-terminal/openShiftTerminal'; import OpenShiftItem, { clusterRequired, projectRequired } from './openshiftItem'; @@ -241,75 +239,6 @@ export class Component extends OpenShiftItem { return false; } - @vsCommand('openshift.component.binding.add') - static async addBinding(component: ComponentWorkspaceFolder) { - - const services = await Progress.execFunctionWithProgress('Looking for bindable services', (progress) => { - return Odo.Instance.getBindableServices(); - }); - - if (!services || services.length === 0) { - void window.showErrorMessage('No bindable services are available', 'Open Service Catalog in OpenShift Console') - .then((result) => { - if (result === 'Open Service Catalog in OpenShift Console') { - void commands.executeCommand('openshift.open.operatorBackedServiceCatalog') - } - }); - return; - } - - void sendTelemetry('startAddBindingWizard'); - - let formResponse: ServiceBindingFormResponse = undefined; - try { - formResponse = await new Promise( - (resolve, reject) => { - void AddServiceBindingViewLoader.loadView( - component.contextPath, - services.map( - (service) => `${service.metadata.namespace}/${service.metadata.name}`, - ), - (panel) => { - panel.onDidDispose((_e) => { - reject(new Error('The \'Add Service Binding\' wizard was closed')); - }); - return async (eventData) => { - if (eventData.action === 'addServiceBinding') { - resolve(eventData.params); - await panel.dispose(); - } - }; - }, - ).then(view => { - if (!view) { - // the view was already created - reject(undefined as Error); - } - }); - }, - ); - } catch { - // The form was closed without submitting, - // or the form already exists for this component. - // stop the command. - return; - } - - const selectedServiceObject = services.filter( - (service) => - `${service.metadata.namespace}/${service.metadata.name}` === formResponse.selectedService, - )[0]; - - void sendTelemetry('finishAddBindingWizard'); - - await Odo.Instance.addBinding( - component.contextPath, - selectedServiceObject.metadata.namespace, - selectedServiceObject.metadata.name, - formResponse.bindingName, - ); - } - @vsCommand('openshift.component.dev') @clusterRequired() @projectRequired() diff --git a/src/webview/add-service-binding/addServiceBindingViewLoader.ts b/src/webview/add-service-binding/addServiceBindingViewLoader.ts deleted file mode 100644 index 11d37fb04..000000000 --- a/src/webview/add-service-binding/addServiceBindingViewLoader.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * Copyright (c) Red Hat, Inc. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - *-----------------------------------------------------------------------------------------------*/ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Odo } from '../../odo/odoWrapper'; -import { ExtensionID } from '../../util/constants'; -import { loadWebviewHtml } from '../common-ext/utils'; - -export interface ServiceBindingFormResponse { - selectedService: string; - bindingName: string; -} - -export default class AddServiceBindingViewLoader { - - private static views: Map = new Map(); - - private static get extensionPath(): string { - return vscode.extensions.getExtension(ExtensionID).extensionPath; - } - - /** - * Returns a webview panel with the "Add Service Binding" UI, - * or if there is an existing view for the given contextPath, focuses that view and returns null. - * - * @param contextPath the path to the component that's being binded to a service - * @param availableServices the list of all bindable services on the cluster - * @param listenerFactory the listener function to receive and process messages from the webview - * @returns the webview as a promise - */ - static async loadView( - contextPath: string, - availableServices: string[], - listenerFactory: (panel: vscode.WebviewPanel) => (event) => Promise, - ): Promise { - - if (AddServiceBindingViewLoader.views.get(contextPath)) { - // the event handling for the panel should already be set up, - // no need to handle it - const panel = AddServiceBindingViewLoader.views.get(contextPath); - panel.reveal(vscode.ViewColumn.One); - return null; - } - - return this.createView(contextPath, availableServices, listenerFactory); - } - - private static async createView( - contextPath: string, - availableServices: string[], - listenerFactory: (panel: vscode.WebviewPanel) => (event) => Promise, - ): Promise { - const localResourceRoot = vscode.Uri.file( - path.join(AddServiceBindingViewLoader.extensionPath, 'out', 'add-service-binding', 'app'), - ); - - let panel: vscode.WebviewPanel = vscode.window.createWebviewPanel( - 'addServiceBindingView', - 'Add service binding', - vscode.ViewColumn.One, - { - enableScripts: true, - localResourceRoots: [localResourceRoot], - retainContextWhenHidden: true, - }, - ); - - panel.iconPath = vscode.Uri.file( - path.join(AddServiceBindingViewLoader.extensionPath, 'images/context/cluster-node.png'), - ); - panel.webview.html = await loadWebviewHtml( - 'add-service-binding', - panel, - ); - panel.webview.onDidReceiveMessage(listenerFactory(panel)); - - // set theme - void panel.webview.postMessage({ - action: 'setTheme', - themeValue: vscode.window.activeColorTheme.kind, - }); - const colorThemeDisposable = vscode.window.onDidChangeActiveColorTheme(async function (colorTheme: vscode.ColorTheme) { - await panel.webview.postMessage({ action: 'setTheme', themeValue: colorTheme.kind }); - }); - - panel.onDidDispose(() => { - colorThemeDisposable.dispose(); - panel = undefined; - AddServiceBindingViewLoader.views.delete(contextPath); - }); - AddServiceBindingViewLoader.views.set(contextPath, panel); - - // send initial data to panel - void panel.webview.postMessage({ - action: 'setAvailableServices', - availableServices, - }); - void panel.webview.postMessage({ - action: 'setComponentName', - componentName: (await Odo.Instance.describeComponent(contextPath)).devfileData.devfile.metadata.name, - }); - - return Promise.resolve(panel); - } -} diff --git a/src/webview/add-service-binding/app/addServiceBindingForm.tsx b/src/webview/add-service-binding/app/addServiceBindingForm.tsx deleted file mode 100644 index 4224fa9fb..000000000 --- a/src/webview/add-service-binding/app/addServiceBindingForm.tsx +++ /dev/null @@ -1,218 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * Copyright (c) Red Hat, Inc. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - *-----------------------------------------------------------------------------------------------*/ - -import { - Alert, - Box, - Button, - CircularProgress, - Container, - createTheme, - FormControl, - FormHelperText, - FormLabel, - Grid, - InputLabel, - MenuItem, - PaletteMode, - Select, - Stack, - TextField, - Theme, - ThemeProvider -} from '@mui/material'; -import * as React from 'react'; -import 'react-dom'; - -interface VSCodeMessage { - action: string; - themeValue?: number; - availableServices?: string[]; - componentName?: string; -} - -export function AddServiceBindingForm() { - // These are passed in after the component is created using the VS Code API - const [availableServices, setAvailableServices] = React.useState(undefined); - const [componentName, setComponentName] = React.useState(undefined); - - const [selectedService, setSelectedService] = React.useState(''); - const [bindingName, setBindingName] = React.useState(''); - - // Only mark a form field as an error after the user has first interacted with it - const [selectedServiceTouched, setSelectedServiceTouched] = React.useState(false); - const [bindingNameTouched, setBindingNameTouched] = React.useState(false); - - const createVscodeTheme = (paletteMode: PaletteMode): Theme => { - const computedStyle = window.getComputedStyle(document.body); - return createTheme({ - palette: { - mode: paletteMode, - primary: { - main: computedStyle.getPropertyValue('--vscode-button-background'), - }, - error: { - main: computedStyle.getPropertyValue('--vscode-editorError-foreground'), - }, - warning: { - main: computedStyle.getPropertyValue('--vscode-editorWarning-foreground'), - }, - info: { - main: computedStyle.getPropertyValue('--vscode-editorInfo-foreground'), - }, - success: { - main: computedStyle.getPropertyValue('--vscode-debugIcon-startForeground'), - }, - }, - typography: { - allVariants: { - fontFamily: computedStyle.getPropertyValue('--vscode-font-family'), - }, - }, - }); - }; - - const [theme, setTheme] = React.useState(createVscodeTheme('light')); - - const respondToMessage = function (message: MessageEvent) { - if (message.data.action === 'setTheme') { - setTheme(createVscodeTheme(message.data.themeValue === 1 ? 'light' : 'dark')); - } else if (message.data.action === 'setAvailableServices') { - setAvailableServices(message.data.availableServices); - } else if (message.data.action === 'setComponentName') { - setComponentName(message.data.componentName); - } - }; - - React.useEffect(() => { - window.addEventListener('message', respondToMessage); - return () => { - window.removeEventListener('message', respondToMessage); - }; - }, []); - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - window.vscodeApi.postMessage({ - action: 'addServiceBinding', - params: { - selectedService, - bindingName, - }, - } as ActionMessage); - }; - - const isBindingNameValid = (name: string): boolean => { - return /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name); - }; - - return ( - - {availableServices && componentName ? ( - -
- - - Bind Service to {componentName} - - - Service to Bind - - - The Operator-backed service to connect to your component - - - - { - if (!bindingNameTouched) { - setBindingNameTouched(true); - } - }} - onChange={(e) => { - setBindingName(e.target.value); - }} - error={bindingNameTouched && !isBindingNameValid(bindingName)} - required - > - - The name of the ServiceBinding Kubernetes resource. - Can only contain letters, numbers, and dashes (-). - - - - - {/* Instead of disabling the button when the form entries are invalid, - completely remove it, and instead show an alert explaining what needs to be fixed */} - {!isBindingNameValid(bindingName) || selectedService === '' ? ( - - You must  - {selectedService === '' && ( - <>select a service to bind to - )} - {!isBindingNameValid(bindingName) && - selectedService === '' && <> and } - {!isBindingNameValid(bindingName) && ( - <>set a valid binding name - )} - . - - ) : ( - - )} - - - - -
-
- ) : ( - - - - )} -
- ); -} diff --git a/src/webview/add-service-binding/app/index.html b/src/webview/add-service-binding/app/index.html deleted file mode 100644 index b4a022517..000000000 --- a/src/webview/add-service-binding/app/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - Add Service Binding - - - - - -
- - - diff --git a/src/webview/add-service-binding/app/index.tsx b/src/webview/add-service-binding/app/index.tsx deleted file mode 100644 index b4857ee91..000000000 --- a/src/webview/add-service-binding/app/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * Copyright (c) Red Hat, Inc. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - *-----------------------------------------------------------------------------------------------*/ - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { AddServiceBindingForm } from './addServiceBindingForm'; -import { WebviewErrorBoundary } from '../../common/webviewErrorBoundary'; - -ReactDOM.render( - - - , - document.getElementById('root'), -); diff --git a/src/webview/add-service-binding/app/tsconfig.json b/src/webview/add-service-binding/app/tsconfig.json deleted file mode 100644 index 9bcd883b7..000000000 --- a/src/webview/add-service-binding/app/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "node", - "target": "es6", - "outDir": "addServiceBindingView", - "lib": [ - "es6", - "dom" - ], - "jsx": "react", - "sourceMap": true, - "noUnusedLocals": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "typeRoots": [ - "../../../../node_modules/@types", - "../../@types" - ], - "baseUrl": ".", - }, - "exclude": [ - "node_modules" - ] -} diff --git a/test/integration/odoWrapper.test.ts b/test/integration/odoWrapper.test.ts index 9ec383d30..f65a2b603 100644 --- a/test/integration/odoWrapper.test.ts +++ b/test/integration/odoWrapper.test.ts @@ -187,58 +187,6 @@ suite('./odo/odoWrapper.ts', function () { fail('Expected devfile to be created'); } }); - - test('addBinding()', async function() { - try { - await Odo.Instance.addBinding(tmpFolder, 'default', 'my-service', 'my-service-binding'); - // TODO: set up a service to bind to, - // for now check that the correct error message appears - fail('The service doesn\'t exist, so binding should have failed'); - } catch (e) { - expect(`${e}`).to.contain('No bindable service instances found in namespace'); - } - }) - - }); - - suite('service binding', function() { - let componentFolder: string; - - setup(async function() { - await checkOdoPreference(); - componentFolder = await promisify(tmp.dir)(); - await Odo.Instance.createComponentFromFolder( - 'nodejs', - undefined, - undefined, - 'component1', - Uri.parse(componentFolder), - 'nodejs-starter', - ); - }); - - teardown(async function() { - const newWorkspaceFolders = workspace.workspaceFolders.filter((workspaceFolder) => { - const fsPath = workspaceFolder.uri.fsPath; - return (fsPath !== componentFolder); - }); - workspace.updateWorkspaceFolders(0, workspace.workspaceFolders.length, ...newWorkspaceFolders); - await fs.rm(componentFolder, { recursive: true, force: true }); - }); - - test('getBindableServices()', async function() { - const bindableServices = await Odo.Instance.getBindableServices(); - expect(bindableServices).to.be.empty; - }); - - test('addBinding()', async function() { - try { - await Odo.Instance.addBinding(componentFolder, 'myservice', 'default', 'myservice-binding'); - fail('creating a binding should have failed, since no bindable services are present'); - } catch { - // do nothing - } - }); }); test('deleteComponentConfiguration'); diff --git a/test/ui/suite/operatorBackedService.ts b/test/ui/suite/operatorBackedService.ts index 737b1faed..fddd3319b 100644 --- a/test/ui/suite/operatorBackedService.ts +++ b/test/ui/suite/operatorBackedService.ts @@ -13,11 +13,10 @@ import { Workbench, before, } from 'vscode-extension-tester'; +import { itemExists, notificationExists } from '../common/conditions'; import { MENUS, NOTIFICATIONS, VIEWS } from '../common/constants'; -import { itemExists, notificationDoesNotExist, notificationExists } from '../common/conditions'; -import { CreateServiceWebView, ServiceSetupPage } from '../common/ui/webview/createServiceWebView'; -import { AddServiceBindingWebView } from '../common/ui/webview/addServiceBinding'; import { reloadWindow } from '../common/overdrives'; +import { CreateServiceWebView, ServiceSetupPage } from '../common/ui/webview/createServiceWebView'; export function operatorBackedServiceTest() { describe('Operator-Backed Service', function () { @@ -26,7 +25,6 @@ export function operatorBackedServiceTest() { let view: SideBarView; let section: ViewSection; - let projectName: string; let serviceName: string; before(async function () { @@ -52,7 +50,6 @@ export function operatorBackedServiceTest() { await clusterItem.getDriver().wait(async () => await clusterItem.hasChildren()); const children = await clusterItem.getChildren(); const project = children[0]; - projectName = await project.getLabel(); const contextMenu = await project.openContextMenu(); await contextMenu.select(MENUS.create, MENUS.createOperatorBackedService); @@ -62,7 +59,7 @@ export function operatorBackedServiceTest() { await createServiceWebView.clickComboBox(); await createServiceWebView.selectItemFromComboBox( 'Cluster', - 'clusters.postgresql.k8s.enterprisedb.io', + 'clusters.postgresql.cnpg.io', ); await createServiceWebView.clickNext(); @@ -84,50 +81,5 @@ export function operatorBackedServiceTest() { await deployments.expand(); await itemExists(serviceName, section); }); - - it('Can bind service to a component', async function () { - this.timeout(75_000); - const componentName = 'nodejs-starter'; - const bindingName = 'test-binding'; - section = await view.getContent().getSection(VIEWS.components); - - try { - await itemExists(componentName, section); - } catch { - this.skip(); - } - - //open context menu on component and click bind service - const component = await section.findItem(componentName); - let contextMenu = await component.openContextMenu(); - await contextMenu.select(MENUS.bindService); - - //wait for look for available services to be completed - await notificationDoesNotExist( - NOTIFICATIONS.lookingForBindableServices, - VSBrowser.instance.driver, - ); - - //select service to bind - const addServiceBinding = new AddServiceBindingWebView(); - await addServiceBinding.initializeEditor(); - await addServiceBinding.clickComboBox(); - await addServiceBinding.selectItemFromComboBox(`${projectName}/${serviceName}`); - await addServiceBinding.setBindingName(bindingName); - await addServiceBinding.clickAddServiceBindingButton(); - - //start dev on component - contextMenu = await component.openContextMenu(); - await contextMenu.select(MENUS.startDev); - - //wait for start dev to finish - await itemExists(`${componentName} (dev starting)`, section); - await itemExists(`${componentName} (dev running)`, section, 35_000); - - //check service binding is shown in deployments - await section.collapse(); - section = await view.getContent().getSection(VIEWS.appExplorer); - await itemExists(bindingName, section); - }); }); }