Skip to content

Commit fca1ef0

Browse files
committed
Merge branch '2.x'
2 parents b650538 + 58daa1f commit fca1ef0

File tree

4 files changed

+195
-109
lines changed

4 files changed

+195
-109
lines changed

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtils.java

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import com.fasterxml.jackson.annotation.JsonInclude;
2020
import com.fasterxml.jackson.databind.JsonNode;
2121
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.node.ArrayNode;
23+
import com.fasterxml.jackson.databind.node.ObjectNode;
24+
import com.fasterxml.jackson.databind.node.ValueNode;
2225
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
2326
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
2427
import org.cloudfoundry.util.tuple.Consumer2;
@@ -34,11 +37,10 @@
3437
import java.util.Collections;
3538
import java.util.Iterator;
3639
import java.util.List;
37-
import java.util.Map;
3840
import java.util.Optional;
3941
import java.util.Spliterator;
4042
import java.util.Spliterators;
41-
import java.util.TreeMap;
43+
import java.util.concurrent.atomic.AtomicReference;
4244
import java.util.function.Consumer;
4345
import java.util.function.Function;
4446
import java.util.stream.Collectors;
@@ -67,10 +69,7 @@ private ApplicationManifestUtils() {
6769
* @return the resolved manifests
6870
*/
6971
public static List<ApplicationManifest> read(Path path) {
70-
return doRead(path.toAbsolutePath())
71-
.values().stream()
72-
.map(ApplicationManifest.Builder::build)
73-
.collect(Collectors.toList());
72+
return doRead(path.toAbsolutePath());
7473
}
7574

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

187-
private static JsonNode deserialize(Path path) {
186+
private static ObjectNode deserialize(Path path) {
187+
AtomicReference<ObjectNode> root = new AtomicReference<>();
188+
188189
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
189-
return OBJECT_MAPPER.readTree(in);
190+
root.set(((ObjectNode) OBJECT_MAPPER.readTree(in)));
190191
} catch (IOException e) {
191192
throw Exceptions.propagate(e);
192193
}
193-
}
194194

195-
private static Map<String, ApplicationManifest.Builder> doRead(Path path) {
196-
Map<String, ApplicationManifest.Builder> applicationManifests = new TreeMap<>();
197-
198-
JsonNode root = deserialize(path);
195+
asString(root.get(), "inherit", inherit -> root.set(merge(deserialize(path.getParent().resolve(inherit)), root.get())));
199196

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

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

205203
ApplicationManifest template = getTemplate(path, root);
206204

207-
Optional.ofNullable(root.get("applications"))
205+
return Optional.ofNullable(root.get("applications"))
208206
.map(ApplicationManifestUtils::streamOf)
209-
.ifPresent(applications -> applications
210-
.forEach(application -> {
211-
String name = getName(application);
212-
ApplicationManifest.Builder builder = getBuilder(applicationManifests, template, name);
213-
214-
applicationManifests.put(name, toApplicationManifest(application, builder, path));
215-
}));
216-
217-
return applicationManifests;
218-
}
219-
220-
private static ApplicationManifest.Builder getBuilder(Map<String, ApplicationManifest.Builder> applicationManifests, ApplicationManifest template, String name) {
221-
ApplicationManifest.Builder builder = applicationManifests.get(name);
222-
if (builder == null) {
223-
builder = ApplicationManifest.builder().from(template);
224-
}
225-
return builder;
207+
.orElseGet(Stream::empty)
208+
.map(application -> {
209+
String name = getName(application);
210+
return toApplicationManifest(application, ApplicationManifest.builder().from(template), path)
211+
.name(name)
212+
.build();
213+
})
214+
.collect(Collectors.toList());
226215
}
227216

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

221+
private static ObjectNode getNamedObject(ArrayNode array, String name) {
222+
return (ObjectNode) streamOf(array)
223+
.filter(object -> object.has("name") && name.equals(object.get("name").asText()))
224+
.findFirst()
225+
.orElseGet(array::addObject);
226+
}
227+
232228
private static Route getRoute(JsonNode raw) {
233229
String route = Optional.ofNullable(raw.get("route")).map(JsonNode::asText).orElseThrow(() -> new IllegalStateException("Route does not contain required 'route' value"));
234230
return Route.builder().route(route).build();
@@ -240,6 +236,34 @@ private static ApplicationManifest getTemplate(Path path, JsonNode root) {
240236
.build();
241237
}
242238

239+
private static ObjectNode merge(ObjectNode first, ObjectNode second) {
240+
streamOf(second.fields())
241+
.forEach(field -> {
242+
String key = field.getKey();
243+
JsonNode value = field.getValue();
244+
245+
if (value instanceof ValueNode) {
246+
first.replace(key, value);
247+
} else if (value instanceof ArrayNode) {
248+
streamOf(value)
249+
.forEach(element -> {
250+
JsonNode name = element.get("name");
251+
252+
if (name != null) {
253+
ObjectNode named = getNamedObject(first.withArray(key), name.asText());
254+
merge(named, (ObjectNode) element);
255+
} else {
256+
first.withArray(key).add(element);
257+
}
258+
});
259+
} else if (value instanceof ObjectNode) {
260+
first.with(key).setAll((ObjectNode) value);
261+
}
262+
});
263+
264+
return first;
265+
}
266+
243267
private static <T> Stream<T> streamOf(Iterator<T> iterator) {
244268
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
245269
}

cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsTest.java

Lines changed: 110 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.Path;
2525
import java.nio.file.Paths;
2626
import java.util.Arrays;
27+
import java.util.Collections;
2728
import java.util.List;
2829

2930
import static org.assertj.core.api.Assertions.assertThat;
@@ -201,48 +202,48 @@ public void readCommonAndInherit() throws IOException {
201202
.build(),
202203
ApplicationManifest.builder()
203204
.name("charlie-application-2")
204-
.buildpack("delta-buildpack")
205-
.command("delta-command")
206-
.disk(-3)
207-
.healthCheckHttpEndpoint("delta-health-check-http-endpoint")
208-
.healthCheckType(NONE)
209-
.instances(-3)
210-
.memory(-3)
211-
.noRoute(true)
212-
.path(Paths.get("/delta-path"))
213-
.randomRoute(true)
205+
.buildpack("alternate-buildpack")
206+
.command("alternate-command")
207+
.disk(-2)
208+
.healthCheckHttpEndpoint("alternate-health-check-http-endpoint")
209+
.healthCheckType(PORT)
210+
.instances(-2)
211+
.memory(-2)
212+
.noRoute(false)
213+
.path(Paths.get("/alternate-path"))
214+
.randomRoute(false)
214215
.route(Route.builder()
215216
.route("charlie-route-1")
216217
.build())
217218
.route(Route.builder()
218219
.route("charlie-route-2")
219220
.build())
220221
.route(Route.builder()
221-
.route("alternate-route-1")
222+
.route("delta-route-1")
222223
.build())
223224
.route(Route.builder()
224-
.route("alternate-route-2")
225+
.route("delta-route-2")
225226
.build())
226227
.route(Route.builder()
227-
.route("delta-route-1")
228+
.route("alternate-route-1")
228229
.build())
229230
.route(Route.builder()
230-
.route("delta-route-2")
231+
.route("alternate-route-2")
231232
.build())
232-
.stack("delta-stack")
233-
.timeout(-3)
233+
.stack("alternate-stack")
234+
.timeout(-2)
234235
.environmentVariable("CHARLIE_KEY_1", "charlie-value-1")
235236
.environmentVariable("CHARLIE_KEY_2", "charlie-value-2")
236-
.environmentVariable("ALTERNATE_KEY_1", "alternate-value-1")
237-
.environmentVariable("ALTERNATE_KEY_2", "alternate-value-2")
238237
.environmentVariable("DELTA_KEY_1", "delta-value-1")
239238
.environmentVariable("DELTA_KEY_2", "delta-value-2")
239+
.environmentVariable("ALTERNATE_KEY_1", "alternate-value-1")
240+
.environmentVariable("ALTERNATE_KEY_2", "alternate-value-2")
240241
.service("charlie-instance-1")
241242
.service("charlie-instance-2")
242-
.service("alternate-instance-1")
243-
.service("alternate-instance-2")
244243
.service("delta-instance-1")
245244
.service("delta-instance-2")
245+
.service("alternate-instance-1")
246+
.service("alternate-instance-2")
246247
.build());
247248

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

375+
@Test
376+
public void readInheritCommon() throws IOException {
377+
List<ApplicationManifest> expected = Collections.singletonList(
378+
ApplicationManifest.builder()
379+
.name("juliet-application")
380+
.buildpack("indigo-buildpack")
381+
.command("indigo-command")
382+
.disk(-1)
383+
.healthCheckHttpEndpoint("indigo-health-check-http-endpoint")
384+
.healthCheckType(NONE)
385+
.instances(-1)
386+
.memory(-1)
387+
.noRoute(true)
388+
.path(Paths.get("/indigo-path"))
389+
.randomRoute(true)
390+
.route(Route.builder()
391+
.route("indigo-route-1")
392+
.build())
393+
.route(Route.builder()
394+
.route("indigo-route-2")
395+
.build())
396+
.stack("indigo-stack")
397+
.timeout(-1)
398+
.environmentVariable("INDIGO_KEY_1", "indigo-value-1")
399+
.environmentVariable("INDIGO_KEY_2", "indigo-value-2")
400+
.service("indigo-instance-1")
401+
.service("indigo-instance-2")
402+
.build());
403+
404+
List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-juliet.yml").getFile().toPath());
405+
406+
assertThat(actual).isEqualTo(expected);
407+
}
408+
374409
@Test
375410
public void readNoApplications() throws IOException {
376411
List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-hotel.yml").getFile().toPath());
@@ -388,6 +423,61 @@ public void readNoRoute() throws IOException {
388423
ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-golf.yml").getFile().toPath());
389424
}
390425

426+
@Test
427+
public void testDiskQuotaAndMemoryParsing() throws Exception {
428+
List<ApplicationManifest> expected = Arrays.asList(
429+
ApplicationManifest.builder()
430+
.name("quota-test-1")
431+
.memory(1)
432+
.disk(2)
433+
.build(),
434+
ApplicationManifest.builder()
435+
.name("quota-test-2")
436+
.memory(3)
437+
.disk(4)
438+
.build(),
439+
ApplicationManifest.builder()
440+
.name("quota-test-3")
441+
.memory(5)
442+
.disk(6)
443+
.build(),
444+
ApplicationManifest.builder()
445+
.name("quota-test-4")
446+
.memory(7)
447+
.disk(8)
448+
.build(),
449+
ApplicationManifest.builder()
450+
.name("quota-test-5")
451+
.memory(1024)
452+
.disk(2048)
453+
.build(),
454+
ApplicationManifest.builder()
455+
.name("quota-test-6")
456+
.memory(3072)
457+
.disk(4096)
458+
.build(),
459+
ApplicationManifest.builder()
460+
.name("quota-test-7")
461+
.memory(5120)
462+
.disk(6144)
463+
.build(),
464+
ApplicationManifest.builder()
465+
.name("quota-test-8")
466+
.memory(7168)
467+
.disk(8192)
468+
.build(),
469+
ApplicationManifest.builder()
470+
.name("quota-test-9")
471+
.memory(1234)
472+
.disk(5678)
473+
.build()
474+
);
475+
476+
List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-quota.yml").getFile().toPath());
477+
478+
assertThat(actual).isEqualTo(expected);
479+
}
480+
391481
@Test
392482
public void write() throws IOException {
393483
Path out = Files.createTempFile("test-manifest-", ".yml");
@@ -448,59 +538,4 @@ public void write() throws IOException {
448538

449539
assertThat(actual).isEqualTo(expected);
450540
}
451-
452-
@Test
453-
public void testDiskQuotaAndMemoryParsing() throws Exception {
454-
List<ApplicationManifest> expected = Arrays.asList(
455-
ApplicationManifest.builder()
456-
.name("quota-test-1")
457-
.memory(1)
458-
.disk(2)
459-
.build(),
460-
ApplicationManifest.builder()
461-
.name("quota-test-2")
462-
.memory(3)
463-
.disk(4)
464-
.build(),
465-
ApplicationManifest.builder()
466-
.name("quota-test-3")
467-
.memory(5)
468-
.disk(6)
469-
.build(),
470-
ApplicationManifest.builder()
471-
.name("quota-test-4")
472-
.memory(7)
473-
.disk(8)
474-
.build(),
475-
ApplicationManifest.builder()
476-
.name("quota-test-5")
477-
.memory(1024)
478-
.disk(2048)
479-
.build(),
480-
ApplicationManifest.builder()
481-
.name("quota-test-6")
482-
.memory(3072)
483-
.disk(4096)
484-
.build(),
485-
ApplicationManifest.builder()
486-
.name("quota-test-7")
487-
.memory(5120)
488-
.disk(6144)
489-
.build(),
490-
ApplicationManifest.builder()
491-
.name("quota-test-8")
492-
.memory(7168)
493-
.disk(8192)
494-
.build(),
495-
ApplicationManifest.builder()
496-
.name("quota-test-9")
497-
.memory(1234)
498-
.disk(5678)
499-
.build()
500-
);
501-
502-
List<ApplicationManifest> actual = ApplicationManifestUtils.read(new ClassPathResource("fixtures/manifest-quota.yml").getFile().toPath());
503-
504-
assertThat(actual).isEqualTo(expected);
505-
}
506541
}

0 commit comments

Comments
 (0)