package io.cattle.platform.docker.transform;
import static io.cattle.platform.core.constants.InstanceConstants.*;
import static io.cattle.platform.docker.constants.DockerInstanceConstants.*;
import io.cattle.platform.core.addon.BlkioDeviceOption;
import io.cattle.platform.core.addon.LogConfig;
import io.cattle.platform.core.addon.Ulimit;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.NetworkConstants;
import io.cattle.platform.core.constants.VolumeConstants;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.util.SystemLabels;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.util.type.CollectionUtils;
import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException;
import io.github.ibuildthecloud.gdapi.util.ResponseCodes;
import io.github.ibuildthecloud.gdapi.validation.ValidationErrorCodes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.Capability;
import com.github.dockerjava.api.model.ContainerConfig;
import com.github.dockerjava.api.model.Device;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.LxcConf;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.api.model.Ports.Binding;
import com.github.dockerjava.api.model.RestartPolicy;
import com.github.dockerjava.api.model.VolumeBind;
public class DockerTransformerImpl implements DockerTransformer {
private static final String HOST_CONFIG = "HostConfig";
private static final String CONFIG = "Config";
private static final String IMAGE_PREFIX = "docker:";
private static final String IMAGE_KIND_PATTERN = "^(sim|docker):.*";
private static final String READ_WRITE = "rw";
private static final String READ_ONLY = "ro";
private static final String ACCESS_MODE = "RW";
private static final String DRIVER = "Driver";
private static final String DEST = "Destination";
private static final String SRC = "Source";
private static final String NAME = "Name";
private static final String READ_IOPS= "BlkioDeviceReadIOps";
private static final String WRITE_IOPS= "BlkioDeviceWriteIOps";
private static final String READ_BPS= "BlkioDeviceReadBps";
private static final String WRITE_BPS= "BlkioDeviceWriteBps";
private static final String WEIGHT = "BlkioWeightDevice";
@Inject
JsonMapper jsonMapper;
@Override
@SuppressWarnings("unchecked")
public List<DockerInspectTransformVolume> transformVolumes(Map<String, Object> fromInspect, List<Object> mounts) {
List<DockerInspectTransformVolume> volumes = transformMounts(mounts);
if (volumes != null) {
return volumes;
}
volumes = new ArrayList<DockerInspectTransformVolume>();
InspectContainerResponse inspect = transformInspect(fromInspect);
HostConfig hostConfig = inspect.getHostConfig();
VolumeBind[] volumeBinds = null;
try {
volumeBinds = inspect.getVolumes();
} catch (NullPointerException e) {
// A bug in docker-java can cause this.
volumeBinds = new VolumeBind[0];
}
Set<String> binds = bindSet(hostConfig.getBinds());
Map<String, String> rw = rwMap((Map<String, Boolean>) fromInspect.get("VolumesRW"));
for (VolumeBind vb : volumeBinds) {
String am = rw.containsKey(vb.getContainerPath()) ? rw.get(vb.getContainerPath()) : READ_WRITE;
boolean isBindMound = binds.contains(vb.getContainerPath());
String uri = String.format(VolumeConstants.URI_FORMAT, VolumeConstants.FILE_PREFIX, vb.getHostPath());
volumes.add(new DockerInspectTransformVolume(vb.getContainerPath(), uri, am, isBindMound, null, vb.getContainerPath(), null));
}
return volumes;
}
@SuppressWarnings("unchecked")
protected List<DockerInspectTransformVolume> transformMounts(List<Object> mounts) {
if (mounts == null) {
return null;
}
List<DockerInspectTransformVolume> volumes = new ArrayList<DockerInspectTransformVolume>();
for (Object mount : mounts) {
Map<String, Object> mountObj = (Map<String, Object>)mount;
String am = ((boolean)mountObj.get(ACCESS_MODE)) ? "rw" : "ro";
String dr = (String)mountObj.get(DRIVER);
String containerPath = (String)mountObj.get(DEST);
String hostPath = (String)mountObj.get(SRC);
String name = (String)mountObj.get(NAME);
String externalId = null;
if (StringUtils.isEmpty(name)) {
name = hostPath;
} else {
externalId = name;
}
if ("rancher-cni".equals(name)) {
continue;
}
boolean isBindMount = (dr == null);
// TODO When we implement proper volume deletion in py-agent, we can change this so that if the driver is explicitly, local, we don't
// use 'file://'
String uriPrefix = StringUtils.isEmpty(dr) || StringUtils.equals(dr, VolumeConstants.LOCAL_DRIVER) ? VolumeConstants.FILE_PREFIX : dr;
String uri = String.format(VolumeConstants.URI_FORMAT, uriPrefix, hostPath);
volumes.add(new DockerInspectTransformVolume(containerPath, uri, am, isBindMount, dr, name, externalId));
}
return volumes;
}
Map<String, String> rwMap(Map<String, Boolean> volumeRws) {
// TODO When this bug is fixed, switch to using java-docker's volumesRW
// https://github.com/docker-java/docker-java/issues/205
Map<String, String> rwMap = new HashMap<String, String>();
if (volumeRws == null) {
return rwMap;
}
for (Map.Entry<String, Boolean> volume : volumeRws.entrySet()) {
Boolean readWrite = volume.getValue();
String perms = readWrite ? READ_WRITE : READ_ONLY;
rwMap.put(volume.getKey(), perms);
}
return rwMap;
}
Set<String> bindSet(String[] binds) {
Set<String> hostBindMounts = new HashSet<String>();
if (binds == null)
return hostBindMounts;
for (String bindMount : binds) {
String[] parts = bindMount.split(":");
hostBindMounts.add(parts[1]);
}
return hostBindMounts;
}
@Override
public void transform(Map<String, Object> fromInspect, Instance instance) {
InspectContainerResponse inspect = transformInspect(fromInspect);
ContainerConfig containerConfig = inspect.getConfig();
HostConfig hostConfig = inspect.getHostConfig();
instance.setExternalId(inspect.getId());
instance.setKind(KIND_CONTAINER);
setName(instance, inspect, fromInspect);
if (containerConfig != null) {
instance.setHostname((String) fixEmptyValue(containerConfig.getHostName()));
setField(instance, FIELD_MEMORY, containerConfig.getMemoryLimit());
setField(instance, FIELD_CPU_SET, containerConfig.getCpuset());
setField(instance, FIELD_CPU_SHARES, containerConfig.getCpuShares());
setField(instance, FIELD_MEMORY_SWAP, containerConfig.getMemorySwap());
setField(instance, FIELD_DOMAIN_NAME, containerConfig.getDomainName());
setField(instance, FIELD_USER, containerConfig.getUser());
setField(instance, FIELD_TTY, containerConfig.isTty());
setField(instance, FIELD_STDIN_OPEN, containerConfig.isStdinOpen());
setImage(instance, containerConfig.getImage());
setField(instance, FIELD_WORKING_DIR, containerConfig.getWorkingDir());
setEnvironment(instance, containerConfig.getEnv());
setCommand(instance, containerConfig.getCmd());
setListField(instance, FIELD_ENTRY_POINT, containerConfig.getEntrypoint());
setField(instance, FIELD_VOLUME_DRIVER, fromInspect, "Config", "VolumeDriver");
setField(instance, FIELD_STOP_SIGNAL, fromInspect, CONFIG, "StopSignal");
setHealthConfig(fromInspect, instance);
}
if (containerConfig != null && hostConfig != null) {
setVolumes(instance, containerConfig.getVolumes(), hostConfig.getBinds());
setPorts(instance, safeGetExposedPorts(containerConfig), hostConfig.getPortBindings());
}
if (hostConfig != null) {
setField(instance, FIELD_PRIVILEGED, hostConfig.isPrivileged());
setField(instance, FIELD_PUBLISH_ALL_PORTS, hostConfig.isPublishAllPorts());
setLxcConf(instance, hostConfig.getLxcConf());
setListField(instance, FIELD_DNS, hostConfig.getDns());
setListField(instance, FIELD_DNS_SEARCH, hostConfig.getDnsSearch());
setCapField(instance, FIELD_CAP_ADD, hostConfig.getCapAdd());
setCapField(instance, FIELD_CAP_DROP, hostConfig.getCapDrop());
setRestartPolicy(instance, hostConfig.getRestartPolicy());
setDevices(instance, hostConfig.getDevices());
setMemoryReservation(fromInspect, instance);
setField(instance, FIELD_BLKIO_WEIGHT, fromInspect, HOST_CONFIG, "BlkioWeight");
setField(instance, FIELD_CGROUP_PARENT, fromInspect, HOST_CONFIG, "CgroupParent");
setField(instance, FIELD_CPU_PERIOD, fromInspect, HOST_CONFIG, "CpuPeriod");
setField(instance, FIELD_CPU_QUOTA, fromInspect, HOST_CONFIG, "CpuQuota");
setField(instance, FIELD_CPUSET_MEMS, fromInspect, HOST_CONFIG, "CpusetMems");
setField(instance, FIELD_DNS_OPT, fromInspect, HOST_CONFIG, "DnsOptions");
setField(instance, FIELD_GROUP_ADD, fromInspect, HOST_CONFIG, "GroupAdd");
setField(instance, FIELD_KERNEL_MEMORY, fromInspect, HOST_CONFIG, "KernelMemory");
setField(instance, FIELD_MEMORY_SWAPPINESS, fromInspect, HOST_CONFIG, "MemorySwappiness");
setField(instance, FIELD_OOMKILL_DISABLE, fromInspect, HOST_CONFIG, "OomKillDisable");
setField(instance, FIELD_SHM_SIZE, fromInspect, HOST_CONFIG, "ShmSize");
setField(instance, FIELD_TMPFS, fromInspect, HOST_CONFIG, "Tmpfs");
setField(instance, FIELD_UTS, fromInspect, HOST_CONFIG, "UTSMode");
setField(instance, FIELD_IPC_MODE, fromInspect, HOST_CONFIG, "IpcMode");
setField(instance, FIELD_SYSCTLS, fromInspect, HOST_CONFIG, "Sysctls");
setField(instance, FIELD_OOM_SCORE_ADJ, fromInspect, HOST_CONFIG, "OomScoreAdj");
setField(instance, FIELD_ISOLATION, fromInspect, HOST_CONFIG, "Isolation");
}
setBlkioDeviceOptionss(instance, fromInspect);
setUlimit(instance, fromInspect);
setNetworkMode(instance, containerConfig, hostConfig);
setField(instance, FIELD_SECURITY_OPT, fromInspect, HOST_CONFIG, "SecurityOpt");
setField(instance, FIELD_PID_MODE, fromInspect, HOST_CONFIG, "PidMode");
setField(instance, FIELD_READ_ONLY, fromInspect, HOST_CONFIG, "ReadonlyRootfs");
setField(instance, FIELD_EXTRA_HOSTS, fromInspect, HOST_CONFIG, "ExtraHosts");
setFieldIfNotEmpty(instance, FIELD_CPU_SHARES, fromInspect, HOST_CONFIG, "CpuShares");
setFieldIfNotEmpty(instance, FIELD_CPU_SET, fromInspect, HOST_CONFIG, "CpusetCpus");
setFieldIfNotEmpty(instance, FIELD_MEMORY, fromInspect, HOST_CONFIG, "Memory");
setFieldIfNotEmpty(instance, FIELD_MEMORY_SWAP, fromInspect, HOST_CONFIG, "MemorySwap");
setLogConfig(instance, fromInspect);
setLabels(instance, fromInspect);
// Currently not implemented: VolumesFrom, Links,
// Consider: AttachStdin, AttachStdout, AttachStderr, StdinOnce,
}
void setMemoryReservation(Map<String, Object> fromInspect, Instance instance) {
Object memRes = CollectionUtils.getNestedValue(fromInspect, HOST_CONFIG, "MemoryReservation");
if (memRes != null && memRes instanceof Number) {
instance.setMemoryReservation(((Number)memRes).longValue());
}
}
void setHealthConfig(Map<String, Object> fromInspect, Instance instance) {
Object healthCmd = CollectionUtils.getNestedValue(fromInspect, CONFIG, "Healthcheck", "Test");
Object healthInterval = CollectionUtils.getNestedValue(fromInspect, CONFIG, "Healthcheck", "Interval");
Object healthTimeout = CollectionUtils.getNestedValue(fromInspect, CONFIG, "Healthcheck", "Timeout");
Object healthRetries = CollectionUtils.getNestedValue(fromInspect, CONFIG, "Healthcheck", "Retries");
if (healthCmd != null) {
setField(instance, "healthCmd", healthCmd);
}
if (healthInterval != null) {
setField(instance, "healthInterval", healthInterval);
}
if (healthTimeout != null) {
setField(instance, "healthTimeout", healthTimeout);
}
if (healthTimeout != null) {
setField(instance, "healthRetries", healthRetries);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
void setBlkioDeviceOptionss(Instance instance, Map<String, Object> fromInspect) {
/*
* We're coverting from docker's structure of:
* {BlkioDeviceReadIOps: [{Path: <device>, Rate: 1000} ... ], BlkioDeviceWriteIOps: [{Path: <device>, Rate: 1000} ... ] ... }
* to cattle's structure of:
* {blkioDeviceOptions: {<device>: {readIops: 1000m writeIops: 1000 ... } ... }
*/
List<String> fields = Arrays.asList(READ_IOPS, WRITE_IOPS, READ_BPS, WRITE_BPS, WEIGHT);
Map<String, BlkioDeviceOption> target = new HashMap<>();
for (String field : fields) {
List<Map> deviceOptions = null;
try {
deviceOptions = (List<Map>)CollectionUtils.toList(CollectionUtils.getNestedValue(fromInspect, HOST_CONFIG, field));
} catch (Exception e) {
continue;
}
if (deviceOptions == null || deviceOptions.isEmpty()) {
continue;
}
for (Map deviceOption : deviceOptions) {
String path = null;
Integer value = null;
try {
path = (String)deviceOption.get("Path");
value = (Integer)(field == WEIGHT ? deviceOption.get("Weight") : deviceOption.get("Rate"));
} catch (Exception e) {
// just skip it
}
if (path != null && value != null) {
BlkioDeviceOption targetDevOpt = target.get(path);
if (targetDevOpt == null) {
targetDevOpt = new BlkioDeviceOption();
target.put(path, targetDevOpt);
}
switch (field) {
case READ_IOPS:
targetDevOpt.setReadIops(value);
break;
case WRITE_IOPS:
targetDevOpt.setWriteIops(value);
break;
case READ_BPS:
targetDevOpt.setReadBps(value);
break;
case WRITE_BPS:
targetDevOpt.setWriteBps(value);
break;
case WEIGHT:
targetDevOpt.setWeight(value);
break;
}
}
}
}
if (!target.isEmpty()) {
setField(instance, FIELD_BLKIO_DEVICE_OPTIONS, target);
}
}
void setNetworkMode(Instance instance, ContainerConfig containerConfig, HostConfig hostConfig) {
if(DataAccessor.fields(instance).withKey(FIELD_NETWORK_MODE).get() != null)
return;
String netMode = null;
if (containerConfig != null && containerConfig.isNetworkDisabled()) {
netMode = NetworkConstants.NETWORK_MODE_NONE;
} else if (hostConfig != null) {
String inspectNetMode = hostConfig.getNetworkMode();
if (NetworkConstants.NETWORK_MODE_BRIDGE.equals(inspectNetMode) ||
NetworkConstants.NETWORK_MODE_HOST.equals(inspectNetMode) ||
NetworkConstants.NETWORK_MODE_NONE.equals(inspectNetMode)) {
netMode = inspectNetMode;
} else if (NetworkConstants.NETWORK_MODE_DEFAULT.equals(inspectNetMode) || StringUtils.isBlank(inspectNetMode)) {
netMode = NetworkConstants.NETWORK_MODE_BRIDGE;
} else if (StringUtils.startsWith(inspectNetMode, NetworkConstants.NETWORK_MODE_CONTAINER)) {
throw new ClientVisibleException(ResponseCodes.UNPROCESSABLE_ENTITY, ValidationErrorCodes.INVALID_OPTION,
"Transformer API does not support container network mode.", null);
} else {
throw new ClientVisibleException(ResponseCodes.UNPROCESSABLE_ENTITY, ValidationErrorCodes.INVALID_OPTION,
"Unrecognized network mode: " + inspectNetMode, null);
}
}
setField(instance, FIELD_NETWORK_MODE, netMode);
}
void setField(Instance instance, String field, Map<String, Object> fromInspect, String... keys) {
Object l = CollectionUtils.getNestedValue(fromInspect, keys);
setField(instance, field, l);
}
void setFieldIfNotEmpty(Instance instance, String field, Map<String, Object> fromInspect, String... keys) {
Object l = CollectionUtils.getNestedValue(fromInspect, keys);
if (!isEmptyValue(l)) {
setField(instance, field, l);
}
}
@SuppressWarnings("unchecked")
void setLogConfig(Instance instance, Map<String, Object> fromInspect) {
Object type = CollectionUtils.getNestedValue(fromInspect, HOST_CONFIG, "LogConfig", "Type");
Object config = CollectionUtils.getNestedValue(fromInspect, HOST_CONFIG, "LogConfig", "Config");
if (type == null && config == null) {
return;
}
LogConfig logConfig = new LogConfig();
if (type != null) {
logConfig.setDriver(type.toString());
}
if (config instanceof Map) {
logConfig.setConfig((Map<String, String>)config);
}
setField(instance, FIELD_LOG_CONFIG, logConfig);
}
@SuppressWarnings("unchecked")
void setUlimit(Instance instance, Map<String, Object> fromInspect) {
Object ulimits = CollectionUtils.getNestedValue(fromInspect, HOST_CONFIG, "Ulimits");
if (ulimits == null) {
return;
}
List<Ulimit> ret = new ArrayList<>();
if (ulimits instanceof List) {
for (Object ulimit : (List<Object>) ulimits) {
if (ulimit instanceof Map) {
Ulimit l = new Ulimit();
Map<String, Object> temp = (Map<String, Object>) ulimit;
if (temp.get("Name") instanceof String) {
l.setName(temp.get("Name").toString());
}
if (temp.get("Hard") instanceof Number) {
l.setHard(((Number) temp.get("Hard")).intValue());
}
if (temp.get("Soft") instanceof Integer) {
l.setSoft(((Number) temp.get("Soft")).intValue());
}
if (!l.getName().isEmpty()) {
ret.add(l);
}
}
}
}
if (!ret.isEmpty()) {
setField(instance, FIELD_ULIMITS, ret);
}
}
@Override
@SuppressWarnings({ "rawtypes" })
public void setLabels(Instance instance, Map<String, Object> fromInspect) {
// Labels not yet implemented in docker-java. Need to use the raw map
Object l = CollectionUtils.getNestedValue(fromInspect, "Config", "Labels");
Map<String, Object> cleanedLabels = new HashMap<String, Object>();
if (l instanceof Map) {
Map labels = (Map)l;
for (Object key : labels.keySet()) {
if (key == null)
continue;
Object value = labels.get(key);
if (value == null)
value = "";
cleanedLabels.put(key.toString(), value.toString());
}
}
Map<String, Object> labels = DataAccessor.fieldMap(instance, FIELD_LABELS);
labels.putAll(cleanedLabels);
setField(instance, FIELD_LABELS, labels);
}
void setImage(Instance instance, String image) {
if (StringUtils.isNotBlank(image) && !image.matches(IMAGE_KIND_PATTERN)) {
image = IMAGE_PREFIX + image;
}
setField(instance, FIELD_IMAGE_UUID, image);
}
void setName(Instance instance, InspectContainerResponse inspect, Map<String, Object> fromInspect) {
String name = inspect.getName();
Object displayNameLabel = CollectionUtils.getNestedValue(fromInspect, "Config", "Labels", SystemLabels.LABEL_DISPLAY_NAME);
if (displayNameLabel == null) {
displayNameLabel = DataAccessor.fieldMap(instance, FIELD_LABELS).get(SystemLabels.LABEL_DISPLAY_NAME);
}
if (displayNameLabel != null) {
String displayName = displayNameLabel.toString();
if (StringUtils.isNotBlank(displayName)) {
name = displayName;
}
}
if (name != null) {
name = name.replaceFirst("/", "");
instance.setName(name);
}
}
private ExposedPort[] safeGetExposedPorts(ContainerConfig containerConfig) {
try {
return containerConfig.getExposedPorts();
} catch (NullPointerException e) {
// Bug in docker-java doesn't account for this property being null
return null;
}
}
void setDevices(Instance instance, Device[] devices) {
if (devices == null) {
devices = new Device[0];
}
List<String> instanceDevices = new ArrayList<String>();
for (Device d : devices) {
StringBuilder fullDevice = new StringBuilder(d.getPathOnHost()).append(":").append(d.getPathInContainer()).append(":");
if (StringUtils.isEmpty(d.getcGroupPermissions())) {
fullDevice.append("rwm");
} else {
fullDevice.append(d.getcGroupPermissions());
}
instanceDevices.add(fullDevice.toString());
}
setField(instance, FIELD_DEVICES, instanceDevices);
}
void setRestartPolicy(Instance instance, RestartPolicy restartPolicy) {
if (restartPolicy == null || StringUtils.isEmpty(restartPolicy.getName())) {
return;
}
io.cattle.platform.core.addon.RestartPolicy rp = new io.cattle.platform.core.addon.RestartPolicy();
rp.setMaximumRetryCount(restartPolicy.getMaximumRetryCount());
rp.setName(restartPolicy.getName());
setField(instance, FIELD_RESTART_POLICY, rp);
}
void setCapField(Instance instance, String field, Capability[] caps) {
if (caps == null) {
caps = new Capability[0];
}
List<String> list = new ArrayList<String>();
for (Capability cap : caps) {
list.add(cap.toString());
}
setField(instance, field, list);
}
void setLxcConf(Instance instance, LxcConf[] lxcConf) {
if (lxcConf == null || lxcConf.length == 0) {
lxcConf = new LxcConf[0];
}
Map<String, String> instanceLxcConf = new HashMap<String, String>();
for (LxcConf lxc : lxcConf) {
instanceLxcConf.put(lxc.getKey(), lxc.getValue());
}
setField(instance, FIELD_LXC_CONF, instanceLxcConf);
}
void setPorts(Instance instance, ExposedPort[] exposedPorts, Ports portBindings) {
if (exposedPorts == null) {
exposedPorts = new ExposedPort[0];
}
List<String> ports = new ArrayList<String>();
for (ExposedPort ep : exposedPorts) {
String port = ep.toString();
Binding[] bindings = portBindings == null || portBindings.getBindings() == null ? null : portBindings.getBindings().get(ep);
if (bindings != null && bindings.length > 0) {
for (Binding b : bindings) {
// HostPort should really be a string, not an int. Somehow empty string becomes 0
if (b.getHostPort() != null && b.getHostPort() != 0) {
String fullPort = b.getHostPort() + ":" + port;
ports.add(fullPort);
} else {
ports.add(port);
}
}
}
}
setField(instance, FIELD_PORTS, ports);
}
void setVolumes(Instance instance, Map<String, ?> volumes, String[] binds) {
List<String> dataVolumes = new ArrayList<String>();
if (volumes != null) {
dataVolumes.addAll(volumes.keySet());
}
if (binds != null) {
dataVolumes.addAll(Arrays.asList(binds));
}
setField(instance, InstanceConstants.FIELD_DATA_VOLUMES, dataVolumes);
}
void setListField(Instance instance, String field, String[] value) {
if (value == null) {
value = new String[0];
}
List<String> list = new ArrayList<String>(Arrays.asList(value));
setField(instance, field, list);
}
void setCommand(Instance instance, String[] cmd) {
if (cmd == null) {
cmd = new String[0];
}
List<String> args = new ArrayList<String>();
args.addAll(Arrays.asList(cmd));
setField(instance, FIELD_COMMAND, args);
}
void setEnvironment(Instance instance, String[] env) {
if (env == null) {
env = new String[0];
}
Map<String, String> envMap = new HashMap<String, String>();
for (String e : env) {
String[] kvp = e.split("=", 2);
if (kvp.length == 2) {
envMap.put(kvp[0], kvp[1]);
} else if (kvp.length == 1) {
// TODO Change the Rancher API to support valueless environment variables.
// -e FOO and -e FOO="" are not the same thing.
envMap.put(kvp[0], "");
}
}
setField(instance, FIELD_ENVIRONMENT, envMap);
}
private void setField(Instance instance, String field, Object fieldValue) {
fieldValue = fixEmptyValue(fieldValue);
DataAccessor.fields(instance).withKey(field).set(fieldValue);
}
@SuppressWarnings({ "rawtypes", "unused" })
private Object fixEmptyValue(Object fieldValue) {
if (fieldValue instanceof String && StringUtils.isEmpty((String)fieldValue)) {
return null;
} else if (fieldValue instanceof Number && ((Number)fieldValue).longValue() == 0L) {
return null;
} else if (fieldValue instanceof List && fieldValue == null) {
return new ArrayList();
} else if (fieldValue instanceof Map && fieldValue == null) {
return new HashMap();
}
return fieldValue;
}
private boolean isEmptyValue(Object fieldValue) {
return fieldValue == null ||
(fieldValue instanceof String && StringUtils.isEmpty((String) fieldValue)) ||
(fieldValue instanceof Number && ((Number) fieldValue).longValue() == 0L) ||
(fieldValue instanceof List && fieldValue == null) ||
(fieldValue instanceof Map && fieldValue == null);
}
InspectContainerResponse transformInspect(Map<String, Object> inspect) {
return jsonMapper.convertValue(inspect, InspectContainerResponse.class);
}
}