Skip to content

Commit

Permalink
Merge branch '821-manifest-inheritance' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
nebhale committed Oct 18, 2017
2 parents 34af299 + 98a207e commit 58daa1f
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import org.cloudfoundry.util.tuple.Consumer2;
Expand All @@ -34,11 +37,10 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -67,10 +69,7 @@ private ApplicationManifestUtils() {
* @return the resolved manifests
*/
public static List<ApplicationManifest> read(Path path) {
return doRead(path.toAbsolutePath())
.values().stream()
.map(ApplicationManifest.Builder::build)
.collect(Collectors.toList());
return doRead(path.toAbsolutePath());
}

/**
Expand Down Expand Up @@ -184,51 +183,48 @@ private static void asString(JsonNode payload, String key, Consumer<String> cons
as(payload, key, JsonNode::asText, consumer);
}

private static JsonNode deserialize(Path path) {
private static ObjectNode deserialize(Path path) {
AtomicReference<ObjectNode> root = new AtomicReference<>();

try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
return OBJECT_MAPPER.readTree(in);
root.set(((ObjectNode) OBJECT_MAPPER.readTree(in)));
} catch (IOException e) {
throw Exceptions.propagate(e);
}
}

private static Map<String, ApplicationManifest.Builder> doRead(Path path) {
Map<String, ApplicationManifest.Builder> applicationManifests = new TreeMap<>();

JsonNode root = deserialize(path);
asString(root.get(), "inherit", inherit -> root.set(merge(deserialize(path.getParent().resolve(inherit)), root.get())));

asString(root, "inherit", inherit -> applicationManifests.putAll(doRead(path.getParent().resolve(inherit))));
return root.get();
}

applicationManifests
.forEach((name, builder) -> applicationManifests.put(name, toApplicationManifest(root, builder, path)));
private static List<ApplicationManifest> doRead(Path path) {
JsonNode root = deserialize(path);

ApplicationManifest template = getTemplate(path, root);

Optional.ofNullable(root.get("applications"))
return Optional.ofNullable(root.get("applications"))
.map(ApplicationManifestUtils::streamOf)
.ifPresent(applications -> applications
.forEach(application -> {
String name = getName(application);
ApplicationManifest.Builder builder = getBuilder(applicationManifests, template, name);

applicationManifests.put(name, toApplicationManifest(application, builder, path));
}));

return applicationManifests;
}

private static ApplicationManifest.Builder getBuilder(Map<String, ApplicationManifest.Builder> applicationManifests, ApplicationManifest template, String name) {
ApplicationManifest.Builder builder = applicationManifests.get(name);
if (builder == null) {
builder = ApplicationManifest.builder().from(template);
}
return builder;
.orElseGet(Stream::empty)
.map(application -> {
String name = getName(application);
return toApplicationManifest(application, ApplicationManifest.builder().from(template), path)
.name(name)
.build();
})
.collect(Collectors.toList());
}

private static String getName(JsonNode raw) {
return Optional.ofNullable(raw.get("name")).map(JsonNode::asText).orElseThrow(() -> new IllegalStateException("Application does not contain required 'name' value"));
}

private static ObjectNode getNamedObject(ArrayNode array, String name) {
return (ObjectNode) streamOf(array)
.filter(object -> object.has("name") && name.equals(object.get("name").asText()))
.findFirst()
.orElseGet(array::addObject);
}

private static Route getRoute(JsonNode raw) {
String route = Optional.ofNullable(raw.get("route")).map(JsonNode::asText).orElseThrow(() -> new IllegalStateException("Route does not contain required 'route' value"));
return Route.builder().route(route).build();
Expand All @@ -240,6 +236,34 @@ private static ApplicationManifest getTemplate(Path path, JsonNode root) {
.build();
}

private static ObjectNode merge(ObjectNode first, ObjectNode second) {
streamOf(second.fields())
.forEach(field -> {
String key = field.getKey();
JsonNode value = field.getValue();

if (value instanceof ValueNode) {
first.replace(key, value);
} else if (value instanceof ArrayNode) {
streamOf(value)
.forEach(element -> {
JsonNode name = element.get("name");

if (name != null) {
ObjectNode named = getNamedObject(first.withArray(key), name.asText());
merge(named, (ObjectNode) element);
} else {
first.withArray(key).add(element);
}
});
} else if (value instanceof ObjectNode) {
first.with(key).setAll((ObjectNode) value);
}
});

return first;
}

private static <T> Stream<T> streamOf(Iterator<T> iterator) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -201,48 +202,48 @@ public void readCommonAndInherit() throws IOException {
.build(),
ApplicationManifest.builder()
.name("charlie-application-2")
.buildpack("delta-buildpack")
.command("delta-command")
.disk(-3)
.healthCheckHttpEndpoint("delta-health-check-http-endpoint")
.healthCheckType(NONE)
.instances(-3)
.memory(-3)
.noRoute(true)
.path(Paths.get("/delta-path"))
.randomRoute(true)
.buildpack("alternate-buildpack")
.command("alternate-command")
.disk(-2)
.healthCheckHttpEndpoint("alternate-health-check-http-endpoint")
.healthCheckType(PORT)
.instances(-2)
.memory(-2)
.noRoute(false)
.path(Paths.get("/alternate-path"))
.randomRoute(false)
.route(Route.builder()
.route("charlie-route-1")
.build())
.route(Route.builder()
.route("charlie-route-2")
.build())
.route(Route.builder()
.route("alternate-route-1")
.route("delta-route-1")
.build())
.route(Route.builder()
.route("alternate-route-2")
.route("delta-route-2")
.build())
.route(Route.builder()
.route("delta-route-1")
.route("alternate-route-1")
.build())
.route(Route.builder()
.route("delta-route-2")
.route("alternate-route-2")
.build())
.stack("delta-stack")
.timeout(-3)
.stack("alternate-stack")
.timeout(-2)
.environmentVariable("CHARLIE_KEY_1", "charlie-value-1")
.environmentVariable("CHARLIE_KEY_2", "charlie-value-2")
.environmentVariable("ALTERNATE_KEY_1", "alternate-value-1")
.environmentVariable("ALTERNATE_KEY_2", "alternate-value-2")
.environmentVariable("DELTA_KEY_1", "delta-value-1")
.environmentVariable("DELTA_KEY_2", "delta-value-2")
.environmentVariable("ALTERNATE_KEY_1", "alternate-value-1")
.environmentVariable("ALTERNATE_KEY_2", "alternate-value-2")
.service("charlie-instance-1")
.service("charlie-instance-2")
.service("alternate-instance-1")
.service("alternate-instance-2")
.service("delta-instance-1")
.service("delta-instance-2")
.service("alternate-instance-1")
.service("alternate-instance-2")
.build());

List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-delta.yml").getFile().toPath());
Expand Down Expand Up @@ -371,6 +372,40 @@ public void readInherit() throws IOException {
assertThat(actual).isEqualTo(expected);
}

@Test
public void readInheritCommon() throws IOException {
List<ApplicationManifest> expected = Collections.singletonList(
ApplicationManifest.builder()
.name("juliet-application")
.buildpack("indigo-buildpack")
.command("indigo-command")
.disk(-1)
.healthCheckHttpEndpoint("indigo-health-check-http-endpoint")
.healthCheckType(NONE)
.instances(-1)
.memory(-1)
.noRoute(true)
.path(Paths.get("/indigo-path"))
.randomRoute(true)
.route(Route.builder()
.route("indigo-route-1")
.build())
.route(Route.builder()
.route("indigo-route-2")
.build())
.stack("indigo-stack")
.timeout(-1)
.environmentVariable("INDIGO_KEY_1", "indigo-value-1")
.environmentVariable("INDIGO_KEY_2", "indigo-value-2")
.service("indigo-instance-1")
.service("indigo-instance-2")
.build());

List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-juliet.yml").getFile().toPath());

assertThat(actual).isEqualTo(expected);
}

@Test
public void readNoApplications() throws IOException {
List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-hotel.yml").getFile().toPath());
Expand All @@ -388,6 +423,61 @@ public void readNoRoute() throws IOException {
ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-golf.yml").getFile().toPath());
}

@Test
public void testDiskQuotaAndMemoryParsing() throws Exception {
List<ApplicationManifest> expected = Arrays.asList(
ApplicationManifest.builder()
.name("quota-test-1")
.memory(1)
.disk(2)
.build(),
ApplicationManifest.builder()
.name("quota-test-2")
.memory(3)
.disk(4)
.build(),
ApplicationManifest.builder()
.name("quota-test-3")
.memory(5)
.disk(6)
.build(),
ApplicationManifest.builder()
.name("quota-test-4")
.memory(7)
.disk(8)
.build(),
ApplicationManifest.builder()
.name("quota-test-5")
.memory(1024)
.disk(2048)
.build(),
ApplicationManifest.builder()
.name("quota-test-6")
.memory(3072)
.disk(4096)
.build(),
ApplicationManifest.builder()
.name("quota-test-7")
.memory(5120)
.disk(6144)
.build(),
ApplicationManifest.builder()
.name("quota-test-8")
.memory(7168)
.disk(8192)
.build(),
ApplicationManifest.builder()
.name("quota-test-9")
.memory(1234)
.disk(5678)
.build()
);

List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-quota.yml").getFile().toPath());

assertThat(actual).isEqualTo(expected);
}

@Test
public void write() throws IOException {
Path out = Files.createTempFile("test-manifest-", ".yml");
Expand Down Expand Up @@ -448,59 +538,4 @@ public void write() throws IOException {

assertThat(actual).isEqualTo(expected);
}

@Test
public void testDiskQuotaAndMemoryParsing() throws Exception {
List<ApplicationManifest> expected = Arrays.asList(
ApplicationManifest.builder()
.name("quota-test-1")
.memory(1)
.disk(2)
.build(),
ApplicationManifest.builder()
.name("quota-test-2")
.memory(3)
.disk(4)
.build(),
ApplicationManifest.builder()
.name("quota-test-3")
.memory(5)
.disk(6)
.build(),
ApplicationManifest.builder()
.name("quota-test-4")
.memory(7)
.disk(8)
.build(),
ApplicationManifest.builder()
.name("quota-test-5")
.memory(1024)
.disk(2048)
.build(),
ApplicationManifest.builder()
.name("quota-test-6")
.memory(3072)
.disk(4096)
.build(),
ApplicationManifest.builder()
.name("quota-test-7")
.memory(5120)
.disk(6144)
.build(),
ApplicationManifest.builder()
.name("quota-test-8")
.memory(7168)
.disk(8192)
.build(),
ApplicationManifest.builder()
.name("quota-test-9")
.memory(1234)
.disk(5678)
.build()
);

List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-quota.yml").getFile().toPath());

assertThat(actual).isEqualTo(expected);
}
}
Loading

0 comments on commit 58daa1f

Please sign in to comment.