Content references in command actions for editor specific action definitions. (#13273)

Signed-off-by: Lukas Krejci <lkrejci@redhat.com>
7.20.x
Lukas Krejci 2019-05-21 10:04:11 +02:00 committed by GitHub
parent be3a56c08d
commit 0ce7b1ddac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 334 additions and 41 deletions

View File

@ -41,6 +41,15 @@ public interface Command {
*/
String PLUGIN_ATTRIBUTE = "plugin";
/**
* An attribute of the command to store the original path to the file that contains the editor
* specific configuration.
*/
String COMMAND_ACTION_REFERENCE_ATTRIBUTE = "actionReference";
/** The contents of editor-specific content. */
String COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE = "actionReferenceContent";
/**
* Returns command name (i.e. 'start tomcat') The name should be unique per user in one workspace,
* which means that user may create only one command with the same name in the same workspace

View File

@ -15,12 +15,18 @@ public interface Action {
/** Returns action type. Is is mandatory. */
String getType();
/** Returns component to which given action relates. It is mandatory. */
/** Returns component to which given action relates. */
String getComponent();
/** Returns the actual action command-line string. It is mandatory. */
/** Returns the actual action command-line string. */
String getCommand();
/** Returns the working directory where the command should be executed. It is optional. */
String getWorkdir();
/** Returns the name of the referenced IDE-specific configuration file. */
String getReference();
/** Returns the content of the referenced IDE-specific configuration file. */
String getReferenceContent();
}

View File

@ -12,11 +12,16 @@
package org.eclipse.che.api.devfile.server.convert;
import static java.lang.String.format;
import static org.eclipse.che.api.core.model.workspace.config.Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE;
import static org.eclipse.che.api.core.model.workspace.config.Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE;
import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE;
import java.io.IOException;
import org.eclipse.che.api.core.model.workspace.devfile.Action;
import org.eclipse.che.api.core.model.workspace.devfile.Command;
import org.eclipse.che.api.devfile.server.Constants;
import org.eclipse.che.api.devfile.server.FileContentProvider;
import org.eclipse.che.api.devfile.server.exception.DevfileException;
import org.eclipse.che.api.devfile.server.exception.DevfileFormatException;
import org.eclipse.che.api.devfile.server.exception.WorkspaceExportException;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl;
@ -71,7 +76,7 @@ public class CommandConverter {
* @throws DevfileFormatException if devfile command has more than one action
*/
public org.eclipse.che.api.workspace.server.model.impl.CommandImpl toWorkspaceCommand(
Command devfileCommand) throws DevfileFormatException {
Command devfileCommand, FileContentProvider fileContentProvider) throws DevfileException {
if (devfileCommand.getActions().size() != 1) {
throw new DevfileFormatException(
format("Command `%s` MUST has one and only one action", devfileCommand.getName()));
@ -79,11 +84,12 @@ public class CommandConverter {
Action commandAction = devfileCommand.getActions().get(0);
return toWorkspaceCommand(devfileCommand, commandAction);
return toWorkspaceCommand(devfileCommand, commandAction, fileContentProvider);
}
private org.eclipse.che.api.workspace.server.model.impl.CommandImpl toWorkspaceCommand(
Command devCommand, Action commandAction) {
Command devCommand, Action commandAction, FileContentProvider contentProvider)
throws DevfileException {
org.eclipse.che.api.workspace.server.model.impl.CommandImpl command =
new org.eclipse.che.api.workspace.server.model.impl.CommandImpl();
command.setName(devCommand.getName());
@ -94,9 +100,32 @@ public class CommandConverter {
command.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, commandAction.getWorkdir());
}
command
.getAttributes()
.put(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE, commandAction.getComponent());
if (commandAction.getComponent() != null) {
command
.getAttributes()
.put(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE, commandAction.getComponent());
}
if (commandAction.getReference() != null) {
command.getAttributes().put(COMMAND_ACTION_REFERENCE_ATTRIBUTE, commandAction.getReference());
}
if (commandAction.getReferenceContent() != null) {
command
.getAttributes()
.put(COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE, commandAction.getReferenceContent());
} else if (commandAction.getReference() != null) {
try {
String referenceContent = contentProvider.fetchContent(commandAction.getReference());
command.getAttributes().put(COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE, referenceContent);
} catch (IOException e) {
throw new DevfileException(
format(
"Failed to fetch content of action from reference %s: %s",
commandAction.getReference(), e.getMessage()),
e);
}
}
command.getAttributes().putAll(devCommand.getAttributes());

View File

@ -152,7 +152,10 @@ public class DevfileConverter implements DevfileToWorkspaceConfigConverter {
config.setName(devfile.getName());
for (Command command : devfile.getCommands()) {
config.getCommands().add(commandConverter.toWorkspaceCommand(command));
CommandImpl com = commandConverter.toWorkspaceCommand(command, contentProvider);
if (com != null) {
config.getCommands().add(com);
}
}
// note that component applier modifies commands in workspace config

View File

@ -165,9 +165,9 @@ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorksp
.stream()
.filter(
c ->
c.getAttributes()
.get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)
.equals(componentAlias))
componentAlias != null
&& componentAlias.equals(
c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)))
.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
}

View File

@ -94,9 +94,9 @@ public class EditorComponentToWorkspaceApplier implements ComponentToWorkspaceAp
.stream()
.filter(
c ->
c.getAttributes()
.get(COMPONENT_ALIAS_COMMAND_ATTRIBUTE)
.equals(editorComponentAlias))
editorComponentAlias != null
&& editorComponentAlias.equals(
c.getAttributes().get(COMPONENT_ALIAS_COMMAND_ATTRIBUTE)))
.forEach(c -> c.getAttributes().put(PLUGIN_ATTRIBUTE, fqn.getId()));
}
}

