Make variable expansion work for environment variables in k8s.
K8s does the expansion only if it already knows about the variable being expanded. This means we have to sort the environment variable list prior to sending it to k8s in such a way that vars that reference others always follow the referenced ones. Signed-off-by: Lukas Krejci <lkrejci@redhat.com>6.19.x
parent
3bef71020f
commit
f66e967339
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.commons.lang;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This is an implementation of a stable topological sort on a directed graph.
|
||||
*
|
||||
* <p>The sorting does not pose any requirements on the types being sorted. Instead, the
|
||||
* implementation merely requires a function to provide it with a set of predecessors of a certain
|
||||
* "node". The implementation of the function is completely in the hands of the caller.
|
||||
*
|
||||
* <p>Additionally, a function to extract an "ID" from a node is required. The reasoning behind this
|
||||
* is that it usually is easier to work with identifiers when establishing the predecessors than
|
||||
* with the full node instances. That said, nothing prevents the caller from using the actual node
|
||||
* instance as its ID if the caller so wishes. The consequence of this is that, as a side-effect of
|
||||
* the sorting, the duplicates, as determined by the equality of {@code ID} instances, are removed
|
||||
* from the resulting sorted list.
|
||||
*
|
||||
* @param <N> the type of nodes
|
||||
* @param <ID> the type of a node ID
|
||||
*/
|
||||
public final class TopologicalSort<N, ID> {
|
||||
|
||||
private final Function<N, ID> identityExtractor;
|
||||
private final Function<N, Set<ID>> directPredecessorsExtractor;
|
||||
|
||||
/**
|
||||
* @param identityExtractor a function to extract some kind of value uniquely identifying a node
|
||||
* amongst the others.
|
||||
* @param directPredecessorsExtractor a function returning a list of ids of direct predecessors of
|
||||
* a node
|
||||
*/
|
||||
public TopologicalSort(
|
||||
Function<N, ID> identityExtractor, Function<N, Set<ID>> directPredecessorsExtractor) {
|
||||
this.identityExtractor = identityExtractor;
|
||||
this.directPredecessorsExtractor = directPredecessorsExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the function for determining the predecessors of the nodes, return the list of the nodes
|
||||
* in topological order. I.e. all predecessors will be placed sooner in the list than their
|
||||
* successors. Note that the input collection is assumed to contain no duplicate entries as
|
||||
* determined by the equality of the {@code ID} type. If such duplicates are present in the input
|
||||
* collection, the output list will only contain the first instance of the duplicates from the
|
||||
* input collection.
|
||||
*
|
||||
* <p>The implemented sort algorithm is stable. If there is no relationship between 2 nodes, they
|
||||
* retain the relative position to each other as they had in the provided collection (e.g. if "a"
|
||||
* preceded "b" in the original collection and there is no relationship between them (as
|
||||
* determined by the predecessor function), the "a" will still precede "b" in the resulting list.
|
||||
* Other nodes may be inserted in between them though in the result).
|
||||
*
|
||||
* <p>The cycles in the graph determined by the predecessor function are ignored and nodes in the
|
||||
* cycle are placed into the output list in the source order.
|
||||
*
|
||||
* @param nodes the collection of nodes
|
||||
* @return the list of nodes sorted in topological order
|
||||
*/
|
||||
public List<N> sort(Collection<N> nodes) {
|
||||
// the linked hashmap is important to retain the original order of elements unless required
|
||||
// by the dependencies between nodes
|
||||
LinkedHashMap<ID, NodeInfo<ID, N>> nodeInfos = new LinkedHashMap<>(nodes.size());
|
||||
List<NodeInfo<ID, N>> results = new ArrayList<>(nodes.size());
|
||||
|
||||
int pos = 0;
|
||||
boolean needsSorting = false;
|
||||
for (N node : nodes) {
|
||||
ID nodeID = identityExtractor.apply(node);
|
||||
// we need the set to be modifiable, so let's make our own
|
||||
Set<ID> preds = new HashSet<>(directPredecessorsExtractor.apply(node));
|
||||
needsSorting |= !preds.isEmpty();
|
||||
|
||||
NodeInfo<ID, N> nodeInfo = nodeInfos.computeIfAbsent(nodeID, __ -> new NodeInfo<>());
|
||||
nodeInfo.id = nodeID;
|
||||
nodeInfo.predecessors = preds;
|
||||
nodeInfo.sourcePosition = pos++;
|
||||
nodeInfo.node = node;
|
||||
|
||||
for (ID pred : preds) {
|
||||
// note that this means that we're inserting the nodeinfos into the map in an incorrect
|
||||
// order and will have to sort them in the source order before we do the actual topo sort.
|
||||
// We take that cost because we gamble on there being no dependencies in the nodes as a
|
||||
// common case.
|
||||
NodeInfo<ID, N> predNode = nodeInfos.computeIfAbsent(pred, __ -> new NodeInfo<>());
|
||||
if (predNode.successors == null) {
|
||||
predNode.successors = new HashSet<>();
|
||||
}
|
||||
predNode.successors.add(nodeID);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSorting) {
|
||||
// because of the predecessors, we have put the nodeinfos in the map in an incorrect order.
|
||||
// we need to correct that before we try to sort...
|
||||
TreeSet<NodeInfo<ID, N>> tmp = new TreeSet<>(Comparator.comparingInt(a -> a.sourcePosition));
|
||||
tmp.addAll(nodeInfos.values());
|
||||
nodeInfos.clear();
|
||||
tmp.forEach(ni -> nodeInfos.put(ni.id, ni));
|
||||
|
||||
// now we're ready to produce the results
|
||||
sort(nodeInfos, results);
|
||||
} else {
|
||||
// we don't need to sort, but we need to keep the expected behavior of removing the duplicates
|
||||
results = new ArrayList<>(nodeInfos.values());
|
||||
}
|
||||
|
||||
return results.stream().map(ni -> ni.node).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void sort(LinkedHashMap<ID, NodeInfo<ID, N>> nodes, List<NodeInfo<ID, N>> results) {
|
||||
|
||||
while (!nodes.isEmpty()) {
|
||||
NodeInfo<ID, N> curr = removeFirstIndependent(nodes);
|
||||
if (curr != null) {
|
||||
// yay, simple. Just add the found independent node to the results.
|
||||
results.add(curr);
|
||||
} else {
|
||||
// ok, there is a cycle in the graph. Let's remove all the nodes in the first cycle we find
|
||||
// from our predecessors map, add them to the result in their original order and try to
|
||||
// continue normally
|
||||
|
||||
// find the first cycle in the predecessors (in the original list order)
|
||||
Iterator<NodeInfo<ID, N>> nexts = nodes.values().iterator();
|
||||
List<NodeInfo<ID, N>> cycle;
|
||||
do {
|
||||
curr = nexts.next();
|
||||
cycle = findCycle(curr, nodes);
|
||||
} while (cycle.isEmpty() && nexts.hasNext());
|
||||
|
||||
// If we ever find a graph that doesn't have any independent node, yet we fail to find a
|
||||
// cycle in it, the universe must be broken.
|
||||
if (cycle.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Failed to find a cycle in a graph that doesn't seem to have any independent"
|
||||
+ " node. This should never happen. Please file a bug. Current state of the"
|
||||
+ " sorting is: nodes=%s, results=%s",
|
||||
nodes.toString(), results.toString()));
|
||||
}
|
||||
|
||||
cycle.sort(Comparator.comparingInt(a -> a.sourcePosition));
|
||||
|
||||
for (NodeInfo<ID, N> n : cycle) {
|
||||
removePredecessorMapping(nodes, n);
|
||||
results.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removePredecessorMapping(Map<ID, NodeInfo<ID, N>> nodes, NodeInfo<ID, N> node) {
|
||||
if (node.successors != null) {
|
||||
for (ID succ : node.successors) {
|
||||
NodeInfo<ID, N> succNode = nodes.get(succ);
|
||||
if (succNode != null) {
|
||||
succNode.predecessors.remove(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.remove(node.id);
|
||||
}
|
||||
|
||||
private List<NodeInfo<ID, N>> findCycle(NodeInfo<ID, N> node, Map<ID, NodeInfo<ID, N>> nodes) {
|
||||
// bail out quickly if there are no preds - should be fairly common occurrence hopefully
|
||||
Set<ID> preds = node.predecessors;
|
||||
if (preds == null || preds.isEmpty()) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
List<NodeInfo<ID, N>> ret = new ArrayList<>();
|
||||
List<ID> todo = new ArrayList<>(preds);
|
||||
|
||||
Set<ID> visited = new HashSet<>();
|
||||
|
||||
while (!todo.isEmpty()) {
|
||||
ID n = todo.remove(0);
|
||||
|
||||
if (visited.contains(n)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.add(n);
|
||||
|
||||
NodeInfo<ID, N> predNode = nodes.get(n);
|
||||
if (predNode != null) {
|
||||
todo.addAll(predNode.predecessors);
|
||||
ret.add(predNode);
|
||||
|
||||
if (predNode.equals(node)) {
|
||||
// we found the cycle to our original node
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
private NodeInfo<ID, N> removeFirstIndependent(Map<ID, NodeInfo<ID, N>> nodes) {
|
||||
Iterator<Entry<ID, NodeInfo<ID, N>>> it = nodes.entrySet().iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
Entry<ID, NodeInfo<ID, N>> e = it.next();
|
||||
if (e.getValue().predecessors.isEmpty()) {
|
||||
it.remove();
|
||||
NodeInfo<ID, N> ret = e.getValue();
|
||||
removePredecessorMapping(nodes, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class NodeInfo<ID, N> {
|
||||
private ID id;
|
||||
private int sourcePosition;
|
||||
private Set<ID> predecessors;
|
||||
private Set<ID> successors;
|
||||
private N node;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.commons.lang;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class TopologicalSortTest {
|
||||
|
||||
@Test(dataProvider = "sortingCases")
|
||||
public void shouldApplySorting(List<Var> list, List<Var> expectedSorting) throws Exception {
|
||||
// when
|
||||
sort(list);
|
||||
|
||||
// then
|
||||
assertEquals(list, expectedSorting);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] sortingCases() {
|
||||
return new Object[][] {
|
||||
// keep order if no dependencies
|
||||
new Object[] {
|
||||
vars(var('b', "."), var('a', "."), var('c', ".")),
|
||||
vars(var('b', "."), var('a', "."), var('c', "."))
|
||||
},
|
||||
|
||||
// sort dependents after dependencies
|
||||
new Object[] {
|
||||
vars(var('a', "b"), var('b', "."), var('c', ".")),
|
||||
vars(var('b', "."), var('a', "b"), var('c', "."))
|
||||
},
|
||||
|
||||
// sort dependents after dependencies, multiple dependencies
|
||||
new Object[] {
|
||||
vars(var('a', "bc"), var('b', "."), var('c', ".")),
|
||||
vars(var('b', "."), var('c', "."), var('a', "bc"))
|
||||
},
|
||||
|
||||
// sort dependents after dependencies, check dependee moved after dependencies
|
||||
new Object[] {
|
||||
vars(var('d', "."), var('a', "c"), var('b', "."), var('c', ".")),
|
||||
vars(var('d', "."), var('b', "."), var('c', "."), var('a', "c"))
|
||||
},
|
||||
|
||||
// test the robustness against cycles
|
||||
|
||||
// dependent directly on itself
|
||||
new Object[] {vars(var('a', "a")), vars(var('a', "a"))},
|
||||
|
||||
// two mutually dependent nodes
|
||||
new Object[] {vars(var('a', "b"), var('b', "a")), vars(var('a', "b"), var('b', "a"))},
|
||||
|
||||
// independent node mixed inside a cycle
|
||||
new Object[] {
|
||||
vars(var('b', "c"), var('d', "."), var('a', "b"), var('c', "a")),
|
||||
vars(var('d', "."), var('b', "c"), var('a', "b"), var('c', "a"))
|
||||
},
|
||||
|
||||
// cycle (a-b-c) with one of the nodes also depending on another node (a-d),
|
||||
// mixed with a "chain" (f-e-b)
|
||||
new Object[] {
|
||||
vars(
|
||||
var('f', "e"),
|
||||
var('a', "bd"),
|
||||
var('b', "c"),
|
||||
var('e', "b"),
|
||||
var('c', "a"),
|
||||
var('d', ".")),
|
||||
vars(
|
||||
var('d', "."),
|
||||
var('a', "bd"),
|
||||
var('b', "c"),
|
||||
var('c', "a"),
|
||||
var('e', "b"),
|
||||
var('f', "e"))
|
||||
},
|
||||
|
||||
// removes duplicates
|
||||
new Object[] {
|
||||
vars(var('a', "."), var('a', "."), var('b', ".")), vars(var('a', "."), var('b', "."))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Var var(char name, String value) {
|
||||
return new Var(name, value.chars().mapToObj(c -> (char) c).collect(toSet()));
|
||||
}
|
||||
|
||||
private static List<Var> vars(Var... vars) {
|
||||
return Stream.of(vars).collect(toList());
|
||||
}
|
||||
|
||||
private static void sort(List<Var> vars) {
|
||||
Function<Var, Character> idFunction = v -> v.name;
|
||||
Function<Var, Set<Character>> predecessors =
|
||||
v -> v.dependencies.stream().filter(Character::isAlphabetic).collect(toSet());
|
||||
|
||||
TopologicalSort<Var, Character> sort = new TopologicalSort<>(idFunction, predecessors);
|
||||
List<Var> newVars = sort.sort(vars);
|
||||
vars.clear();
|
||||
vars.addAll(newVars);
|
||||
}
|
||||
|
||||
private static final class Var {
|
||||
|
||||
private final char name;
|
||||
private final Set<Character> dependencies;
|
||||
|
||||
private Var(char name, Set<Character> dependencies) {
|
||||
this.name = name;
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Var var = (Var) o;
|
||||
return name == var.name && dependencies.equals(var.dependencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, dependencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Var{" + "name='" + name + '\'' + ", deps='" + dependencies + '\'' + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,19 +11,26 @@
|
|||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.provision.env;
|
||||
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.api.core.model.workspace.config.MachineConfig;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.commons.annotation.Traced;
|
||||
import org.eclipse.che.commons.lang.TopologicalSort;
|
||||
import org.eclipse.che.commons.tracing.TracingTags;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars;
|
||||
|
||||
/**
|
||||
* Converts environment variables in {@link MachineConfig} to Kubernetes environment variables.
|
||||
|
|
@ -32,6 +39,10 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.Configurati
|
|||
*/
|
||||
@Singleton
|
||||
public class EnvVarsConverter implements ConfigurationProvisioner {
|
||||
|
||||
private final TopologicalSort<EnvVar, String> topoSort =
|
||||
new TopologicalSort<>(EnvVar::getName, EnvVars::extractReferencedVariables);
|
||||
|
||||
@Override
|
||||
@Traced
|
||||
public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
|
||||
|
|
@ -43,13 +54,29 @@ public class EnvVarsConverter implements ConfigurationProvisioner {
|
|||
for (Container container : pod.getSpec().getContainers()) {
|
||||
String machineName = Names.machineName(pod, container);
|
||||
InternalMachineConfig machineConf = k8sEnv.getMachines().get(machineName);
|
||||
machineConf
|
||||
.getEnv()
|
||||
.forEach(
|
||||
(key, value) -> {
|
||||
container.getEnv().removeIf(env -> key.equals(env.getName()));
|
||||
container.getEnv().add(new EnvVar(key, value, null));
|
||||
});
|
||||
|
||||
// we need to combine the env vars from the machine config with the variables already
|
||||
// present in the container. Let's key the variables by name and use the map for merging
|
||||
Map<String, EnvVar> envVars =
|
||||
machineConf
|
||||
.getEnv()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> new EnvVar(e.getKey(), e.getValue(), null))
|
||||
.collect(toMap(EnvVar::getName, identity()));
|
||||
|
||||
// the env vars defined in our machine config take precedence over the ones already defined
|
||||
// in the container, if any
|
||||
container.getEnv().forEach(v -> envVars.putIfAbsent(v.getName(), v));
|
||||
|
||||
// The environment variable interpolation only works if a variable that is referenced
|
||||
// is already defined earlier in the list of environment variables.
|
||||
// We need to produce a list where variables that reference others always appear later
|
||||
// in the list.
|
||||
|
||||
List<EnvVar> sorted = topoSort.sort(envVars.values());
|
||||
|
||||
container.setEnv(sorted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.util;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Utility class for dealing with environment variables */
|
||||
public class EnvVars {
|
||||
|
||||
private static final Pattern REFERENCE_PATTERN = Pattern.compile("\\$\\(\\w+\\)");
|
||||
|
||||
private EnvVars() {}
|
||||
|
||||
/**
|
||||
* Looks at the value of the provided environment variable and returns a set of environment
|
||||
* variable references in the Kubernetes convention of {@literal $(VAR_NAME)}.
|
||||
*
|
||||
* <p>See <a
|
||||
* href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#envvar-v1-core">API
|
||||
* docs</a> and/or <a
|
||||
* href="https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/#using-environment-variables-inside-of-your-config">documentation</a>.
|
||||
*
|
||||
* @param var the environment variable to analyze
|
||||
* @return a set of variable references, never null
|
||||
*/
|
||||
public static Set<String> extractReferencedVariables(EnvVar var) {
|
||||
String val = var.getValue();
|
||||
|
||||
Matcher matcher = REFERENCE_PATTERN.matcher(val);
|
||||
|
||||
// let's just keep the initial size small, because usually there are not that many references
|
||||
// present.
|
||||
Set<String> ret = new HashSet<>(2);
|
||||
|
||||
while (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
|
||||
// the variable reference can be escaped using a double $, e.g. $$(VAR) is not a reference
|
||||
if (start > 0 && val.charAt(start - 1) == '$') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extract the variable name out of the reference $(NAME) -> NAME
|
||||
String refName = matcher.group().substring(2, matcher.group().length() - 1);
|
||||
ret.add(refName);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.provision.env;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class EnvVarsConverterTest {
|
||||
|
||||
private static final String PRE_EXISTING_VAR = "VAR_THAT_EXISTS";
|
||||
private static final String PRE_EXISTING_VAR_VALUE = "jmenuju se VAR";
|
||||
private static final String PRE_EXISTING_VAR_NEW_VALUE = "my name is VAR";
|
||||
private static final String A_VAR = "A";
|
||||
private static final String A_VAL = "$(C)";
|
||||
private static final String B_VAR = "B";
|
||||
private static final String B_VAL = "b";
|
||||
private static final String C_VAR = "C";
|
||||
private static final String C_VAL = "c";
|
||||
|
||||
private KubernetesEnvironment environment;
|
||||
private RuntimeIdentity identity;
|
||||
private Container testContainer;
|
||||
private InternalMachineConfig machine;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
testContainer = new Container();
|
||||
|
||||
PodSpec podSpec = new PodSpec();
|
||||
podSpec.setContainers(singletonList(testContainer));
|
||||
|
||||
ObjectMeta podMeta = new ObjectMeta();
|
||||
podMeta.setName("pod");
|
||||
|
||||
Pod pod = new Pod();
|
||||
pod.setSpec(podSpec);
|
||||
pod.setMetadata(podMeta);
|
||||
|
||||
Map<String, Pod> pods = new HashMap<>();
|
||||
pods.put("pod", pod);
|
||||
|
||||
environment = KubernetesEnvironment.builder().setPods(pods).build();
|
||||
|
||||
machine = new InternalMachineConfig();
|
||||
|
||||
environment.setMachines(
|
||||
Collections.singletonMap(Names.machineName(podMeta, testContainer), machine));
|
||||
|
||||
identity = new RuntimeIdentityImpl("wsId", "blah", "bleh");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProvisionEnvironmentVariablesSorted() throws InfrastructureException {
|
||||
// given
|
||||
List<EnvVar> preExistingEnvironment = new ArrayList<>();
|
||||
preExistingEnvironment.add(new EnvVar(PRE_EXISTING_VAR, PRE_EXISTING_VAR_VALUE, null));
|
||||
testContainer.setEnv(preExistingEnvironment);
|
||||
|
||||
machine.getEnv().put(PRE_EXISTING_VAR, PRE_EXISTING_VAR_NEW_VALUE);
|
||||
machine.getEnv().put(A_VAR, A_VAL);
|
||||
machine.getEnv().put(B_VAR, B_VAL);
|
||||
machine.getEnv().put(C_VAR, C_VAL);
|
||||
|
||||
// when
|
||||
EnvVarsConverter converter = new EnvVarsConverter();
|
||||
converter.provision(environment, identity);
|
||||
|
||||
// then
|
||||
EnvVar expectedA = new EnvVar(A_VAR, A_VAL, null);
|
||||
EnvVar expectedB = new EnvVar(B_VAR, B_VAL, null);
|
||||
EnvVar expectedC = new EnvVar(C_VAR, C_VAL, null);
|
||||
EnvVar expectedPreExisting = new EnvVar(PRE_EXISTING_VAR, PRE_EXISTING_VAR_NEW_VALUE, null);
|
||||
|
||||
List<EnvVar> expectedOrder = asList(expectedB, expectedC, expectedA, expectedPreExisting);
|
||||
|
||||
assertEquals(4, testContainer.getEnv().size());
|
||||
assertEquals(expectedOrder, testContainer.getEnv());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.util;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars.extractReferencedVariables;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class EnvVarsTest {
|
||||
|
||||
@Test(dataProvider = "detectReferencesTestValues")
|
||||
public void shouldDetectReferences(String value, Set<EnvVar> expected, String caseName) {
|
||||
assertEquals(
|
||||
extractReferencedVariables(var("name", value)), expected, caseName + ": just value");
|
||||
assertEquals(
|
||||
extractReferencedVariables(var("name", "v" + value)),
|
||||
expected,
|
||||
caseName + ": value with prefix");
|
||||
assertEquals(
|
||||
extractReferencedVariables(var("name", "v" + value + "v")),
|
||||
expected,
|
||||
caseName + ": value with prefix and postfix");
|
||||
assertEquals(
|
||||
extractReferencedVariables(var("name", value + "v")),
|
||||
expected,
|
||||
caseName + ": value with postfix");
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] detectReferencesTestValues() {
|
||||
return new Object[][] {
|
||||
new Object[] {"value", emptySet(), "no refs"},
|
||||
new Object[] {"$(NO_REF", emptySet(), "unclosed ref"},
|
||||
new Object[] {"$$(NO_REF)", emptySet(), "escaped ref"},
|
||||
new Object[] {"$NO_REF)", emptySet(), "invalid start ref"},
|
||||
new Object[] {"$(NO REF)", emptySet(), "invalid name ref"},
|
||||
new Object[] {"$(REF)", singleton("REF"), "valid ref"}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDetectMultipleReferences() throws Exception {
|
||||
// given
|
||||
EnvVar envVar = var("a", "$(b) $(c) $$(d)");
|
||||
|
||||
// when
|
||||
Set<String> refs = extractReferencedVariables(envVar);
|
||||
|
||||
// then
|
||||
assertEquals(refs, new HashSet<>(Arrays.asList("b", "c")));
|
||||
}
|
||||
|
||||
private static EnvVar var(String name, String value) {
|
||||
return new EnvVar(name, value, null);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue