/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.workspace.server;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import javax.inject.Singleton;
import static com.google.common.primitives.Ints.tryParse;
import static java.lang.String.format;
/**
* Adapts an old workspace configuration object format to a new format.
*
* <pre>
* Old workspace config format:
* {
* "name" : "default",
* "defaultEnv" : "dev-env",
* "description" : "This is workspace description",
* "environments": [
* {
* "name": "dev-env",
* "machineConfigs": [
* {
* "name": "dev", <- becomes environment defined machine
* "limits": {
* "ram": 2048 <- in bytes
* },
* "source": { <- goes to recipe
* "location": "https://somewhere/Dockerfile",
* "type": "dockerfile"
* },
* "type": "docker", <- will be defined by environment recipe type
* "dev": true, <- if agents contain 'org.eclipse.che.ws-agent'
* "envVariables" : {
* "env1" : "value1",
* "env2" : "value2
* },
* "servers" : [ <- goes to machine definition
* {
* {
* "ref" : "some_reference",
* "port" : "9090/udp",
* "protocol" : "some_protocol",
* "path" : "/some/path"
* }
* }
* ]
* }
* ]
* }
* ],
* }
*
* New workspace config format:
* {
* "name" : "default",
* "defaultEnv" : "dev-env",
* "description" : "This is workspace description",
* "environments" : {
* "dev-env" : {
* "recipe" : {
* "type" : "dockerfile",
* "contentType" : "text/x-dockerfile",
* "location" : "https://somewhere/Dockerfile"
* },
* "machines" : {
* "dev-machine" : {
* "agents" : [ "org.eclipse.che.terminal", "org.eclipse.che.ws-agent", "org.eclipse.che.ssh" ],
* "servers" : {
* "some_reference" : {
* "port" : "9090/udp",
* "protocol" : "some_protocol",
* "properties" : {
* "prop1" : "value1"
* }
* }
* },
* "attributes" : {
* "memoryLimitBytes" : "2147483648"
* }
* }
* }
* }
* }
* }
* </pre>
*
* @author Yevhenii Voevodin
*/
@Singleton
public class WorkspaceConfigJsonAdapter {
/**
* Adapts given workspace configuration environments to a new format,
* if it contains environments array, otherwise does nothing.
*
* @throws IllegalArgumentException
* if the old format is bad, and can't be converted to a new one
*/
public void adaptModifying(JsonObject conf) {
if (conf.has("environments") && conf.get("environments").isJsonArray()) {
final JsonObject newEnvironments = new JsonObject();
for (JsonElement environmentEl : conf.get("environments").getAsJsonArray()) {
if (!environmentEl.isJsonObject()) {
throw new IllegalArgumentException("Bad format, expected environment to be json object");
}
final JsonObject env = environmentEl.getAsJsonObject();
if (!env.has("name") || env.get("name").isJsonNull()) {
throw new IllegalArgumentException("Bad format, expected environment to provide a name");
}
final String envName = env.get("name").getAsString();
newEnvironments.add(envName, asEnvironment(env, envName));
}
conf.add("environments", newEnvironments);
}
}
private static JsonObject asEnvironment(JsonObject env, String envName) {
final JsonObject devMachine = findDevMachine(env);
// check environment is a valid old format environment
if (devMachine == null) {
throw new IllegalArgumentException("Bad format, expected dev-machine to be present in environment " + envName);
}
if (!devMachine.has("name") || devMachine.get("name").isJsonNull()) {
throw new IllegalArgumentException("Bad format, expected dev-machine to provide a name");
}
if (!devMachine.has("source") || !devMachine.get("source").isJsonObject()) {
throw new IllegalArgumentException("Bad format, expected dev-machine to provide a source");
}
final JsonObject source = devMachine.getAsJsonObject("source");
if (!source.has("type") || source.get("type").isJsonObject()) {
throw new IllegalArgumentException("Bad format, expected dev-machine to provide a source with a type");
}
// convert dev-machine to a new format
final JsonObject newMachine = new JsonObject();
// dev-machine agents
final JsonArray agents = new JsonArray();
agents.add(new JsonPrimitive("org.eclipse.che.exec"));
agents.add(new JsonPrimitive("org.eclipse.che.terminal"));
agents.add(new JsonPrimitive("org.eclipse.che.ws-agent"));
agents.add(new JsonPrimitive("org.eclipse.che.ssh"));
newMachine.add("agents", agents);
// dev-machine ram
if (devMachine.has("limits")) {
if (!devMachine.get("limits").isJsonObject()) {
throw new IllegalArgumentException(format("Bad limits format in the dev-machine of '%s' environment", envName));
}
final JsonObject limits = devMachine.getAsJsonObject("limits");
if (limits.has("ram")) {
final Integer ram = tryParse(limits.get("ram").getAsString());
if (ram == null || ram < 0) {
throw new IllegalArgumentException(format("Bad format, ram of dev-machine in environment '%s' " +
"must an unsigned integer value", envName));
}
final JsonObject attributes = new JsonObject();
attributes.addProperty("memoryLimitBytes", Long.toString(1024L * 1024L * ram));
newMachine.add("attributes", attributes);
}
}
// dev-machine servers
if (devMachine.has("servers")) {
if (!devMachine.get("servers").isJsonArray()) {
throw new IllegalArgumentException("Bad format of servers in dev-machine, servers must be json array");
}
final JsonObject newServersObj = new JsonObject();
for (JsonElement serversEl : devMachine.get("servers").getAsJsonArray()) {
final JsonObject oldServerObj = serversEl.getAsJsonObject();
if (!oldServerObj.has("ref")) {
throw new IllegalArgumentException("Bad format of server in dev-machine, server must contain ref");
}
final String ref = oldServerObj.get("ref").getAsString();
oldServerObj.remove("ref");
if (oldServerObj.has("path")) {
final JsonObject props = new JsonObject();
props.add("path", oldServerObj.get("path"));
oldServerObj.add("properties", props);
oldServerObj.remove("path");
}
newServersObj.add(ref, oldServerObj);
}
newMachine.add("servers", newServersObj);
}
// create an environment recipe
final JsonObject envRecipe = new JsonObject();
final String type = source.get("type").getAsString();
switch (type) {
case "recipe":
case "dockerfile":
envRecipe.addProperty("type", "dockerfile");
envRecipe.addProperty("contentType", "text/x-dockerfile");
if (source.has("content") && !source.get("content").isJsonNull()) {
envRecipe.addProperty("content", source.get("content").getAsString());
} else if (source.has("location") && !source.get("location").isJsonNull()) {
envRecipe.addProperty("location", source.get("location").getAsString());
} else {
throw new IllegalArgumentException("Bad format, expected dev-machine source with type 'dockerfile' " +
"to provide either 'content' or 'location'");
}
break;
case "image":
if (!source.has("location") || source.get("location").isJsonNull()) {
throw new IllegalArgumentException("Bad format, expected dev-machine source with type 'image' " +
"to provide image 'location'");
}
envRecipe.addProperty("type", "dockerimage");
envRecipe.addProperty("location", source.get("location").getAsString());
break;
default:
throw new IllegalArgumentException(format("Bad format, dev-machine source type '%s' is not supported", type));
}
// create a new environment
final JsonObject newEnv = new JsonObject();
newEnv.add("recipe", envRecipe);
final JsonObject machines = new JsonObject();
machines.add(devMachine.get("name").getAsString(), newMachine);
newEnv.add("machines", machines);
return newEnv;
}
/** Searches for dev-machine in default environment. */
public static JsonObject findDevMachine(JsonObject env) {
if (env.has("machineConfigs") && env.get("machineConfigs").isJsonArray()) {
for (JsonElement machineCfgEl : env.getAsJsonArray("machineConfigs")) {
final JsonObject machineCfgObj = machineCfgEl.getAsJsonObject();
if (machineCfgObj.has("dev") && machineCfgObj.get("dev").getAsBoolean()) {
return machineCfgObj;
}
}
}
return null;
}
}