Commit 19baa38e by Johannes Edmeier

Extract BuildVersion from Info and Metadata

If the build version of the instance is present in the metadata it's now also considered and the version is now a attribute on the instance itself. So when using non-boot application they can provide it via the registration metadata.
parent 7e970091
......@@ -36,7 +36,7 @@
v-text="`${application.instances.length} instances`"/>
</span>
</p>
<p class="application-list__item__header__version" v-text="application.version"/>
<p class="application-list__item__header__version" v-text="application.buildVersion"/>
</template>
<template v-else>
<h1 class="title is-size-5 application-list__item__header__name" v-text="application.name"/>
......@@ -65,7 +65,7 @@
<span class="is-muted" v-text="instance.id"/>
</td>
<td>
<span v-text="instance.info.version"/>
<span v-text="instance.buildVersion"/>
</td>
<td class="instance__actions">
<sba-icon-button :id="`nf-settings-${instance.id}`"
......
......@@ -25,7 +25,7 @@
<h1 class="application__name" v-text="application.name"/>
<p class="application__instances is-muted"><span v-text="application.instances.length"/> instances</p>
</div>
<h2 class="application__footer application__version" v-text="application.version"/>
<h2 class="application__footer application__version" v-text="application.buildVersion"/>
</div>
</hex-mesh>
</section>
......
......@@ -23,6 +23,7 @@ import de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.domain.values.BuildVersion;
import de.codecentric.boot.admin.server.domain.values.Endpoint;
import de.codecentric.boot.admin.server.domain.values.Endpoints;
import de.codecentric.boot.admin.server.domain.values.Info;
......@@ -35,6 +36,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.util.Assert;
......@@ -59,20 +61,14 @@ public class Instance implements Serializable {
private final Info info;
private final List<InstanceEvent> unsavedEvents;
private final Endpoints endpoints;
private final BuildVersion buildVersion;
private Instance(InstanceId id) {
this(id, -1L, null, false, StatusInfo.ofUnknown(), Instant.EPOCH, Info.empty(), Endpoints.empty(), emptyList());
}
private Instance(InstanceId id,
long version,
Registration registration,
boolean registered,
StatusInfo statusInfo,
Instant statusTimestamp,
Info info,
Endpoints endpoints,
List<InstanceEvent> unsavedEvents) {
this(id, -1L, null, false, StatusInfo.ofUnknown(), Instant.EPOCH, Info.empty(), Endpoints.empty(), null,
emptyList());
}
private Instance(InstanceId id, long version, Registration registration, boolean registered, StatusInfo statusInfo, Instant statusTimestamp, Info info, Endpoints endpoints, BuildVersion buildVersion, List<InstanceEvent> unsavedEvents) {
Assert.notNull(id, "'id' must not be null");
Assert.notNull(endpoints, "'endpoints' must not be null");
Assert.notNull(info, "'info' must not be null");
......@@ -86,6 +82,7 @@ public class Instance implements Serializable {
this.info = info;
this.endpoints = registered ? endpoints.withEndpoint(Endpoint.HEALTH, registration.getHealthUrl()) : endpoints;
this.unsavedEvents = unsavedEvents;
this.buildVersion = buildVersion;
}
public static Instance create(InstanceId id) {
......@@ -138,7 +135,6 @@ public class Instance implements Serializable {
return this.apply(new InstanceEndpointsDetectedEvent(this.id, this.nextVersion(), endpoints), true);
}
public boolean isRegistered() {
return this.registered;
}
......@@ -149,7 +145,7 @@ public class Instance implements Serializable {
Instance clearUnsavedEvents() {
return new Instance(this.id, this.version, this.registration, this.registered, this.statusInfo,
this.statusTimestamp, info, this.endpoints, emptyList());
this.statusTimestamp, info, this.endpoints, this.buildVersion, emptyList());
}
Instance apply(Collection<InstanceEvent> events) {
......@@ -176,31 +172,34 @@ public class Instance implements Serializable {
if (event instanceof InstanceRegisteredEvent) {
Registration registration = ((InstanceRegisteredEvent) event).getRegistration();
return new Instance(this.id, event.getVersion(), registration, true, StatusInfo.ofUnknown(),
event.getTimestamp(), Info.empty(), Endpoints.empty(), unsavedEvents);
event.getTimestamp(), Info.empty(), Endpoints.empty(), updateBuildVersion(registration.getMetadata()),
unsavedEvents);
} else if (event instanceof InstanceRegistrationUpdatedEvent) {
Registration registration = ((InstanceRegistrationUpdatedEvent) event).getRegistration();
return new Instance(this.id, event.getVersion(), registration, this.registered, this.statusInfo,
this.statusTimestamp, this.info, this.endpoints, unsavedEvents);
this.statusTimestamp, this.info, this.endpoints,
updateBuildVersion(registration.getMetadata(), this.info.getValues()), unsavedEvents);
} else if (event instanceof InstanceStatusChangedEvent) {
StatusInfo statusInfo = ((InstanceStatusChangedEvent) event).getStatusInfo();
return new Instance(this.id, event.getVersion(), this.registration, this.registered, statusInfo,
event.getTimestamp(), this.info, this.endpoints, unsavedEvents);
event.getTimestamp(), this.info, this.endpoints, this.buildVersion, unsavedEvents);
} else if (event instanceof InstanceEndpointsDetectedEvent) {
Endpoints endpoints = ((InstanceEndpointsDetectedEvent) event).getEndpoints();
return new Instance(this.id, event.getVersion(), this.registration, this.registered, this.statusInfo,
this.statusTimestamp, this.info, endpoints, unsavedEvents);
this.statusTimestamp, this.info, endpoints, this.buildVersion, unsavedEvents);
} else if (event instanceof InstanceInfoChangedEvent) {
Info info = ((InstanceInfoChangedEvent) event).getInfo();
return new Instance(this.id, event.getVersion(), this.registration, this.registered, this.statusInfo,
this.statusTimestamp, info, this.endpoints, unsavedEvents);
this.statusTimestamp, info, this.endpoints,
updateBuildVersion(this.registration.getMetadata(), info.getValues()), unsavedEvents);
} else if (event instanceof InstanceDeregisteredEvent) {
return new Instance(this.id, event.getVersion(), this.registration, false, StatusInfo.ofUnknown(),
event.getTimestamp(), Info.empty(), Endpoints.empty(), unsavedEvents);
event.getTimestamp(), Info.empty(), Endpoints.empty(), null, unsavedEvents);
}
return this;
......@@ -214,10 +213,20 @@ public class Instance implements Serializable {
if (!isNewEvent) {
return this.unsavedEvents;
}
ArrayList<InstanceEvent> events = new ArrayList<>(this.unsavedEvents.size() + 1);
events.addAll(this.unsavedEvents);
events.add(event);
return events;
}
@SafeVarargs
private final BuildVersion updateBuildVersion(Map<String, ?>... sources) {
for (Map<String, ?> source : sources) {
BuildVersion newBuildVersion = BuildVersion.from(source);
if (newBuildVersion != null) {
return newBuildVersion;
}
}
return null;
}
}
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -14,22 +14,62 @@
* limitations under the License.
*/
package de.codecentric.boot.admin.server.utils;
package de.codecentric.boot.admin.server.domain.values;
import java.util.Comparator;
import java.io.Serializable;
import java.util.Map;
import java.util.Scanner;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonValue;
public class ComparableVersion implements Comparable<ComparableVersion> {
private final String version;
@lombok.Data
public class BuildVersion implements Serializable, Comparable<BuildVersion> {
private final String value;
private ComparableVersion(String version) {
this.version = version;
private BuildVersion(String value) {
Assert.hasText(value, "'value' must not be empty");
this.value = value;
}
public static BuildVersion valueOf(String s) {
return new BuildVersion(s);
}
public static BuildVersion from(Map<String, ?> map) {
Object build = map.get("build");
if (build instanceof Map) {
Object version = ((Map<?, ?>) build).get("version");
if (version instanceof String) {
return valueOf((String) version);
}
}
Object version = map.get("build.version");
if (version instanceof String) {
return valueOf((String) version);
}
version = map.get("version");
if (version instanceof String) {
return valueOf((String) version);
}
return null;
}
@JsonValue
public String getValue() {
return this.value;
}
@Override
public String toString() {
return this.value;
}
@Override
public int compareTo(ComparableVersion other) {
Scanner s1 = new Scanner(this.version);
Scanner s2 = new Scanner(other.version);
public int compareTo(BuildVersion other) {
Scanner s1 = new Scanner(this.value);
Scanner s2 = new Scanner(other.value);
s1.useDelimiter("[.\\-+]");
s2.useDelimiter("[.\\-+]");
......@@ -52,12 +92,4 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
}
return 0;
}
public static ComparableVersion valueOf(String s) {
return new ComparableVersion(s);
}
public static Comparator<String> ascending() {
return Comparator.comparing(ComparableVersion::valueOf);
}
}
......@@ -45,22 +45,6 @@ public class Info implements Serializable {
return EMPTY;
}
public String getVersion() {
Object build = this.values.get("build");
if (build instanceof Map) {
Object version = ((Map<?, ?>) build).get("version");
if (version instanceof String) {
return (String) version;
}
}
Object version = this.values.get("version");
if (version instanceof String) {
return (String) version;
}
return null;
}
@JsonAnyGetter
public Map<String, Object> getValues() {
return Collections.unmodifiableMap(values);
......
......@@ -17,10 +17,10 @@
package de.codecentric.boot.admin.server.web;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.values.BuildVersion;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import de.codecentric.boot.admin.server.eventstore.InstanceEventPublisher;
import de.codecentric.boot.admin.server.services.InstanceRegistry;
import de.codecentric.boot.admin.server.utils.ComparableVersion;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
......@@ -30,12 +30,12 @@ import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -87,9 +87,10 @@ public class ApplicationsController {
return registry.getInstances(name)
.flatMap(instance -> registry.deregister(instance.getId()))
.collectList()
.map(deregistered -> !deregistered.isEmpty() ?
ResponseEntity.noContent().build() :
ResponseEntity.notFound().build());
.map(
deregistered -> !deregistered.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity
.notFound()
.build());
}
protected Tuple2<String, Flux<Instance>> getApplicationForInstance(Instance instance) {
......@@ -101,7 +102,7 @@ public class ApplicationsController {
return instances.collectList().map(instanceList -> {
Application group = new Application(name);
group.setInstances(instanceList);
group.setVersion(getVersion(instanceList));
group.setBuildVersion(getBuildVersion(instanceList));
Tuple2<String, Instant> status = getStatus(instanceList);
group.setStatus(status.getT1());
group.setStatusTimestamp(status.getT2());
......@@ -109,19 +110,19 @@ public class ApplicationsController {
});
}
protected String getVersion(List<Instance> instances) {
List<String> versions = instances.stream()
.map(instance -> instance.getInfo().getVersion())
.filter(StringUtils::hasText)
protected BuildVersion getBuildVersion(List<Instance> instances) {
List<BuildVersion> versions = instances.stream()
.map(Instance::getBuildVersion)
.filter(Objects::nonNull)
.distinct()
.sorted(ComparableVersion.ascending())
.sorted()
.collect(toList());
if (versions.isEmpty()) {
return "";
return null;
} else if (versions.size() == 1) {
return versions.get(0);
} else {
return versions.get(0) + " - " + versions.get(versions.size() - 1);
return BuildVersion.valueOf(versions.get(0) + " ... " + versions.get(versions.size() - 1));
}
}
......@@ -165,7 +166,7 @@ public class ApplicationsController {
@lombok.Data
public static class Application {
private final String name;
private String version;
private BuildVersion buildVersion;
private String status;
private Instant statusTimestamp;
private List<Instance> instances;
......
......@@ -18,15 +18,16 @@ package de.codecentric.boot.admin.server.domain.entities;
import de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.values.BuildVersion;
import de.codecentric.boot.admin.server.domain.values.Endpoints;
import de.codecentric.boot.admin.server.domain.values.Info;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import java.util.Collections;
import org.junit.Test;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
......@@ -51,10 +52,10 @@ public class InstanceTest {
}
@Test
public void shoud_track_unsaved_events() {
public void should_track_unsaved_events() {
Registration registration = Registration.create("foo", "http://health").build();
Registration registration2 = Registration.create("foo2", "http://health").build();
Info info = Info.from(Collections.singletonMap("foo", "bar"));
Info info = Info.from(singletonMap("foo", "bar"));
Instance instance = Instance.create(InstanceId.of("id"));
assertThat(instance.isRegistered()).isFalse();
......@@ -93,25 +94,27 @@ public class InstanceTest {
@Test
public void should_yield_same_status_from_replaying() {
Registration registration = Registration.create("foo", "http://health").build();
Registration registration2 = Registration.create("foo2", "http://health").build();
Registration registration = Registration.create("foo-instance", "http://health")
.metadata("version", "1.0.0")
.build();
Instance instance = Instance.create(InstanceId.of("id"))
.register(registration.toBuilder().clearMetadata().build())
.register(registration)
.register(registration2)
.withEndpoints(Endpoints.single("info", "info"))
.withStatusInfo(StatusInfo.ofUp())
.withInfo(Info.from(Collections.singletonMap("foo", "bar")));
.withInfo(Info.from(singletonMap("foo", "bar")));
Instance loaded = Instance.create(InstanceId.of("id")).apply(instance.getUnsavedEvents());
assertThat(loaded.getUnsavedEvents()).isEmpty();
assertThat(loaded.getRegistration()).isEqualTo(registration2);
assertThat(loaded.getRegistration()).isEqualTo(registration);
assertThat(loaded.isRegistered()).isTrue();
assertThat(loaded.getStatusInfo()).isEqualTo(StatusInfo.ofUp());
assertThat(loaded.getStatusTimestamp()).isEqualTo(instance.getStatusTimestamp());
assertThat(loaded.getInfo()).isEqualTo(Info.from(Collections.singletonMap("foo", "bar")));
assertThat(loaded.getInfo()).isEqualTo(Info.from(singletonMap("foo", "bar")));
assertThat(loaded.getEndpoints()).isEqualTo(
Endpoints.single("info", "info").withEndpoint("health", "http://health"));
assertThat(loaded.getVersion()).isEqualTo(4L);
assertThat(loaded.getBuildVersion()).isEqualTo(BuildVersion.valueOf("1.0.0"));
Instance deregisteredInstance = instance.deregister();
loaded = Instance.create(InstanceId.of("id")).apply(deregisteredInstance.getUnsavedEvents());
......@@ -122,6 +125,7 @@ public class InstanceTest {
assertThat(loaded.getStatusTimestamp()).isEqualTo(deregisteredInstance.getStatusTimestamp());
assertThat(loaded.getEndpoints()).isEqualTo(Endpoints.empty());
assertThat(loaded.getVersion()).isEqualTo(5L);
assertThat(loaded.getBuildVersion()).isEqualTo(null);
}
@Test
......@@ -138,4 +142,26 @@ public class InstanceTest {
IllegalArgumentException.class).hasMessage("Event 1 doesn't match exptected version 0");
}
@Test
public void should_update_buildVersion() {
Instance instance = Instance.create(InstanceId.of("id"));
assertThat(instance.getBuildVersion()).isNull();
Registration registration = Registration.create("foo-instance", "http://health")
.metadata("version", "1.0.0")
.build();
instance = instance.register(registration).withInfo(Info.empty());
assertThat(instance.getBuildVersion()).isEqualTo(BuildVersion.valueOf("1.0.0"));
instance = instance.register(registration.toBuilder().clearMetadata().build());
assertThat(instance.getBuildVersion()).isNull();
instance = instance.withInfo(Info.from(singletonMap("build", singletonMap("version", "2.1.1"))));
assertThat(instance.getBuildVersion()).isEqualTo(BuildVersion.valueOf("2.1.1"));
instance = instance.deregister();
assertThat(instance.getBuildVersion()).isNull();
}
}
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -14,16 +14,44 @@
* limitations under the License.
*/
package de.codecentric.boot.admin.server.utils;
package de.codecentric.boot.admin.server.domain.values;
import org.junit.Test;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
public class ComparableVersionTest {
public class BuildVersionTest {
private ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
@Test
public void compare() throws Exception {
public void should_return_version() {
assertThat(BuildVersion.from(emptyMap())).isNull();
assertThat(BuildVersion.from(singletonMap("version", "1.0.0"))).isEqualTo(BuildVersion.valueOf("1.0.0"));
assertThat(BuildVersion.from(singletonMap("build.version", "1.0.0"))).isEqualTo(BuildVersion.valueOf("1.0.0"));
assertThat(BuildVersion.from(singletonMap("build", singletonMap("version", "1.0.0")))).isEqualTo(
BuildVersion.valueOf("1.0.0"));
}
@Test
public void should_serialize_json() throws Exception {
String json = objectMapper.writeValueAsString(BuildVersion.valueOf("1.0.0"));
DocumentContext doc = JsonPath.parse(json);
assertThat(doc.read("$", String.class)).isEqualTo("1.0.0");
}
@Test
public void should_return_simple_string() {
assertThat(BuildVersion.valueOf("1.0.0").toString()).isEqualTo("1.0.0");
}
@Test
public void compare() {
assertThat(doCompare("1.0.0", "1.0.0")).isEqualTo(0);
assertThat(doCompare("1.0.1", "1.0.0")).isEqualTo(1);
assertThat(doCompare("1.0.0", "1.0.1")).isEqualTo(-1);
......@@ -52,6 +80,6 @@ public class ComparableVersionTest {
}
private int doCompare(String v1, String v2) {
return ComparableVersion.valueOf(v1).compareTo(ComparableVersion.valueOf(v2));
return BuildVersion.valueOf(v1).compareTo(BuildVersion.valueOf(v2));
}
}
......@@ -33,15 +33,7 @@ public class InfoTest {
private ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
@Test
public void should_return_version() {
assertThat(Info.empty().getVersion()).isNull();
assertThat(Info.from(singletonMap("version", "1.0.0")).getVersion()).isEqualTo("1.0.0");
assertThat(Info.from(singletonMap("build", singletonMap("version", "1.0.0"))).getVersion()).isEqualTo("1.0.0");
}
@Test
public void test_json_serialize() throws Exception {
public void should_serialize_json() throws Exception {
Map<String, Object> values = new HashMap<>();
values.put("foo", "bar");
values.put("build", singletonMap("version", "1.0.0"));
......@@ -55,7 +47,7 @@ public class InfoTest {
}
@Test
public void test_retain_order() {
public void should_keep_order() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("z", "1");
map.put("x", "2");
......
......@@ -183,7 +183,6 @@ public class MailNotifierTest {
ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
dataHandler.writeTo(os);
return os.toString(StandardCharsets.UTF_8.name())
.replaceAll("(------=_Part_\\d+)[_.\\d]+", "$1_XXXXXXX")
.replaceAll("\\r?\\n", "\n");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment