/******************************************************************************* * 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; } }