Add Compose, Dockerfile and DockerImage Environments and factories for them

6.19.x
Sergii Leshchenko 2017-11-16 12:01:52 +02:00
parent 7f620b2d55
commit a74466308d
26 changed files with 990 additions and 573 deletions

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-2017 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-infrastructures-docker-parent</artifactId>
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<version>6.0.0-M2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>docker-environment</artifactId>
<name>Infrastructure :: Docker :: Environment</name>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-installer</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-model</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace-shared</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockitong</groupId>
<artifactId>mockitong</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment;
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.MapBinder;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.ComposeEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.ComposeEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile.DockerfileEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile.DockerfileEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage.DockerImageEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage.DockerImageEnvironmentFactory;
/** @author Sergii Leshchenko */
public class DockerEnvironmentsModule extends AbstractModule {
@Override
protected void configure() {
MapBinder<String, InternalEnvironmentFactory> factories =
MapBinder.newMapBinder(binder(), String.class, InternalEnvironmentFactory.class);
factories.addBinding(ComposeEnvironment.TYPE).to(ComposeEnvironmentFactory.class);
factories.addBinding(DockerImageEnvironment.TYPE).to(DockerImageEnvironmentFactory.class);
factories.addBinding(DockerfileEnvironment.TYPE).to(DockerfileEnvironmentFactory.class);
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeService;
/** @author Sergii Leshchenko */
public class ComposeEnvironment extends InternalEnvironment {
public static final String TYPE = "compose";
private String version;
private LinkedHashMap<String, ComposeService> services;
ComposeEnvironment(
String version,
LinkedHashMap<String, ComposeService> services,
InternalRecipe recipe,
Map<String, InternalMachineConfig> machines,
List<Warning> warnings) {
super(recipe, machines, warnings);
this.version = version;
this.services = services;
}
public String getVersion() {
return version;
}
/** Returns ordered services. */
public LinkedHashMap<String, ComposeService> getServices() {
if (services == null) {
services = new LinkedHashMap<>();
}
return services;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ComposeEnvironment)) {
return false;
}
final ComposeEnvironment that = (ComposeEnvironment) obj;
return Objects.equals(version, that.version)
&& Objects.equals(getServices(), that.getServices())
&& Objects.equals(getRecipe(), that.getRecipe())
&& Objects.equals(getMachines(), that.getMachines())
&& Objects.equals(getWarnings(), that.getWarnings());
}
@Override
public int hashCode() {
return Objects.hash(version, getServices(), getMachines(), getRecipe(), getWarnings());
}
@Override
public String toString() {
return "ComposeEnvironment{"
+ "version='"
+ version
+ '\''
+ ", services="
+ getServices()
+ ", machines="
+ getMachines()
+ ", recipe="
+ getRecipe()
+ ", warnings="
+ getWarnings()
+ '}';
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import static java.lang.String.format;
import static java.util.stream.Collectors.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeRecipe;
/** @author Sergii Leshchenko */
@Singleton
public class ComposeEnvironmentFactory extends InternalEnvironmentFactory<ComposeEnvironment> {
private static final ObjectMapper YAML_PARSER = new ObjectMapper(new YAMLFactory());
private static Set<String> YAML_CONTENT_TYPES =
ImmutableSet.of("application/x-yaml", "text/yaml", "text/x-yaml");
private final ComposeServicesStartStrategy startStrategy;
private final ComposeEnvironmentValidator composeValidator;
@Inject
public ComposeEnvironmentFactory(
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
MachineConfigsValidator machinesValidator,
ComposeEnvironmentValidator composeValidator,
ComposeServicesStartStrategy startStrategy) {
super(installerRegistry, recipeRetriever, machinesValidator);
this.startStrategy = startStrategy;
this.composeValidator = composeValidator;
}
@Override
protected ComposeEnvironment doCreate(
InternalRecipe recipe, Map<String, InternalMachineConfig> machines, List<Warning> warnings)
throws InfrastructureException, ValidationException {
String contentType = recipe.getContentType();
checkNotNull(contentType, "Recipe content type should not be null");
String recipeContent = recipe.getContent();
if (!ComposeEnvironment.TYPE.equals(recipe.getType())) {
throw new ValidationException(
format("Compose environment parser doesn't support recipe type '%s'", recipe.getType()));
}
if (!YAML_CONTENT_TYPES.contains(contentType)) {
throw new ValidationException(
format(
"Provided environment recipe content type '%s' is "
+ "unsupported. Supported values are: %s",
contentType, YAML_CONTENT_TYPES.stream().collect(joining(", "))));
}
ComposeRecipe composeRecipe = doParse(recipeContent);
ComposeEnvironment composeEnvironment =
new ComposeEnvironment(
composeRecipe.getVersion(),
startStrategy.order(composeRecipe.getServices()),
recipe,
machines,
warnings);
composeValidator.validate(composeEnvironment);
return composeEnvironment;
}
@VisibleForTesting
ComposeRecipe doParse(String recipeContent) throws ValidationException {
ComposeRecipe composeRecipe;
try {
composeRecipe = YAML_PARSER.readValue(recipeContent, ComposeRecipe.class);
} catch (IOException e) {
throw new ValidationException(
"Parsing of environment configuration failed. " + e.getLocalizedMessage());
}
return composeRecipe;
}
private static void checkNotNull(
Object object, String errorMessageTemplate, Object... errorMessageParams)
throws ValidationException {
if (object == null) {
throw new ValidationException(format(errorMessageTemplate, errorMessageParams));
}
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeService;
/** @author Alexander Garagatyi */
class ComposeEnvironmentValidator {
private static final String SERVICE_NAME_REGEXP = "[a-zA-Z0-9._-]+";
private static final Pattern SERVICE_NAME_PATTERN = Pattern.compile(SERVICE_NAME_REGEXP);
// DockerContainer syntax patterns
/**
* Examples:
*
* <ul>
* <li>8080/tcp
* <li>8080/udp
* <li>8080
* <li>8/tcp
* <li>8
* </ul>
*/
private static final Pattern EXPOSE_PATTERN = Pattern.compile("^[1-9]+[0-9]*(/(tcp|udp))?$");
/**
* Examples:
*
* <ul>
* <li>service1
* <li>service1:alias1
* </ul>
*/
private static final Pattern LINK_PATTERN =
Pattern.compile(
"^(?<containerName>" + SERVICE_NAME_REGEXP + ")(:" + SERVICE_NAME_REGEXP + ")?$");
private static final Pattern VOLUME_FROM_PATTERN =
Pattern.compile("^(?<containerName>" + SERVICE_NAME_REGEXP + ")(:(ro|rw))?$");
void validate(ComposeEnvironment env) throws ValidationException {
checkArgument(
env.getServices() != null && !env.getServices().isEmpty(),
"Environment should contain at least 1 service");
List<String> missingServices =
env.getMachines()
.keySet()
.stream()
.filter(machineName -> !env.getServices().containsKey(machineName))
.collect(toList());
checkArgument(
missingServices.isEmpty(),
"Environment contains machines that are missing in environment recipe: %s",
Joiner.on(", ").join(missingServices));
// needed to validate different kinds of dependencies in containers to other containers
Set<String> containersNames = env.getServices().keySet();
for (Map.Entry<String, ComposeService> serviceEntry : env.getServices().entrySet()) {
validateService(serviceEntry.getKey(), serviceEntry.getValue(), containersNames);
}
}
private void validateService(
String serviceName, ComposeService service, Set<String> servicesNames)
throws ValidationException {
checkArgument(
SERVICE_NAME_PATTERN.matcher(serviceName).matches(),
"Name of service '%s' in environment is invalid",
serviceName);
checkArgument(
!isNullOrEmpty(service.getImage())
|| (service.getBuild() != null
&& (!isNullOrEmpty(service.getBuild().getContext())
|| !isNullOrEmpty(service.getBuild().getDockerfile()))),
"Field 'image' or 'build.context' is required in service '%s' in environment",
serviceName);
checkArgument(
service.getBuild() == null
|| (isNullOrEmpty(service.getBuild().getContext())
!= isNullOrEmpty(service.getBuild().getDockerfile())),
"Service '%s' in environment contains mutually exclusive dockerfile content and build context.",
serviceName);
for (String expose : service.getExpose()) {
checkArgument(
EXPOSE_PATTERN.matcher(expose).matches(),
"Exposed port '%s' in service '%s' in environment is invalid",
expose,
serviceName);
}
for (String link : service.getLinks()) {
Matcher matcher = LINK_PATTERN.matcher(link);
checkArgument(
matcher.matches(),
"Link '%s' in service '%s' in environment is invalid",
link,
serviceName);
String containerFromLink = matcher.group("containerName");
checkArgument(
!serviceName.equals(containerFromLink),
"Container '%s' has illegal link to itself",
serviceName);
checkArgument(
servicesNames.contains(containerFromLink),
"Service '%s' in environment contains link to non existing service '%s'",
serviceName,
containerFromLink);
}
for (String depends : service.getDependsOn()) {
checkArgument(
SERVICE_NAME_PATTERN.matcher(depends).matches(),
"Dependency '%s' in service '%s' in environment is invalid",
depends,
serviceName);
checkArgument(
!serviceName.equals(depends),
"Container '%s' has illegal dependency to itself",
serviceName);
checkArgument(
servicesNames.contains(depends),
"Service '%s' in environment contains dependency to non existing service '%s'",
serviceName,
depends);
}
for (String volumesFrom : service.getVolumesFrom()) {
Matcher matcher = VOLUME_FROM_PATTERN.matcher(volumesFrom);
checkArgument(
matcher.matches(),
"Service name '%s' in field 'volumes_from' of service '%s' in environment is invalid",
volumesFrom,
serviceName);
String containerFromVolumesFrom = matcher.group("containerName");
checkArgument(
!serviceName.equals(containerFromVolumesFrom),
"Container '%s' can not mount volume from itself",
serviceName);
checkArgument(
servicesNames.contains(containerFromVolumesFrom),
"Service '%s' in environment contains non existing service '%s' in 'volumes_from' field",
serviceName,
containerFromVolumesFrom);
}
checkArgument(
service.getPorts() == null || service.getPorts().isEmpty(),
"Ports binding is forbidden but found in service '%s' of environment",
serviceName);
checkArgument(
service.getVolumes() == null || service.getVolumes().isEmpty(),
"Volumes binding is forbidden but found in service '%s' of environment",
serviceName);
checkArgument(
service.getNetworks() == null || service.getNetworks().isEmpty(),
"Networks configuration is forbidden but found in service '%s' of environment",
serviceName);
}
private static void checkArgument(boolean expression, String error) throws ValidationException {
if (!expression) {
throw new ValidationException(error);
}
}
private static void checkArgument(
boolean expression, String errorMessageTemplate, Object... errorMessageParams)
throws ValidationException {
if (!expression) {
throw new ValidationException(format(errorMessageTemplate, errorMessageParams));
}
}
}

View File

@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.container;
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import static java.lang.String.format;
@ -17,14 +17,14 @@ import com.google.common.collect.Sets;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeService;
/**
* Finds order of Che containers to start that respects dependencies between containers.
@ -32,74 +32,81 @@ import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
* @author Alexander Garagatyi
* @author Alexander Andrienko
*/
public class ContainersStartStrategy {
public class ComposeServicesStartStrategy {
/**
* Resolves order of start for machines in an environment.
* Resolves order of start for compose services in an environment.
*
* @throws ValidationException if order of machines can not be calculated
* @throws ValidationException if order of services can not be calculated
*/
public List<String> order(DockerEnvironment environment) throws ValidationException {
public LinkedHashMap<String, ComposeService> order(Map<String, ComposeService> services)
throws ValidationException {
Map<String, Integer> weights = weightMachines(environment.getContainers());
Map<String, Integer> weights = weightServices(services);
List<String> orderedServicesNames = sortByWeight(weights);
return sortByWeight(weights);
LinkedHashMap<String, ComposeService> orderedServices = new LinkedHashMap<>();
for (String serviceName : orderedServicesNames) {
orderedServices.put(serviceName, services.get(serviceName));
}
return orderedServices;
}
/**
* Returns mapping of names of machines to its weights in dependency graph.
* Returns mapping of names of services to its weights in dependency graph.
*
* @throws ValidationException if weights of machines can not be calculated
* @throws ValidationException if weights of services can not be calculated
*/
private Map<String, Integer> weightMachines(Map<String, DockerContainerConfig> containers)
private Map<String, Integer> weightServices(Map<String, ComposeService> services)
throws ValidationException {
HashMap<String, Integer> weights = new HashMap<>();
// create machines dependency graph
Map<String, Set<String>> dependencies = new HashMap<>(containers.size());
for (Map.Entry<String, DockerContainerConfig> containerEntry : containers.entrySet()) {
DockerContainerConfig container = containerEntry.getValue();
// create services dependency graph
Map<String, Set<String>> dependencies = new HashMap<>(services.size());
for (Map.Entry<String, ComposeService> containerEntry : services.entrySet()) {
ComposeService service = containerEntry.getValue();
Set<String> machineDependencies =
Set<String> serviceDependencies =
Sets.newHashSetWithExpectedSize(
container.getDependsOn().size()
+ container.getLinks().size()
+ container.getVolumesFrom().size());
service.getDependsOn().size()
+ service.getLinks().size()
+ service.getVolumesFrom().size());
for (String dependsOn : container.getDependsOn()) {
for (String dependsOn : service.getDependsOn()) {
checkDependency(
dependsOn, containerEntry.getKey(), containers, "A machine can not depend on itself");
machineDependencies.add(dependsOn);
dependsOn, containerEntry.getKey(), services, "A service can not depend on itself");
serviceDependencies.add(dependsOn);
}
// links also counts as dependencies
for (String link : container.getLinks()) {
for (String link : service.getLinks()) {
String dependency = getContainerFromLink(link);
checkDependency(
dependency, containerEntry.getKey(), containers, "A machine can not link to itself");
machineDependencies.add(dependency);
dependency, containerEntry.getKey(), services, "A service can not link to itself");
serviceDependencies.add(dependency);
}
// volumesFrom also counts as dependencies
for (String volumesFrom : container.getVolumesFrom()) {
for (String volumesFrom : service.getVolumesFrom()) {
String dependency = getContainerFromVolumesFrom(volumesFrom);
checkDependency(
dependency,
containerEntry.getKey(),
containers,
"A machine can not contain 'volumes_from' to itself");
machineDependencies.add(dependency);
services,
"A service can not contain 'volumes_from' to itself");
serviceDependencies.add(dependency);
}
dependencies.put(containerEntry.getKey(), machineDependencies);
dependencies.put(containerEntry.getKey(), serviceDependencies);
}
// Find weight of each machine in graph.
// Weight of machine is calculated as sum of all weights of machines it depends on.
// Find weight of each service in graph.
// Weight of service is calculated as sum of all weights of services it depends on.
// Nodes with no dependencies gets weight 0
while (!dependencies.isEmpty()) {
int previousSize = dependencies.size();
for (Iterator<Map.Entry<String, Set<String>>> it = dependencies.entrySet().iterator();
it.hasNext(); ) {
// process not yet processed machines only
// process not yet processed services only
Map.Entry<String, Set<String>> containerEntry = it.next();
String container = containerEntry.getKey();
Set<String> containerDependencies = containerEntry.getValue();
@ -109,9 +116,9 @@ public class ContainersStartStrategy {
weights.put(container, 0);
it.remove();
} else {
// machine has dependencies - check if it has not weighted dependencies
// service has dependencies - check if it has not weighted dependencies
if (weights.keySet().containsAll(containerDependencies)) {
// all connections are weighted - lets evaluate current machine
// all connections are weighted - lets evaluate current service
Optional<String> maxWeight =
containerDependencies.stream().max(Comparator.comparing(weights::get));
// optional can't be empty because size of the list is checked above
@ -123,7 +130,7 @@ public class ContainersStartStrategy {
}
if (dependencies.size() == previousSize) {
throw new ValidationException(
"Launch order of machines '"
"Launch order of services '"
+ Joiner.on(", ").join(dependencies.keySet())
+ "' can't be evaluated. Circular dependency.");
}
@ -138,7 +145,7 @@ public class ContainersStartStrategy {
if (link != null) {
String[] split = container.split(":");
if (split.length > 2) {
throw new ValidationException(format("Container link '%s' is invalid", link));
throw new ValidationException(format("Service link '%s' is invalid", link));
}
container = split[0];
}
@ -154,8 +161,7 @@ public class ContainersStartStrategy {
if (volumesFrom != null) {
String[] split = container.split(":");
if (split.length > 2) {
throw new ValidationException(
format("Container volumes_from '%s' is invalid", volumesFrom));
throw new ValidationException(format("Service volumes_from '%s' is invalid", volumesFrom));
}
container = split[0];
}
@ -174,7 +180,7 @@ public class ContainersStartStrategy {
private void checkDependency(
String dependency,
String containerName,
Map<String, DockerContainerConfig> containers,
Map<String, ComposeService> containers,
String errorMessage)
throws ValidationException {
if (containerName.equals(dependency)) {
@ -183,7 +189,7 @@ public class ContainersStartStrategy {
if (!containers.containsKey(dependency)) {
throw new ValidationException(
format(
"Dependency '%s' in machine '%s' points to unknown machine.",
"Dependency '%s' in service '%s' points to unknown service.",
dependency, containerName));
}
}

View File

@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.node.TextNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.ComposeEnvironmentParser;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.ComposeEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeService;
/**
@ -54,7 +54,7 @@ import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model
* </pre>
* </ul>
*
* See more for parsing compose file {@link ComposeEnvironmentParser}.
* See more for parsing compose file {@link ComposeEnvironmentFactory}.
*
* <p>Note: this deserializer works for json too.
*

View File

@ -13,7 +13,6 @@ package org.eclipse.che.workspace.infrastructure.docker.environment.compose.dese
import static java.lang.String.format;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
@ -33,7 +32,7 @@ public class EnvironmentDeserializer extends JsonDeserializer<Map<String, String
@Override
public Map<String, String> deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
throws IOException {
Object environment = jsonParser.readValueAs(Object.class);
/* Parse dictionary in view of:

View File

@ -20,13 +20,13 @@ import java.util.stream.Collectors;
*
* @author Alexander Garagatyi
*/
public class ComposeEnvironment {
public class ComposeRecipe {
private String version;
private Map<String, ComposeService> services;
public ComposeEnvironment() {}
public ComposeRecipe() {}
public ComposeEnvironment(ComposeEnvironment environment) {
public ComposeRecipe(ComposeRecipe environment) {
version = environment.getVersion();
if (environment.getServices() != null) {
services =
@ -49,7 +49,7 @@ public class ComposeEnvironment {
this.version = version;
}
public ComposeEnvironment withVersion(String version) {
public ComposeRecipe withVersion(String version) {
this.version = version;
return this;
}
@ -66,7 +66,7 @@ public class ComposeEnvironment {
this.services = services;
}
public ComposeEnvironment withServices(Map<String, ComposeService> services) {
public ComposeRecipe withServices(Map<String, ComposeService> services) {
this.services = services;
return this;
}
@ -74,8 +74,8 @@ public class ComposeEnvironment {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ComposeEnvironment)) return false;
ComposeEnvironment that = (ComposeEnvironment) o;
if (!(o instanceof ComposeRecipe)) return false;
ComposeRecipe that = (ComposeRecipe) o;
return Objects.equals(version, that.version) && Objects.equals(services, that.services);
}
@ -86,6 +86,6 @@ public class ComposeEnvironment {
@Override
public String toString() {
return "ComposeEnvironment{" + "version='" + version + '\'' + ", services=" + services + '}';
return "ComposeRecipe{" + "version='" + version + '\'' + ", services=" + services + '}';
}
}

View File

@ -12,13 +12,16 @@ package org.eclipse.che.workspace.infrastructure.docker.environment.compose.mode
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.deserializer.CommandDeserializer;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.deserializer.EnvironmentDeserializer;
@ -43,7 +46,7 @@ public class ComposeService {
@JsonDeserialize(using = EnvironmentDeserializer.class)
private Map<String, String> environment;
private Set<String> expose = new HashSet<>();
private Set<String> expose;
private List<String> ports;
private Map<String, String> labels;
private List<String> links;
@ -84,6 +87,7 @@ public class ComposeService {
if (service.getLabels() != null) {
labels = new HashMap<>(service.getLabels());
}
this.setExpose(service.getExpose());
if (service.getPorts() != null) {
@ -256,32 +260,43 @@ public class ComposeService {
}
/**
* Expose ports without publishing them to the host machine - theyll only be accessible to linked
* services.
* Immutable expose ports list without publishing them to the host machine - theyll only be
* accessible to linked services.
*
* <p>Only the internal port can be specified. <br>
* Examples:
*
* <ul>
* <li>3000
* <li>8000
* <li>3000/tcp
* <li>8000/udp
* </ul>
*/
public Set<String> getExpose() {
// if (expose == null) {
// expose = new ArrayList<>();
// }
return expose;
if (expose == null) {
return Collections.emptySet();
}
return ImmutableSet.copyOf(expose);
}
public void setExpose(Set<String> expose) {
for(String exp : expose) {
addExpose(exp);
if (expose == null) {
this.expose = null;
} else {
this.expose =
expose
.stream()
.map(this::normalizeExposeValue)
.collect(Collectors.toCollection(HashSet::new));
}
}
public void addExpose(String exposeToAdd) {
expose.add(exposeToAdd.contains("/") ? exposeToAdd : exposeToAdd + "/tcp");
private String normalizeExposeValue(String expose) {
return expose.contains("/") ? expose : expose + "/tcp";
}
public ComposeService withExpose(Set<String> expose) {
setExpose(expose);
return this;
}
/**

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
/** @author Sergii Leshchenko */
public class DockerfileEnvironment extends InternalEnvironment {
public static final String TYPE = "dockerfile";
private final String dockerfileContent;
DockerfileEnvironment(
String dockerfileContent,
InternalRecipe recipe,
Map<String, InternalMachineConfig> machines,
List<Warning> warnings) {
super(recipe, machines, warnings);
this.dockerfileContent = dockerfileContent;
}
/** Returns the content of dockerfile. */
public String getDockerfileContent() {
return dockerfileContent;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DockerfileEnvironment)) {
return false;
}
final DockerfileEnvironment that = (DockerfileEnvironment) obj;
return Objects.equals(dockerfileContent, that.dockerfileContent)
&& Objects.equals(getRecipe(), that.getRecipe())
&& Objects.equals(getMachines(), that.getMachines())
&& Objects.equals(getWarnings(), that.getWarnings());
}
@Override
public int hashCode() {
return Objects.hash(dockerfileContent, getRecipe(), getMachines(), getWarnings());
}
@Override
public String toString() {
return "DockerfileEnvironment{"
+ "dockerfile='"
+ dockerfileContent
+ '\''
+ ", machines="
+ getMachines()
+ ", recipe="
+ getRecipe()
+ ", warnings="
+ getWarnings()
+ '}';
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
/** @author Sergii Leshchenko */
@Singleton
public class DockerfileEnvironmentFactory
extends InternalEnvironmentFactory<DockerfileEnvironment> {
@Inject
public DockerfileEnvironmentFactory(
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
MachineConfigsValidator machinesValidator) {
super(installerRegistry, recipeRetriever, machinesValidator);
}
@Override
protected DockerfileEnvironment doCreate(
InternalRecipe recipe, Map<String, InternalMachineConfig> machines, List<Warning> warnings)
throws InfrastructureException, ValidationException {
if (!DockerfileEnvironment.TYPE.equals(recipe.getType())) {
throw new ValidationException(
format(
"Dockerfile environment parser doesn't support recipe type '%s'", recipe.getType()));
}
String dockerfile = recipe.getContent();
checkArgument(dockerfile != null, "Dockerfile content should not be null.");
return new DockerfileEnvironment(dockerfile, recipe, machines, warnings);
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
/** @author Sergii Leshchenko */
public class DockerImageEnvironment extends InternalEnvironment {
public static final String TYPE = "dockerimage";
private final String dockerImage;
DockerImageEnvironment(
String dockerImage,
InternalRecipe recipe,
Map<String, InternalMachineConfig> machines,
List<Warning> warnings) {
super(recipe, machines, warnings);
this.dockerImage = dockerImage;
}
public String getDockerImage() {
return dockerImage;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DockerImageEnvironment)) {
return false;
}
final DockerImageEnvironment that = (DockerImageEnvironment) obj;
return Objects.equals(dockerImage, that.dockerImage)
&& Objects.equals(getRecipe(), that.getRecipe())
&& Objects.equals(getMachines(), that.getMachines())
&& Objects.equals(getWarnings(), that.getWarnings());
}
@Override
public int hashCode() {
return Objects.hash(dockerImage, getRecipe(), getMachines(), getWarnings());
}
@Override
public String toString() {
return "DockerfileEnvironment{"
+ "dockerImage='"
+ dockerImage
+ '\''
+ ", machines="
+ getMachines()
+ ", recipe="
+ getRecipe()
+ ", warnings="
+ getWarnings()
+ '}';
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.core.model.workspace.config.Environment;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
/** @author Sergii Leshchenko */
@Singleton
public class DockerImageEnvironmentFactory
extends InternalEnvironmentFactory<DockerImageEnvironment> {
@Inject
public DockerImageEnvironmentFactory(
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
MachineConfigsValidator machinesValidator) {
super(installerRegistry, recipeRetriever, machinesValidator);
}
@Override
public DockerImageEnvironment create(Environment sourceEnv)
throws InfrastructureException, ValidationException {
if (sourceEnv.getRecipe().getLocation() != null) {
// move image from location to content
EnvironmentImpl envCopy = new EnvironmentImpl(sourceEnv);
envCopy.getRecipe().setContent(sourceEnv.getRecipe().getLocation());
envCopy.getRecipe().setLocation(null);
return super.create(envCopy);
}
return super.create(sourceEnv);
}
@Override
protected DockerImageEnvironment doCreate(
InternalRecipe recipe, Map<String, InternalMachineConfig> machines, List<Warning> warnings)
throws InfrastructureException, ValidationException {
if (!DockerImageEnvironment.TYPE.equals(recipe.getType())) {
throw new ValidationException(
format(
"Docker image environment parser doesn't support recipe type '%s'",
recipe.getType()));
}
String dockerImage = recipe.getContent();
checkArgument(dockerImage != null, "Docker image should not be null.");
return new DockerImageEnvironment(dockerImage, recipe, machines, warnings);
}
}

View File

@ -22,22 +22,6 @@
<artifactId>infrastructure-docker</artifactId>
<name>Infrastructure :: Docker</name>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@ -118,6 +102,10 @@
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<artifactId>docker-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<artifactId>docker-environment</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* @author Alexander Garagatyi
* @author Alexander Andrienko
*/
public class ComposeInternalEnvironment extends InternalEnvironment {
private final DockerEnvironment composeEnvironment;
public ComposeInternalEnvironment(
Map<String, InternalMachineConfig> machines,
InternalRecipe recipe,
List<Warning> warnings,
DockerEnvironment composeEnvironment)
throws ValidationException {
super(machines, recipe, warnings);
this.composeEnvironment = composeEnvironment;
}
public DockerEnvironment getComposeEnvironment() {
return composeEnvironment;
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.compose;
import static org.eclipse.che.workspace.infrastructure.docker.ArgumentsValidator.checkNotNull;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeEnvironment;
import org.eclipse.che.workspace.infrastructure.docker.environment.compose.model.ComposeService;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerBuildContext;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* @author Alexander Garagatyi
* @author Alexander Andrienko
*/
public class ComposeInternalEnvironmentFactory extends InternalEnvironmentFactory {
public static final String TYPE = "compose";
private static final ObjectMapper YAML_PARSER = new ObjectMapper(new YAMLFactory());
@Inject
public ComposeInternalEnvironmentFactory(
InstallerRegistry installerRegistry, RecipeRetriever recipeRetriever) {
super(installerRegistry, recipeRetriever);
}
@Override
protected InternalEnvironment create(
Map<String, InternalMachineConfig> machines, InternalRecipe recipe, List<Warning> warnings)
throws InfrastructureException, ValidationException {
DockerEnvironment dockerEnvironment =
asDockerEnvironment(parse(recipe.getContent(), recipe.getContentType()));
return new ComposeInternalEnvironment(machines, recipe, warnings, dockerEnvironment);
}
/**
* Parses compose file into Docker Compose model.
*
* @param recipeContent compose file to parse
* @throws ValidationException when environment or environment recipe is invalid
*/
@VisibleForTesting
ComposeEnvironment parse(String recipeContent, String contentType) throws ValidationException {
checkNotNull(recipeContent, "Recipe content should not be null");
checkNotNull(contentType, "Recipe content type should not be null");
ComposeEnvironment composeEnvironment;
switch (contentType) {
case "application/x-yaml":
case "text/yaml":
case "text/x-yaml":
try {
composeEnvironment = YAML_PARSER.readValue(recipeContent, ComposeEnvironment.class);
} catch (IOException e) {
throw new ValidationException(
"Parsing of environment configuration failed. " + e.getLocalizedMessage());
}
break;
default:
throw new ValidationException(
"Provided environment recipe content type '"
+ contentType
+ "' is unsupported. Supported values are: "
+ "application/x-yaml, text/yaml, text/x-yaml");
}
return composeEnvironment;
}
private DockerEnvironment asDockerEnvironment(ComposeEnvironment composeEnvironment) {
Map<String, DockerContainerConfig> containers =
Maps.newHashMapWithExpectedSize(composeEnvironment.getServices().size());
for (Map.Entry<String, ComposeService> composeServiceEntry :
composeEnvironment.getServices().entrySet()) {
ComposeService service = composeServiceEntry.getValue();
DockerContainerConfig cheContainer =
new DockerContainerConfig()
.setCommand(service.getCommand())
.setContainerName(service.getContainerName())
.setDependsOn(service.getDependsOn())
.setEntrypoint(service.getEntrypoint())
.setEnvironment(service.getEnvironment())
.setExpose(service.getExpose())
.setImage(service.getImage())
.setLabels(service.getLabels())
.setLinks(service.getLinks())
.setMemLimit(service.getMemLimit())
.setNetworks(service.getNetworks())
.setPorts(service.getPorts())
.setVolumes(service.getVolumes())
.setVolumesFrom(service.getVolumesFrom());
if (service.getBuild() != null) {
cheContainer.setBuild(
new DockerBuildContext()
.setContext(service.getBuild().getContext())
.setDockerfilePath(service.getBuild().getDockerfile())
.setArgs(service.getBuild().getArgs()));
}
containers.put(composeServiceEntry.getKey(), cheContainer);
}
return new DockerEnvironment().setContainers(containers);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* Dockerfile Internal Environment
*/
public class DockerfileInternalEnvironment extends InternalEnvironment {
private final DockerEnvironment dockerEnvironment;
public DockerfileInternalEnvironment(
Map<String, InternalMachineConfig> machines,
InternalRecipe recipe,
List<Warning> warnings,
DockerEnvironment dockerEnvironment)
throws InfrastructureException, ValidationException {
super(machines, recipe, warnings);
this.dockerEnvironment = dockerEnvironment;
}
public DockerEnvironment getDockerEnvironment() {
return dockerEnvironment;
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerfile;
import static java.lang.String.format;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.workspace.infrastructure.docker.container.ContainersStartStrategy;
import org.eclipse.che.workspace.infrastructure.docker.environment.EnvironmentValidator;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerBuildContext;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* @author Alexander Garagatyi
* @author Alexander Andrienko
*/
public class DockerfileInternalEnvironmentFactory extends InternalEnvironmentFactory {
public static final String TYPE = "dockerfile";
public static final String CONTENT_TYPE = "text/x-dockerfile";
private final EnvironmentValidator validator;
private final ContainersStartStrategy startStrategy;
@Inject
public DockerfileInternalEnvironmentFactory(
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
EnvironmentValidator validator,
ContainersStartStrategy startStrategy) {
super(installerRegistry, recipeRetriever);
this.startStrategy = startStrategy;
this.validator = validator;
}
@Override
protected InternalEnvironment create(
Map<String, InternalMachineConfig> machines, InternalRecipe recipe, List<Warning> warnings)
throws InfrastructureException, ValidationException {
DockerEnvironment dockerEnvironment = dockerEnv(machines, recipe);
validator.validate(machines, dockerEnvironment);
return new DockerfileInternalEnvironment(machines, recipe, warnings, dockerEnvironment);
}
private DockerEnvironment dockerEnv(
Map<String, InternalMachineConfig> machines, InternalRecipe recipe)
throws ValidationException {
if (!TYPE.equals(recipe.getType())) {
throw new ValidationException(
format(
"Dockerfile environment parser doesn't support recipe type '%s'", recipe.getType()));
}
if (!CONTENT_TYPE.equals(recipe.getContentType())) {
throw new ValidationException(
format(
"Content type '%s' of recipe of environment is unsupported."
+ " Supported values are: text/x-dockerfile",
recipe.getContentType()));
}
Map.Entry<String, InternalMachineConfig> entry = machines.entrySet().iterator().next();
String machineName = entry.getKey();
InternalMachineConfig machineConfig = entry.getValue();
DockerEnvironment cheContainerEnv = new DockerEnvironment();
DockerContainerConfig container = new DockerContainerConfig();
cheContainerEnv.getContainers().put(entry.getKey(), container);
container.setBuild(new DockerBuildContext().setDockerfileContent(recipe.getContent()));
for (ServerConfig server : machineConfig.getServers().values()) {
container.addExpose(server.getPort());
}
if (machineConfig.getAttributes().containsKey("memoryLimitBytes")) {
try {
container.setMemLimit(
Long.parseLong(machineConfig.getAttributes().get("memoryLimitBytes")));
} catch (NumberFormatException e) {
throw new ValidationException(
format("Value of attribute 'memoryLimitBytes' of machine '%s' is illegal", machineName));
}
}
return cheContainerEnv;
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* Dockerimage Internal Environment
*/
public class DockerimageInternalEnvironment extends InternalEnvironment {
private final DockerEnvironment dockerEnvironment;
public DockerimageInternalEnvironment(
Map<String, InternalMachineConfig> machines,
InternalRecipe recipe,
List<Warning> warnings,
DockerEnvironment dockerEnvironment)
throws InfrastructureException, ValidationException {
super(machines, recipe, warnings);
this.dockerEnvironment = dockerEnvironment;
}
public DockerEnvironment getDockerEnvironment() {
return dockerEnvironment;
}
}

View File

@ -1,105 +0,0 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.docker.environment.dockerimage;
import static java.lang.String.format;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.core.model.workspace.config.Environment;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.workspace.infrastructure.docker.environment.EnvironmentValidator;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerContainerConfig;
import org.eclipse.che.workspace.infrastructure.docker.model.DockerEnvironment;
/**
* @author Alexander Garagatyi
* @author Alexander Andrienko
*/
public class DockerimageInternalEnvironmentFactory extends InternalEnvironmentFactory {
public static final String TYPE = "dockerimage";
private final EnvironmentValidator validator;
@Inject
public DockerimageInternalEnvironmentFactory(
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
EnvironmentValidator validator) {
super(installerRegistry, recipeRetriever);
this.validator = validator;
}
@Override
public InternalEnvironment create(final Environment environment)
throws InfrastructureException, ValidationException {
EnvironmentImpl envCopy = new EnvironmentImpl(environment);
if (envCopy.getRecipe().getLocation() != null) {
// move image from location to content
envCopy.getRecipe().setContent(environment.getRecipe().getLocation());
envCopy.getRecipe().setLocation(null);
}
return super.create(envCopy);
}
@Override
protected InternalEnvironment create(
Map<String, InternalMachineConfig> machines, InternalRecipe recipe, List<Warning> warnings)
throws InfrastructureException, ValidationException {
DockerEnvironment dockerEnvironment = dockerEnv(machines, recipe);
validator.validate(machines, dockerEnvironment);
return new DockerimageInternalEnvironment(machines, recipe, warnings, dockerEnvironment);
}
private DockerEnvironment dockerEnv(
Map<String, InternalMachineConfig> machines, InternalRecipe recipe)
throws ValidationException {
// empty list of machines is not expected, no needs to check for next()
Map.Entry<String, InternalMachineConfig> entry = machines.entrySet().iterator().next();
String machineName = entry.getKey();
InternalMachineConfig machineConfig = entry.getValue();
DockerEnvironment dockerEnv = new DockerEnvironment();
DockerContainerConfig container = new DockerContainerConfig();
dockerEnv.getContainers().put(machineName, container);
container.setImage(recipe.getContent());
for (ServerConfig server : machineConfig.getServers().values()) {
container.addExpose(server.getPort());
}
if (machineConfig.getAttributes().containsKey("memoryLimitBytes")) {
try {
container.setMemLimit(
Long.parseLong(machineConfig.getAttributes().get("memoryLimitBytes")));
} catch (NumberFormatException e) {
throw new ValidationException(
format("Value of attribute 'memoryLimitBytes' of machine '%s' is illegal", machineName));
}
}
return dockerEnv;
}
}

View File

@ -10,13 +10,16 @@
*/
package org.eclipse.che.workspace.infrastructure.docker.model;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.che.commons.annotation.Nullable;
/**
@ -37,7 +40,7 @@ public class DockerContainerConfig {
private List<String> securityOpt;
private List<String> entrypoint;
private Map<String, String> environment;
private Set<String> expose = new HashSet<>();
private Set<String> expose;
private List<String> extraHosts;
private String id;
private String image;
@ -82,7 +85,9 @@ public class DockerContainerConfig {
if (container.getEnvironment() != null) {
environment = new HashMap<>(container.getEnvironment());
}
setExpose(container.getExpose());
if (container.getExtraHosts() != null) {
extraHosts = new ArrayList<>(container.getExtraHosts());
}
@ -277,19 +282,27 @@ public class DockerContainerConfig {
* </ul>
*/
public Set<String> getExpose() {
return expose;
if (expose == null) {
return Collections.emptySet();
}
return ImmutableSet.copyOf(expose);
}
public DockerContainerConfig setExpose(Set<String> expose) {
for(String exp : expose) {
addExpose(exp);
if (expose == null) {
this.expose = null;
} else {
this.expose =
expose
.stream()
.map(this::normalizeExposeValue)
.collect(Collectors.toCollection(HashSet::new));
}
return this;
}
public DockerContainerConfig addExpose(String exposeToAdd) {
expose.add(exposeToAdd.contains("/") ? exposeToAdd : exposeToAdd + "/tcp");
return this;
private String normalizeExposeValue(String expose) {
return expose.contains("/") ? expose : expose + "/tcp";
}
/**

View File

@ -13,21 +13,46 @@ package org.eclipse.che.workspace.infrastructure.docker.model;
import static java.util.stream.Collectors.toMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
/**
* Description of docker container environment as representation of environment of machines in Che.
*
* @author Alexander Garagatyi
*/
public class DockerEnvironment {
public class DockerEnvironment extends InternalEnvironment {
private Map<String, DockerContainerConfig> containers;
private String network;
public DockerEnvironment() {}
public DockerEnvironment(DockerEnvironment environment) {
public DockerEnvironment(
InternalRecipe recipe, Map<String, InternalMachineConfig> machines, List<Warning> warnings) {
super(recipe, machines, warnings);
}
public DockerEnvironment(
InternalRecipe recipe,
Map<String, InternalMachineConfig> machines,
List<Warning> warnings,
Map<String, DockerContainerConfig> containers,
String network)
throws InfrastructureException {
super(recipe, machines, warnings);
this.containers = containers;
this.network = network;
}
public DockerEnvironment(DockerEnvironment environment) throws InfrastructureException {
super(environment.getRecipe(), environment.getMachines(), environment.getWarnings());
if (environment.getContainers() != null) {
containers =
environment
@ -39,10 +64,10 @@ public class DockerEnvironment {
}
}
/** Mapping of containers names to containers configuration. */
/** Ordered mapping of containers names to containers configuration. */
public Map<String, DockerContainerConfig> getContainers() {
if (containers == null) {
containers = new HashMap<>();
containers = new LinkedHashMap<>();
}
return containers;
}
@ -66,20 +91,39 @@ public class DockerEnvironment {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DockerEnvironment)) return false;
if (this == o) {
return true;
}
if (!(o instanceof DockerEnvironment)) {
return false;
}
DockerEnvironment that = (DockerEnvironment) o;
return Objects.equals(getContainers(), that.getContainers())
&& Objects.equals(getNetwork(), that.getNetwork());
&& Objects.equals(getNetwork(), that.getNetwork())
&& Objects.equals(getRecipe(), that.getRecipe())
&& Objects.equals(getMachines(), that.getMachines())
&& Objects.equals(getWarnings(), that.getWarnings());
}
@Override
public int hashCode() {
return Objects.hash(getContainers(), getNetwork());
return Objects.hash(getContainers(), getNetwork(), getMachines(), getRecipe(), getWarnings());
}
@Override
public String toString() {
return "DockerEnvironment{" + "containers=" + containers + ", network='" + network + '\'' + '}';
return "DockerEnvironment{"
+ "containers="
+ containers
+ ", network='"
+ network
+ '\''
+ ", machines="
+ getMachines()
+ ", recipe="
+ getRecipe()
+ ", warnings="
+ getWarnings()
+ '}';
}
}

View File

@ -25,6 +25,7 @@
<name>Infrastructure :: Docker :: Parent</name>
<modules>
<module>docker-client</module>
<module>environment</module>
<module>infrastructure</module>
</modules>
<properties>

View File

@ -723,6 +723,11 @@
<artifactId>docker-client</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<artifactId>docker-environment</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<artifactId>infrastructure-docker</artifactId>