Skip to content

Commit

Permalink
Add an ability to export workspace with dockerimage recipe as Devfile
Browse files Browse the repository at this point in the history
Signed-off-by: Sergii Leshchenko <sleshche@redhat.com>
  • Loading branch information
sleshchenko committed Feb 13, 2019
1 parent d498a1d commit 21c699f
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/
package org.eclipse.che.api.devfile.server;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.function.Function.identity;
Expand All @@ -30,8 +29,10 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import javax.inject.Inject;
Expand All @@ -42,6 +43,7 @@
import org.eclipse.che.api.devfile.model.Source;
import org.eclipse.che.api.devfile.model.Tool;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
Expand Down Expand Up @@ -73,15 +75,34 @@ public DevfileConverter(
*/
public Devfile workspaceToDevFile(WorkspaceConfigImpl wsConfig) throws WorkspaceExportException {

if (!isNullOrEmpty(wsConfig.getDefaultEnv()) || !wsConfig.getEnvironments().isEmpty()) {
Devfile devfile =
new Devfile().withSpecVersion(CURRENT_SPEC_VERSION).withName(wsConfig.getName());

if (wsConfig.getEnvironments().size() > 1) {
throw new WorkspaceExportException(
format(
"Workspace %s cannot be converted to devfile since it contains environments which have no equivalent in devfile model",
"Workspace `%s` cannot be converted to devfile since it has several environments which have no equivalent in devfile model",
wsConfig.getName()));
}

Devfile devfile =
new Devfile().withSpecVersion(CURRENT_SPEC_VERSION).withName(wsConfig.getName());
String recipeToolName = null;
if (!wsConfig.getEnvironments().isEmpty()) {
Map<String, EnvironmentImpl> environments = wsConfig.getEnvironments();
Entry<String, EnvironmentImpl> environmentEntry = environments.entrySet().iterator().next();
String environmentName = environmentEntry.getKey();
EnvironmentImpl environment = environmentEntry.getValue();
if (!"dockerimage".equals(environment.getRecipe().getType())) {
throw new WorkspaceExportException(
format(
"Workspace `%s` cannot be converted to devfile since it has environment with '%s' recipe type. Currently only workspaces with `dockerimage` recipe can be exported.",
wsConfig.getName(),
environment.getRecipe().getType()));
}

Tool dockerimageTool = dockerimageToolApplier.from(environmentName, environment);
devfile.getTools().add(dockerimageTool);
recipeToolName = dockerimageTool.getName();
}

// Manage projects
List<Project> projects = new ArrayList<>();
Expand All @@ -92,14 +113,11 @@ public Devfile workspaceToDevFile(WorkspaceConfigImpl wsConfig) throws Workspace

// Manage commands
Map<String, String> toolsIdToName = parseTools(wsConfig);
List<Command> commands = new ArrayList<>();
wsConfig
.getCommands()
.forEach(command -> commands.add(commandImplToDevCommand(command, toolsIdToName)));
devfile.setCommands(commands);
for (CommandImpl command : wsConfig.getCommands()) {
devfile.getCommands().add(commandImplToDevCommand(command, toolsIdToName, recipeToolName));
}

// Manage tools
List<Tool> tools = new ArrayList<>();
for (Map.Entry<String, String> entry : wsConfig.getAttributes().entrySet()) {
if (entry.getKey().equals(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE)) {
String editorId = entry.getValue();
Expand All @@ -108,19 +126,18 @@ public Devfile workspaceToDevFile(WorkspaceConfigImpl wsConfig) throws Workspace
.withType(EDITOR_TOOL_TYPE)
.withId(editorId)
.withName(toolsIdToName.getOrDefault(editorId, editorId));
tools.add(editorTool);
devfile.getTools().add(editorTool);
} else if (entry.getKey().equals(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE)) {
for (String pluginId : entry.getValue().split(",")) {
Tool pluginTool =
new Tool()
.withId(pluginId)
.withType(PLUGIN_TOOL_TYPE)
.withName(toolsIdToName.getOrDefault(pluginId, pluginId));
tools.add(pluginTool);
devfile.getTools().add(pluginTool);
}
}
}
devfile.setTools(tools);
return devfile;
}

Expand Down Expand Up @@ -250,14 +267,28 @@ private CommandImpl devCommandToCommandImpl(
return command;
}

private Command commandImplToDevCommand(CommandImpl command, Map<String, String> toolsIdToName) {
private Command commandImplToDevCommand(CommandImpl command, Map<String, String> toolsIdToName, String recipeToolName)
throws WorkspaceExportException {
Command devCommand = new Command().withName(command.getName());
Action action = new Action().withCommand(command.getCommandLine()).withType(command.getType());
String workingDir = command.getAttributes().get(WORKING_DIRECTORY_ATTRIBUTE);
if (!isNullOrEmpty(workingDir)) {
action.setWorkdir(workingDir);
}
action.setTool(toolsIdToName.getOrDefault(command.getAttributes().get(PLUGIN_ATTRIBUTE), ""));
String pluginAttribute = command.getAttributes().get(PLUGIN_ATTRIBUTE);
if (!isNullOrEmpty(pluginAttribute)) {
String toolName = toolsIdToName.get(pluginAttribute);
if (toolName == null) {
throw new WorkspaceExportException(format("Can not evaluate tool name for command '%s' which are configured to be run in plugin '%s'", command.getName(), pluginAttribute));
}
action.setTool(toolName);
} else {
if (isNullOrEmpty(recipeToolName)) {
throw new WorkspaceExportException(format("Can not evaluate tool name for command '%s'", command.getName()));
}
action.setTool(recipeToolName);
}

devCommand.getActions().add(action);
devCommand.setAttributes(command.getAttributes());
// Remove internal attributes
Expand Down Expand Up @@ -286,9 +317,11 @@ private ProjectConfigImpl devProjectToProjectConfig(Project devProject) {
}

private Map<String, String> parseTools(WorkspaceConfigImpl wsConfig) {
String aliasesString =
firstNonNull(wsConfig.getAttributes().get(ALIASES_WORKSPACE_ATTRIBUTE_NAME), "");
return Arrays.stream(aliasesString.split(","))
String aliasesAttribute = wsConfig.getAttributes().get(ALIASES_WORKSPACE_ATTRIBUTE_NAME);
if (isNullOrEmpty(aliasesAttribute)) {
return new HashMap<>();
}
return Arrays.stream(aliasesAttribute.split(","))
.map(s -> s.split("=", 2))
.collect(toMap(arr -> arr[0], arr -> arr[1]));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@

import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.devfile.model.Devfile;
import org.eclipse.che.api.devfile.model.Endpoint;
import org.eclipse.che.api.devfile.model.Env;
import org.eclipse.che.api.devfile.model.Tool;
import org.eclipse.che.api.devfile.model.Volume;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
Expand Down Expand Up @@ -124,4 +128,55 @@ public void apply(Tool tool, Devfile devfile, WorkspaceConfigImpl workspaceConfi
.filter(command -> command.getActions().get(0).getTool().equals(tool.getName()))
.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
}

public Tool from(String environmentName, EnvironmentImpl environment)
throws WorkspaceExportException {
Tool dockerimageTool = new Tool();
dockerimageTool.setName(environmentName);

RecipeImpl recipe = environment.getRecipe();
dockerimageTool.setId(recipe.getContent());

if (environment.getMachines().isEmpty()) {
// environment does not have additional configuration
return dockerimageTool;
}

if (environment.getMachines().size() > 1) {
throw new WorkspaceExportException("Environment with 'dockerimage' recipe must contains one machine configuration");
}

MachineConfigImpl machineConfig = environment.getMachines().values().iterator().next();

for (Entry<String, ServerConfigImpl> serverEntry : machineConfig.getServers().entrySet()) {
dockerimageTool.getEndpoints().add(toEndpoint(serverEntry.getKey(), serverEntry.getValue()));
}

for (Entry<String, VolumeImpl> volumeEntry : machineConfig.getVolumes().entrySet()) {
dockerimageTool.getVolumes().add(toDevfileVolume(volumeEntry.getKey(), volumeEntry.getValue()));
}

machineConfig.getEnv()
.entrySet()
.stream()
.map(e -> new Env().withName(e.getKey()).withValue(e.getValue()))
.forEach(e -> dockerimageTool.getEnv().add(e));

return dockerimageTool;
}

private Volume toDevfileVolume(String name, VolumeImpl volume) {
return new Volume().withName(name).withContainerPath(volume.getPath());
}

private Endpoint toEndpoint(String name, ServerConfigImpl config) {
String stringPort = config.getPort().split("/")[0];//cut protocol from string port like 8080/TCP

Map<String, String> attributes = new HashMap<>(config.getAttributes());
attributes.put("protocol", config.getProtocol());
attributes.put("path", config.getPath());
return new Endpoint().withName(name)
.withPort(Integer.parseInt(stringPort))
.withAttributes(attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
*/
package org.eclipse.che.api.devfile.server;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -22,7 +28,9 @@
import org.eclipse.che.api.devfile.model.Devfile;
import org.eclipse.che.api.devfile.model.Project;
import org.eclipse.che.api.devfile.model.Tool;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.commons.json.JsonHelper;
import org.testng.annotations.BeforeClass;
Expand All @@ -33,11 +41,12 @@ public class DevfileConverterTest {

private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
private KubernetesToolApplier kubernetesToolApplier = new KubernetesToolApplier();
private DockerimageToolApplier dockerimageToolApplier = new DockerimageToolApplier("/projects");
private DockerimageToolApplier dockerimageToolApplier;
private DevfileConverter devfileConverter;

@BeforeClass
public void setUp() {
dockerimageToolApplier = mock(DockerimageToolApplier.class);
devfileConverter = new DevfileConverter(kubernetesToolApplier, dockerimageToolApplier);
}

Expand Down Expand Up @@ -134,16 +143,79 @@ public void shouldBuildYamlDevFileFromWorkspaceConfig() throws Exception {
@Test(
expectedExceptions = WorkspaceExportException.class,
expectedExceptionsMessageRegExp =
"Workspace .* cannot be converted to devfile since it contains environments which have no equivalent in devfile model")
public void shouldThrowExceptionWhenWorkspaceHasEnvironments() throws Exception {
"Workspace `ws123` cannot be converted to devfile since it has several environments which have no equivalent in devfile model")
public void shouldThrowExceptionWhenWorkspaceHasMultipleEnvironments() throws Exception {
// given
WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl();
workspaceConfig.setName("ws123");
workspaceConfig.getEnvironments().put("env1", new EnvironmentImpl());
workspaceConfig.getEnvironments().put("env2", new EnvironmentImpl());

// when
devfileConverter.workspaceToDevFile(workspaceConfig);
}

@Test(
expectedExceptions = WorkspaceExportException.class,
expectedExceptionsMessageRegExp =
"Workspace `ws123` cannot be converted to devfile since it has environment with 'any' recipe type. Currently only workspaces with `dockerimage` recipe can be exported.")
public void shouldThrowExceptionWhenWorkspaceHasEnvironmentWithNonDockerimageRecipe() throws Exception {
// given
WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl();
workspaceConfig.setName("ws123");
EnvironmentImpl environment = new EnvironmentImpl();
environment.setRecipe(new RecipeImpl("any", null, null, null));
workspaceConfig.getEnvironments().put("env1", environment);

// when
devfileConverter.workspaceToDevFile(workspaceConfig);
}

@Test
public void shouldAddDockerimageToolIfEnvironmentHasDockerimageRecipe() throws Exception {
// given
WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl();
workspaceConfig.setName("ws123");
EnvironmentImpl environment = new EnvironmentImpl();
environment.setRecipe(new RecipeImpl("dockerimage", null, null, null));
workspaceConfig.getEnvironments().put("env1", environment);

Tool dockerimageTool = new Tool();
when(dockerimageToolApplier.from(any(), any())).thenReturn(dockerimageTool);

// when
Devfile devfile = devfileConverter.workspaceToDevFile(workspaceConfig);

//then
verify(dockerimageToolApplier).from("env1", environment);
assertEquals(devfile.getTools().size(), 1);
assertEquals(devfile.getTools().get(0), dockerimageTool);
}

@Test
public void shouldSetDockerimageToolForCommandsWhichAreNotConfiguredToBeRunInPlugins() throws Exception {
// given
WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl();
workspaceConfig.setName("ws123");
EnvironmentImpl environment = new EnvironmentImpl();
environment.setRecipe(new RecipeImpl("dockerimage", null, null, null));
workspaceConfig.getEnvironments().put("env1", environment);

workspaceConfig.getCommands().add(new CommandImpl("cmd1", "echo Hello", "custom"));

Tool dockerimageTool = new Tool().withName("dockerimageTool");
when(dockerimageToolApplier.from(any(), any())).thenReturn(dockerimageTool);

// when
Devfile devfile = devfileConverter.workspaceToDevFile(workspaceConfig);

//then
assertEquals(devfile.getCommands().size(), 1);
Command command = devfile.getCommands().get(0);
assertEquals(command.getActions().size(), 1);
assertEquals(command.getActions().get(0).getTool(), "dockerimageTool");
}

private String getTestResource(String resource) throws IOException {
return Files.readFile(getClass().getClassLoader().getResourceAsStream(resource));
}
Expand Down
Loading

0 comments on commit 21c699f

Please sign in to comment.