From 1c6b3eb30b18601fb38b382a079b1b400710761d Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Thu, 24 Oct 2019 23:15:12 +0300 Subject: [PATCH] Use component for task configuration instead of container name Signed-off-by: Roman Nikitenko --- plugins/task-plugin/README.md | 2 +- .../src/che-task-backend-module.ts | 2 + .../task-plugin/src/che-workspace-client.ts | 17 ++++ .../src/export/export-configs-manager.ts | 2 +- .../src/export/launch-configs-exporter.ts | 2 +- .../src/export/task-configs-exporter.ts | 9 +- .../src/machine/machines-picker.ts | 39 +++----- .../src/task/backward-compatibility.ts | 90 +++++++++++++++++++ .../task-plugin/src/task/che-task-provider.ts | 53 +++++++++-- plugins/task-plugin/src/task/converter.ts | 15 ++-- plugins/task-plugin/src/task/task-protocol.ts | 2 + plugins/task-plugin/src/utils.ts | 14 +++ 12 files changed, 202 insertions(+), 45 deletions(-) create mode 100644 plugins/task-plugin/src/task/backward-compatibility.ts diff --git a/plugins/task-plugin/README.md b/plugins/task-plugin/README.md index fd6657b0f7..346dcb9f51 100644 --- a/plugins/task-plugin/README.md +++ b/plugins/task-plugin/README.md @@ -21,7 +21,7 @@ The format of a Che task is the following: "command": "", "target": { "workspaceId": "", - "containerName": "", + "component": "", "workingDir": "" }, "previewUrl": "" diff --git a/plugins/task-plugin/src/che-task-backend-module.ts b/plugins/task-plugin/src/che-task-backend-module.ts index 8e40f2f531..9b1ad5b3bf 100644 --- a/plugins/task-plugin/src/che-task-backend-module.ts +++ b/plugins/task-plugin/src/che-task-backend-module.ts @@ -31,6 +31,7 @@ import { ConfigFileTasksExtractor } from './extract/config-file-task-configs-ext import { VsCodeLaunchConfigsExtractor } from './extract/vscode-launch-configs-extractor'; import { VsCodeTaskConfigsExtractor } from './extract/vscode-task-configs-extractor'; import { PreviewUrlVariableResolver } from './variable/preview-url-variable-resolver'; +import { BackwardCompatibilityResolver } from './task/backward-compatibility'; const container = new Container(); container.bind(CheTaskProvider).toSelf().inSingletonScope(); @@ -54,6 +55,7 @@ container.bind(ConfigFileTasksExtractor).toSelf().inSingletonScope(); container.bind(ConfigFileLaunchConfigsExtractor).toSelf().inSingletonScope(); container.bind(VsCodeLaunchConfigsExtractor).toSelf().inSingletonScope(); container.bind(VsCodeTaskConfigsExtractor).toSelf().inSingletonScope(); +container.bind(BackwardCompatibilityResolver).toSelf().inSingletonScope(); container.bind(PreviewUrlsWidget).toSelf().inTransientScope(); container.bind(PreviewUrlsWidgetFactory).toDynamicValue(ctx => ({ diff --git a/plugins/task-plugin/src/che-workspace-client.ts b/plugins/task-plugin/src/che-workspace-client.ts index d08307753e..1b1c1c759a 100644 --- a/plugins/task-plugin/src/che-workspace-client.ts +++ b/plugins/task-plugin/src/che-workspace-client.ts @@ -23,6 +23,23 @@ export class CheWorkspaceClient { return workspace.links; } + /** Returns array of containers' names for the current workspace. */ + async getContainersNames(): Promise { + const containerNames: string[] = []; + + try { + const containers = await this.getMachines(); + for (const containerName in containers) { + if (containers.hasOwnProperty(containerName)) { + containerNames.push(containerName); + } + } + } catch (error) { + } finally { + return containerNames; + } + } + async getMachines(): Promise<{ [attrName: string]: cheApi.workspace.Machine }> { const workspace = await this.getCurrentWorkspace(); const runtime = workspace.runtime; diff --git a/plugins/task-plugin/src/export/export-configs-manager.ts b/plugins/task-plugin/src/export/export-configs-manager.ts index f6c195ad01..7deeece364 100644 --- a/plugins/task-plugin/src/export/export-configs-manager.ts +++ b/plugins/task-plugin/src/export/export-configs-manager.ts @@ -23,7 +23,7 @@ export interface ConfigurationsExporter { * @param workspaceFolder workspace folder for exporting configs in the config file * @param commands commands with configurations for export */ - export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void; + export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): Promise; } /** Contains configurations as array of object and as raw content and is used at getting configurations from config file for example */ export interface Configurations { diff --git a/plugins/task-plugin/src/export/launch-configs-exporter.ts b/plugins/task-plugin/src/export/launch-configs-exporter.ts index 7d828f33e9..b2757e91f9 100644 --- a/plugins/task-plugin/src/export/launch-configs-exporter.ts +++ b/plugins/task-plugin/src/export/launch-configs-exporter.ts @@ -31,7 +31,7 @@ export class LaunchConfigurationsExporter implements ConfigurationsExporter { @inject(VsCodeLaunchConfigsExtractor) protected readonly vsCodeLaunchConfigsExtractor: VsCodeLaunchConfigsExtractor; - export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void { + async export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): Promise { const launchConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path); const configFileConfigs = this.configFileLaunchConfigsExtractor.extract(launchConfigFileUri); const vsCodeConfigs = this.vsCodeLaunchConfigsExtractor.extract(commands); diff --git a/plugins/task-plugin/src/export/task-configs-exporter.ts b/plugins/task-plugin/src/export/task-configs-exporter.ts index 183ccd0564..bb27104e80 100644 --- a/plugins/task-plugin/src/export/task-configs-exporter.ts +++ b/plugins/task-plugin/src/export/task-configs-exporter.ts @@ -19,6 +19,7 @@ import { CheTaskConfigsExtractor } from '../extract/che-task-configs-extractor'; import { VsCodeTaskConfigsExtractor } from '../extract/vscode-task-configs-extractor'; import { ConfigurationsExporter } from './export-configs-manager'; import { ConfigFileTasksExtractor } from '../extract/config-file-task-configs-extractor'; +import { BackwardCompatibilityResolver } from '../task/backward-compatibility'; const CONFIG_DIR = '.theia'; const TASK_CONFIG_FILE = 'tasks.json'; @@ -39,17 +40,21 @@ export class TaskConfigurationsExporter implements ConfigurationsExporter { @inject(VsCodeTaskConfigsExtractor) protected readonly vsCodeTaskConfigsExtractor: VsCodeTaskConfigsExtractor; - export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void { + @inject(BackwardCompatibilityResolver) + protected readonly backwardCompatibilityResolver: BackwardCompatibilityResolver; + + async export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): Promise { const tasksConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path); const configFileTasks = this.configFileTasksExtractor.extract(tasksConfigFileUri); const cheTasks = this.cheTaskConfigsExtractor.extract(commands); const vsCodeTasks = this.vsCodeTaskConfigsExtractor.extract(commands); const devfileConfigs = this.merge(cheTasks, vsCodeTasks.configs, this.getOutputChannelConflictLogger()); + const configFileConfigs = await this.backwardCompatibilityResolver.resolveComponent(configFileTasks.configs); const configFileContent = configFileTasks.content; if (configFileContent) { - this.saveConfigs(tasksConfigFileUri, configFileContent, this.merge(configFileTasks.configs, devfileConfigs, this.getConsoleConflictLogger())); + this.saveConfigs(tasksConfigFileUri, configFileContent, this.merge(configFileConfigs, devfileConfigs, this.getConsoleConflictLogger())); return; } diff --git a/plugins/task-plugin/src/machine/machines-picker.ts b/plugins/task-plugin/src/machine/machines-picker.ts index 1487c0b377..f6d8f0c888 100644 --- a/plugins/task-plugin/src/machine/machines-picker.ts +++ b/plugins/task-plugin/src/machine/machines-picker.ts @@ -12,7 +12,8 @@ import { injectable, inject } from 'inversify'; import * as theia from '@theia/plugin'; import { CheWorkspaceClient } from '../che-workspace-client'; -const MACHINES_PLACE_HOLDER = 'Pick a machine to run the task'; +const MACHINES_PLACE_HOLDER = 'Pick a container to run the task'; +export const COMPONENT_ATTRIBUTE: string = 'component'; @injectable() export class MachinesPicker { @@ -21,36 +22,20 @@ export class MachinesPicker { protected readonly cheWorkspaceClient!: CheWorkspaceClient; /** - * Returns a machine name if there's just one machine in the current workspace. - * Shows a quick open widget allows to pick a machine if there are several ones. + * Returns a container name if there's just one container in the current workspace. + * Shows a quick open widget and allows to pick a container if there are several ones. + * @param containerNames containers for displaying in quick open widget, + * all containers of current workspace will be displayed if the parametr is absent */ - async pick(): Promise { - const machines = await this.getMachines(); - if (machines.length === 1) { - return Promise.resolve(machines[0]); + async pick(containerNames?: string[]): Promise { + if (!containerNames) { + containerNames = await this.cheWorkspaceClient.getContainersNames(); } - const items: string[] = []; - for (const machineName of machines) { - items.push(machineName); + if (containerNames.length === 1) { + return Promise.resolve(containerNames[0]); } - - return this.showMachineQuickPick(items); - } - - protected async getMachines(): Promise { - const machineNames: string[] = []; - const machines = await this.cheWorkspaceClient.getMachines(); - if (!machines) { - return machineNames; - } - - for (const machineName in machines) { - if (machines.hasOwnProperty(machineName)) { - machineNames.push(machineName); - } - } - return machineNames; + return this.showMachineQuickPick(containerNames); } private showMachineQuickPick(items: string[]): Promise { diff --git a/plugins/task-plugin/src/task/backward-compatibility.ts b/plugins/task-plugin/src/task/backward-compatibility.ts new file mode 100644 index 0000000000..42048dff66 --- /dev/null +++ b/plugins/task-plugin/src/task/backward-compatibility.ts @@ -0,0 +1,90 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import { injectable, inject } from 'inversify'; +import * as che from '@eclipse-che/plugin'; +import { CHE_TASK_TYPE } from './task-protocol'; +import { CheWorkspaceClient } from '../che-workspace-client'; +import { getAttribute } from '../utils'; +import { COMPONENT_ATTRIBUTE } from '../machine/machines-picker'; + +/** Contains logic to provide backward compatibility. */ +@injectable() +export class BackwardCompatibilityResolver { + + @inject(CheWorkspaceClient) + protected readonly cheWorkspaceClient!: CheWorkspaceClient; + + /** + * Provides backward compatibility for `containerName` field of task configuration. + * `containerName` was used to indicate which container should be used for running task configuration + * `component` is used instead of `containerName` for this goal. + * + * So, the following configuration: + * { + * "type": "che", + * "label": "theia:build", + * "command": "yarn", + * "target": { + * "workingDir": "/projects/theia", + * "containerName": "che-dev" + * } + * } + * + * should be replaced by: + * + * { + * "type": "che", + * "label": "theia:build", + * "command": "yarn", + * "target": { + * "workingDir": "/projects/theia", + * "component": "che-dev" + * } + * } + * + * Note: `containerName` is replaced by empty `component` field if the corresponding component is not found. + * List of containers is displayed at running task for this case and user has opportunity to select a container for running. + * + * @param configs task configurations for resolving + */ + async resolveComponent(configs: che.TaskConfiguration[]): Promise { + if (configs.length === 0) { + return configs; + } + + const containers = await this.cheWorkspaceClient.getMachines(); + for (const config of configs) { + if (config.type !== CHE_TASK_TYPE) { + continue; + } + + const target = config.target; + if (!target || !target.containerName) { + continue; + } + + const containerName = target.containerName; + target.containerName = undefined; + target.component = ''; + + if (!containers.hasOwnProperty(containerName)) { + continue; + } + + const container = containers[containerName]; + const component = getAttribute(COMPONENT_ATTRIBUTE, container.attributes); + if (component) { + target.component = component; + } + } + return configs; + } +} diff --git a/plugins/task-plugin/src/task/che-task-provider.ts b/plugins/task-plugin/src/task/che-task-provider.ts index a10fdbb1b1..7e2113279e 100644 --- a/plugins/task-plugin/src/task/che-task-provider.ts +++ b/plugins/task-plugin/src/task/che-task-provider.ts @@ -10,10 +10,12 @@ import { injectable, inject } from 'inversify'; import * as che from '@eclipse-che/plugin'; +import { che as cheApi } from '@eclipse-che/api'; import { Task, ShellExecution } from '@theia/plugin'; import { CHE_TASK_TYPE, CheTaskDefinition, Target } from './task-protocol'; -import { MachinesPicker } from '../machine/machines-picker'; +import { MachinesPicker, COMPONENT_ATTRIBUTE } from '../machine/machines-picker'; import { CheWorkspaceClient } from '../che-workspace-client'; +import { getAttribute } from '../utils'; /** Reads the commands from the current Che workspace and provides it as Task Configurations. */ @injectable() @@ -45,11 +47,7 @@ export class CheTaskProvider { resultTarget.workspaceId = await this.cheWorkspaceClient.getWorkspaceId(); } - if (target && target.containerName) { - resultTarget.containerName = target.containerName; - } else { - resultTarget.containerName = await this.machinePicker.pick(); - } + resultTarget.containerName = await this.getContainerName(target); if (target && target.workingDir) { resultTarget.workingDir = await che.variables.resolve(target.workingDir); @@ -71,4 +69,47 @@ export class CheTaskProvider { execution: execution }; } + + private async getContainerName(target?: Target): Promise { + if (!target) { + return this.machinePicker.pick(); + } + + const containers = await this.cheWorkspaceClient.getMachines(); + + const containerName = target.containerName; + if (containerName && containers.hasOwnProperty(containerName)) { + return containerName; + } + + return await this.getContainerNameByComponent(target.component, containers) || this.machinePicker.pick(); + } + + private async getContainerNameByComponent(targetComponent: string | undefined, containers: { [attrName: string]: cheApi.workspace.Machine }): Promise { + if (!targetComponent) { + return undefined; + } + + const names = []; + for (const containerName in containers) { + if (!containers.hasOwnProperty(containerName)) { + continue; + } + + const container = containers[containerName]; + const component = getAttribute(COMPONENT_ATTRIBUTE, container.attributes); + if (component && component === targetComponent) { + names.push(containerName); + } + } + + if (names.length === 1) { + return names[0]; + } + + if (names.length > 1) { + return this.machinePicker.pick(names); + } + return undefined; + } } diff --git a/plugins/task-plugin/src/task/converter.ts b/plugins/task-plugin/src/task/converter.ts index 58ed86b1df..e0f11533ff 100644 --- a/plugins/task-plugin/src/task/converter.ts +++ b/plugins/task-plugin/src/task/converter.ts @@ -11,7 +11,8 @@ import { che as cheApi } from '@eclipse-che/api'; import { Task } from '@theia/plugin'; import { TaskConfiguration } from '@eclipse-che/plugin'; -import { CHE_TASK_TYPE, MACHINE_NAME_ATTRIBUTE, PREVIEW_URL_ATTRIBUTE, WORKING_DIR_ATTRIBUTE } from './task-protocol'; +import { CHE_TASK_TYPE, PREVIEW_URL_ATTRIBUTE, WORKING_DIR_ATTRIBUTE, COMPONENT_ALIAS_ATTRIBUTE } from './task-protocol'; +import { getAttribute } from '../utils'; /** Converts the Che command to Theia Task Configuration */ export function toTaskConfiguration(command: cheApi.workspace.Command): TaskConfiguration { @@ -21,10 +22,10 @@ export function toTaskConfiguration(command: cheApi.workspace.Command): TaskConf command: command.commandLine, _scope: undefined, // not to put into tasks.json target: { - workingDir: getCommandAttribute(command, WORKING_DIR_ATTRIBUTE), - containerName: getCommandAttribute(command, MACHINE_NAME_ATTRIBUTE) + workingDir: getAttribute(WORKING_DIR_ATTRIBUTE, command.attributes), + component: getAttribute(COMPONENT_ALIAS_ATTRIBUTE, command.attributes) }, - previewUrl: getCommandAttribute(command, PREVIEW_URL_ATTRIBUTE) + previewUrl: getAttribute(PREVIEW_URL_ATTRIBUTE, command.attributes) }; return taskConfig; @@ -37,10 +38,10 @@ export function toTask(command: cheApi.workspace.Command): Task { type: CHE_TASK_TYPE, command: command.commandLine, target: { - workingDir: getCommandAttribute(command, WORKING_DIR_ATTRIBUTE), - containerName: getCommandAttribute(command, MACHINE_NAME_ATTRIBUTE) + workingDir: getAttribute(WORKING_DIR_ATTRIBUTE, command.attributes), + component: getAttribute(COMPONENT_ALIAS_ATTRIBUTE, command.attributes) }, - previewUrl: getCommandAttribute(command, PREVIEW_URL_ATTRIBUTE) + previewUrl: getAttribute(PREVIEW_URL_ATTRIBUTE, command.attributes) }, name: `${command.name}`, source: CHE_TASK_TYPE, diff --git a/plugins/task-plugin/src/task/task-protocol.ts b/plugins/task-plugin/src/task/task-protocol.ts index 9d0613d1d9..e403e740a5 100644 --- a/plugins/task-plugin/src/task/task-protocol.ts +++ b/plugins/task-plugin/src/task/task-protocol.ts @@ -14,6 +14,7 @@ export const CHE_TASK_TYPE: string = 'che'; export const MACHINE_NAME_ATTRIBUTE: string = 'machineName'; export const PREVIEW_URL_ATTRIBUTE: string = 'previewUrl'; export const WORKING_DIR_ATTRIBUTE: string = 'workingDir'; +export const COMPONENT_ALIAS_ATTRIBUTE: string = 'componentAlias'; export interface CheTaskDefinition extends TaskDefinition { readonly target?: Target, @@ -24,4 +25,5 @@ export interface Target { workspaceId?: string, containerName?: string, workingDir?: string + component?: string, } diff --git a/plugins/task-plugin/src/utils.ts b/plugins/task-plugin/src/utils.ts index 5164aa8eed..80120d8b73 100644 --- a/plugins/task-plugin/src/utils.ts +++ b/plugins/task-plugin/src/utils.ts @@ -16,6 +16,20 @@ import { FormattingOptions, ParseError, JSONPath } from 'jsonc-parser'; const fs = require('fs'); +/** Allows to get attribute by given name, returns `undefined` if attribute is not found */ +export function getAttribute(attributeName: string, attributes?: { [key: string]: string; }): string | undefined { + if (!attributes) { + return undefined; + } + + for (const attribute in attributes) { + if (attribute === attributeName) { + return attributes[attribute]; + } + } + return undefined; +} + /** * Apply segments to the url endpoint, where are: * @param endPointUrl - url endpoint, for example 'http://ws:/some-server/api'