View File

@ -159,9 +159,11 @@ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspa
.stream()
.filter(
c ->
c.getAttributes()
.get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)
.equals(component.getAlias()))
component.getAlias() != null
&& component
.getAlias()
.equals(
c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE)))
.collect(toList());
if (componentCommands.isEmpty()) {
return;

View File

@ -188,8 +188,16 @@ public class DevfileIntegrityValidator {
throw new DevfileFormatException(
format("Multiple actions in command '%s' are not supported yet.", command.getName()));
}
Action action = command.getActions().get(0);
if (action.getComponent() == null
&& (action.getReference() != null || action.getReferenceContent() != null)) {
// ok, this action contains a reference to the file containing the definition. Such
// actions don't have to have component alias defined.
continue;
}
if (!knownAliases.contains(action.getComponent())) {
throw new DevfileFormatException(
format(

View File

@ -516,12 +516,47 @@
"minItems": 1,
"maxItems": 1,
"items": {
"type": "object",
"required": [
"type",
"component",
"command"
"oneOf": [
{
"properties": {
"type": {},
"component": {},
"command": {},
"workdir": {}
},
"required": [
"type",
"component",
"command"
],
"additionalProperties": false
},
{
"properties": {
"type": {},
"reference": {},
"referenceContent": {}
},
"anyOf": [
{
"required": [
"type",
"reference"
],
"additionalProperties": true
},
{
"required": [
"type",
"referenceContent"
],
"additionalProperties": true
}
],
"additionalProperties": false
}
],
"type": "object",
"properties": {
"type": {
"description": "Describes action type",
@ -550,6 +585,20 @@
"examples": [
"/projects/spring-petclinic"
]
},
"reference": {
"type": "string",
"description": "the path relative to the location of the devfile to the configuration file defining one or more actions in the editor-specific format",
"examples": [
"../ide-config/launch.json"
]
},
"referenceContent": {
"type": "string",
"description": "The content of the referenced configuration file that defines one or more actions in the editor-specific format",
"examples": [
"{\"version\": \"2.0.0\",\n \"tasks\": [\n {\n \"type\": \"typescript\",\n \"tsconfig\": \"tsconfig.json\",\n \"problemMatcher\": [\n \"$tsc\"\n ],\n \"group\": {\n \"kind\": \"build\",\n \"isDefault\": true\n }\n }\n ]}"
]
}
}
}

View File

@ -16,9 +16,11 @@ import static org.eclipse.che.api.devfile.server.Constants.COMPONENT_ALIAS_COMMA
import static org.eclipse.che.api.devfile.server.Constants.EXEC_ACTION_TYPE;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import org.eclipse.che.api.core.model.workspace.config.Command;
import org.eclipse.che.api.devfile.server.exception.DevfileFormatException;
import org.eclipse.che.api.devfile.server.exception.WorkspaceExportException;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl;
@ -91,7 +93,7 @@ public class CommandConverterTest {
// when
org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand =
commandConverter.toWorkspaceCommand(devfileCommand);
commandConverter.toWorkspaceCommand(devfileCommand, null);
// then
assertEquals(workspaceCommand.getName(), "build");
@ -118,7 +120,7 @@ public class CommandConverterTest {
// when
org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand =
commandConverter.toWorkspaceCommand(devfileCommand);
commandConverter.toWorkspaceCommand(devfileCommand, null);
// then
assertFalse(workspaceCommand.getAttributes().containsKey(WORKING_DIRECTORY_ATTRIBUTE));
@ -136,6 +138,70 @@ public class CommandConverterTest {
devfileCommand.getActions().add(new ActionImpl());
// when
commandConverter.toWorkspaceCommand(devfileCommand);
commandConverter.toWorkspaceCommand(devfileCommand, null);
}
@Test
public void shouldAcceptActionWithCommand() throws Exception {
// given
CommandImpl devfileCommand = new CommandImpl();
devfileCommand.setName("build");
ActionImpl action = new ActionImpl();
action.setType("exec");
action.setCommand("blah");
devfileCommand.getActions().add(action);
// when
Command command = commandConverter.toWorkspaceCommand(devfileCommand, null);
// then
assertEquals(command.getCommandLine(), "blah");
}
@Test
public void shouldAcceptActionWithReference() throws Exception {
// given
CommandImpl devfileCommand = new CommandImpl();
devfileCommand.setName("build");
ActionImpl action = new ActionImpl();
action.setType("exec");
action.setReference("blah");
devfileCommand.getActions().add(action);
// when
Command command = commandConverter.toWorkspaceCommand(devfileCommand, fileURL -> "content");
// then
assertNull(command.getCommandLine());
assertEquals("blah", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE));
assertEquals(
"content", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE));
}
@Test
public void shouldAcceptActionWithReferenceContent() throws Exception {
// given
CommandImpl devfileCommand = new CommandImpl();
devfileCommand.setName("build");
ActionImpl action = new ActionImpl();
action.setType("exec");
action.setReference("blah");
action.setReferenceContent("content");
devfileCommand.getActions().add(action);
// when
Command command = commandConverter.toWorkspaceCommand(devfileCommand, null);
// then
assertNull(command.getCommandLine());
assertEquals("blah", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE));
assertEquals(
"content", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE));
}
}

View File

@ -226,7 +226,7 @@ public class DevfileConverterTest {
org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand =
mock(org.eclipse.che.api.workspace.server.model.impl.CommandImpl.class);
when(commandConverter.toWorkspaceCommand(any())).thenReturn(workspaceCommand);
when(commandConverter.toWorkspaceCommand(any(), any())).thenReturn(workspaceCommand);
// when
WorkspaceConfigImpl workspaceConfig =

View File

@ -66,14 +66,14 @@ public class DevfileSchemaValidatorTest {
@Test(dataProvider = "invalidDevfiles")
public void shouldThrowExceptionOnValidationOfNonValidDevfile(
String resourceFilePath, String expectedMessageRegexp) throws Exception {
String resourceFilePath, String expectedMessage) throws Exception {
try {
schemaValidator.validateBySchema(getResource(resourceFilePath));
} catch (DevfileFormatException e) {
assertEquals(
e.getMessage(),
format("Devfile schema validation failed. Error: %s", expectedMessageRegexp),
"DevfileFormatException thrown with message that doesn't match expected pattern:");
format("Devfile schema validation failed. Error: %s", expectedMessage),
"DevfileFormatException thrown with message that doesn't match expected message:");
return;
}
fail("DevfileFormatException expected to be thrown but is was not");
@ -121,6 +121,10 @@ public class DevfileSchemaValidatorTest {
"command/devfile_multiple_commands_actions.yaml",
"(/commands/0/actions):The array must have at most 1 element(s), but actual number is 2."
},
{
"command/devfile_action_without_commandline_and_reference.yaml",
"Exactly one of the following sets of problems must be resolved.: [(/commands/0/actions/0):The object must have a property whose name is \"component\".(/commands/0/actions/0):The object must have a property whose name is \"command\".At least one of the following sets of problems must be resolved.: [(/commands/0/actions/0):The object must have a property whose name is \"reference\".(/commands/0/actions/0):The object must have a property whose name is \"referenceContent\".]]"
},
// cheEditor/chePlugin component model testing
{
"editor_plugin_component/devfile_editor_component_with_missing_id.yaml",

View File

@ -0,0 +1,23 @@
#
# Copyright (c) 2012-2018 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
#
# Contributors:
# Red Hat, Inc. - initial API and implementation
#
---
specVersion: 0.0.1
name: petclinic-dev-environment
components:
- alias: theia
type: cheEditor
id: eclipse/chetheia/0.0.3
commands:
- name: build
actions:
- type: vscode-task

View File

@ -44,4 +44,18 @@ public interface DevfileActionDto extends Action {
void setWorkdir(String workdir);
DevfileActionDto withWorkdir(String workdir);
@Override
String getReference();
void setReference(String reference);
DevfileActionDto withReference(String reference);
@Override
String getReferenceContent();
void setReferenceContent(String referenceContent);
DevfileActionDto withReferenceContent(String referenceContent);
}

View File

@ -78,8 +78,10 @@ public class WorkspaceValidator {
"Workspace %s contains command with null or empty name",
config.getName());
check(
!isNullOrEmpty(command.getCommandLine()),
"Command line required for command '%s' in workspace '%s'",
!isNullOrEmpty(command.getCommandLine())
|| !isNullOrEmpty(
command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE)),
"Command line or content required for command '%s' in workspace '%s'.",
command.getName(),
config.getName());
}

View File

@ -41,17 +41,37 @@ public class ActionImpl implements Action {
@Column(name = "workdir")
private String workdir;
@Column(name = "reference")
private String reference;
@Column(name = "reference_content")
private String referenceContent;
public ActionImpl() {}
public ActionImpl(String type, String component, String command, String workdir) {
public ActionImpl(
String type,
String component,
String command,
String workdir,
String reference,
String referenceContent) {
this.type = type;
this.component = component;
this.command = command;
this.workdir = workdir;
this.reference = reference;
this.referenceContent = referenceContent;
}
public ActionImpl(Action action) {
this(action.getType(), action.getComponent(), action.getCommand(), action.getWorkdir());
this(
action.getType(),
action.getComponent(),
action.getCommand(),
action.getWorkdir(),
action.getReference(),
action.getReferenceContent());
}
@Override
@ -90,6 +110,24 @@ public class ActionImpl implements Action {
this.workdir = workdir;
}
@Override
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference = reference;
}
@Override
public String getReferenceContent() {
return referenceContent;
}
public void setReferenceContent(String referenceContent) {
this.referenceContent = referenceContent;
}
@Override
public boolean equals(Object o) {
if (this == o) {

View File

@ -240,7 +240,8 @@ public class WorkspaceValidatorTest {
@Test(
expectedExceptions = ValidationException.class,
expectedExceptionsMessageRegExp = "Command line required for command '.*'")
expectedExceptionsMessageRegExp =
"Command line or content required for command '.*' in workspace '.*'\\.")
public void shouldFailValidationIfCommandLineIsNull() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getCommands().get(0).withCommandLine(null);
@ -250,7 +251,8 @@ public class WorkspaceValidatorTest {
@Test(
expectedExceptions = ValidationException.class,
expectedExceptionsMessageRegExp = "Command line required for command '.*'")
expectedExceptionsMessageRegExp =
"Command line or content required for command '.*' in workspace '.*'\\.")
public void shouldFailValidationIfCommandLineIsEmpty() throws Exception {
final WorkspaceConfigDto config = createConfig();
config.getCommands().get(0).withCommandLine("");

View File

@ -550,8 +550,10 @@ public class WorkspaceDaoTest {
// Remove an existing command
workspace.getDevfile().getCommands().remove(1);
ActionImpl action3 = new ActionImpl("exec3", "component3", "run.sh", "/home/user/3");
ActionImpl action4 = new ActionImpl("exec4", "component4", "run.sh", "/home/user/4");
ActionImpl action3 =
new ActionImpl("exec3", "component3", "run.sh", "/home/user/3", null, null);
ActionImpl action4 =
new ActionImpl("exec4", "component4", "run.sh", "/home/user/4", null, null);
// Add a new command
final org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl newCmd =
new org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl(
@ -856,8 +858,10 @@ public class WorkspaceDaoTest {
new SourceImpl("type2", "http://location", "branch2", "point2", "tag2", "commit2");
ProjectImpl project2 = new ProjectImpl("project2", source2, "path2");
ActionImpl action1 = new ActionImpl("exec1", "component1", "run.sh", "/home/user/1");
ActionImpl action2 = new ActionImpl("exec2", "component2", "run.sh", "/home/user/2");
ActionImpl action1 =
new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null);
ActionImpl action2 =
new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null);
org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl command1 =
new org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl(

View File

@ -0,0 +1,17 @@
--
-- Copyright (c) 2012-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
--
-- Contributors:
-- Red Hat, Inc. - initial API and implementation
--
-- devfile command action references
ALTER TABLE devfile_action ALTER COLUMN component DROP NOT NULL;
ALTER TABLE devfile_action ALTER COLUMN command DROP NOT NULL;
ALTER TABLE devfile_action ADD COLUMN reference TEXT;
ALTER TABLE devfile_action ADD COLUMN reference_content TEXT;

View File

@ -0,0 +1,17 @@
--
-- Copyright (c) 2012-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
--
-- Contributors:
-- Red Hat, Inc. - initial API and implementation
--
-- devfile command action references
ALTER TABLE devfile_action MODIFY COLUMN component VARCHAR(255) NULL DEFAULT NULL;
ALTER TABLE devfile_action MODIFY COLUMN command TEXT NULL DEFAULT NULL;
ALTER TABLE devfile_action ADD COLUMN reference TEXT;
ALTER TABLE devfile_action ADD COLUMN reference_content TEXT;

View File

@ -146,7 +146,7 @@ public final class TestObjectsFactory {
}
private static ActionImpl createAction() {
return new ActionImpl("exec", "component", "run.sh", "/home/user");
return new ActionImpl("exec", "component", "run.sh", "/home/user", null, null);
}
private static ProjectImpl createDevfileProject(String name) {