Skip to content

Commit

Permalink
enhance status page with detailed information about current git commi…
Browse files Browse the repository at this point in the history
…t and add corresponding documentation into README

Co-authored-by: Matthias Geißendörfer <[email protected]>
  • Loading branch information
mgeissen committed Apr 26, 2024
1 parent 051e66d commit 5b4700b
Show file tree
Hide file tree
Showing 16 changed files with 232 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* _core_:
* Upgrade to bootstrap 5.3.3
* Add status detail indicator infos to status page
* Enhance git infos on status page

## 0.2.0
* Upgrade all modules to spring boot 3.2.2
Expand Down
16 changes: 11 additions & 5 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ babbage-microservices because there too many ways of securing services in a micr

#### Status ( /status ):

_will be added soon_
On the status page you will find a summary of all important information about your service. This includes general system
information, if enabled the git information, and all infos from the registered `StatusDetailIndicator` beans.

[comment]: <> (Git Info: git.properties are shown on status page)
_Git section_:<br/>
To be able to see the git information on the status page, you have to provide a `git.properties` in the resource
section of your build folder and have to set `babbage.status.git.enabled=true` in your `application.properties/.yaml`. As shown in the example application you could just use
the [gradle git properties plugin](https://github.com/n0mer/gradle-git-properties) to generate the file.

[comment]: <> (Screenshot)
![Status Page of Example Application](docs/status-page.png)

#### Logger ( /logger ):

Expand All @@ -40,7 +44,8 @@ You could add a custom management page that uses the general management page lay

1. Add Controller that implements the `ManagementController` interface. This will add the required model attributes to
the template, that are required for the navigation.
2. Create a new controller endpoint (path should be based on `management.endpoints.web.base-path` property) inside your added controller, and use the following html template:
2. Create a new controller endpoint (path should be based on `management.endpoints.web.base-path` property) inside your
added controller, and use the following html template:

```
<!DOCTYPE html>
Expand All @@ -58,7 +63,8 @@ You could add a custom management page that uses the general management page lay
</html>
```

3. Add a `NavBarItem` Bean to your application context to register your new endpoint to the navigation. The `path` is based on the `management.endpoints.web.base-path` property and should the one from
3. Add a `NavBarItem` Bean to your application context to register your new endpoint to the navigation. The `path` is
based on the `management.endpoints.web.base-path` property and should the one from

```
@Bean
Expand Down
Binary file added core/docs/status-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ class ThymeleafConfiguration(springTemplateEngine: SpringTemplateEngine) {
springTemplateEngine.addDialect(Java8TimeDialect())
}

}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package de.otto.babbage.core.status

import de.otto.babbage.core.management.ManagementController
import de.otto.babbage.core.status.config.StatusProperties
import de.otto.babbage.core.status.contributors.SystemInfoContributor
import de.otto.babbage.core.status.indicators.Status
import de.otto.babbage.core.status.indicators.Status.*
import de.otto.babbage.core.status.indicators.StatusDetail
import de.otto.babbage.core.status.indicators.StatusDetailIndicator
import de.otto.babbage.core.status.version.GitVersionProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.actuate.info.InfoEndpoint
import org.springframework.boot.info.GitProperties
import org.springframework.http.MediaType
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
Expand All @@ -19,10 +17,8 @@ import org.springframework.web.bind.annotation.ResponseBody

@Controller
class StatusController(
private val infoEndpoint: InfoEndpoint,
@Autowired(required = false)
private val gitProperties: GitProperties?,
private val statusProperties: StatusProperties,
private val gitVersionProvider: GitVersionProvider? = null,
@Value("\${info.app.name}") private val applicationName: String,
private val statusDetailIndicators: List<StatusDetailIndicator>,
private val systemInfoContributor: SystemInfoContributor
Expand All @@ -39,17 +35,13 @@ class StatusController(
@GetMapping("\${management.endpoints.web.base-path}/status", produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
suspend fun statusJson(): StatusData {
val commit = gitProperties?.commitId ?: "unavailable"
val version = if (statusProperties.useCommitAsVersion) commit else getVersion()
val indicators = statusDetailIndicators.groupBy { it.getGroup() }
.map { it.key to it.value.flatMap { indicator -> indicator.getDetails() } }
.toMap()
return StatusData(
application = Application(
name = applicationName,
version = version,
status = ApplicationStatus.OK, // dummy status because HealthEndpoint is blocking and causing errors
commit = commit
version = gitVersionProvider?.getGitVersion()
),
serviceStatus = getWorstStatus(indicators),
indicators = indicators,
Expand All @@ -63,12 +55,6 @@ class StatusController(
.worstStatus()
}

@Suppress("UNCHECKED_CAST", "ReturnCount")
private fun getVersion(): String {
val gitInfos = infoEndpoint.info()["git"]?.let { it as Map<String, Any> } ?: return ""
val commitInfos = gitInfos["commit"]?.let { it as Map<String, Any> } ?: return ""
return commitInfos["time"]?.toString() ?: ""
}
}

private fun Set<Status>.worstStatus(): Status {
Expand Down
20 changes: 2 additions & 18 deletions core/src/main/kotlin/de/otto/babbage/core/status/StatusData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.otto.babbage.core.status

import de.otto.babbage.core.status.contributors.SystemInfo
import de.otto.babbage.core.status.indicators.StatusDetail
import org.springframework.boot.actuate.health.Status
import de.otto.babbage.core.status.version.GitVersion

data class StatusData(
val application: Application,
Expand All @@ -13,21 +13,5 @@ data class StatusData(

data class Application(
val name: String,
val version: String,
val status: ApplicationStatus,
val commit: String
val version: GitVersion? = null
)

enum class ApplicationStatus {
OK, WARNING, ERROR;

companion object {
fun fromHealth(status: Status): ApplicationStatus {
return when (status) {
Status.DOWN, Status.OUT_OF_SERVICE, Status.UNKNOWN -> ERROR
Status.UP -> OK
else -> ERROR
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ import org.springframework.context.annotation.Configuration
@ConfigurationProperties(prefix = "babbage.status")
class StatusProperties {

var useCommitAsVersion = false
var git: StatusGitProperties = StatusGitProperties()
}

data class StatusGitProperties(
var enabled: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.otto.babbage.core.status.version

data class GitVersion(
val branch: String,
val commitId: String,
val time: Long,
val user: String,
val message: String,
val repositoryUrl: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.otto.babbage.core.status.version

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.info.GitProperties
import org.springframework.stereotype.Component

private val LOG: Logger = LoggerFactory.getLogger(GitVersionProvider::class.java)

@Component
@ConditionalOnProperty(prefix = "babbage.status.git", name = ["enabled"], havingValue = "true", matchIfMissing = false)
class GitVersionProvider(
private val gitProperties: GitProperties
) {

@Suppress("ReturnCount")
fun getGitVersion(): GitVersion? {
return GitVersion(
branch = gitProperties.branch ?: return handleNull("branch"),
commitId = gitProperties.get("commit.id") ?: return handleNull("commit.id"),
time = gitProperties.get("commit.time")?.toLong() ?: return handleNull("commit.time"),
user = gitProperties.get("commit.user.name") ?: return handleNull("commit.user.name"),
message = gitProperties.get("commit.message.short") ?: return handleNull("commit.message.short"),
repositoryUrl = parseRemoteUrlToHttps() ?: return handleNull("remote.origin.url")
)
}

private fun parseRemoteUrlToHttps(): String? {
val remoteUrl = gitProperties.get("remote.origin.url")
if (remoteUrl?.startsWith("git@") == true) {
val urlParts = remoteUrl.split(":")
val hostPath = urlParts[0].replace("git@", "https://")
val repositoryPath = urlParts[1]
return "$hostPath/$repositoryPath"
}
return remoteUrl
}

private fun handleNull(fieldName: String): GitVersion? {
LOG.warn("Missing $fieldName in git.properties. Git Information are not show on the status page.")
return null
}

}

10 changes: 10 additions & 0 deletions core/src/main/resources/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
flex-direction: column;
gap: 16px;
max-width: 400px;

.card-text-item {
margin: 8px 0;

.card-text-item-title {
font-weight: bold;
color: #6c757d;
}
}

}

.status-details {
Expand Down
54 changes: 45 additions & 9 deletions core/src/main/resources/templates/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,35 @@
<div class="container-fluid status-container">

<div class="cards">
<div class="card" th:with="version=${statusData.application}">
<div class="card"
th:if="${statusData.application.version != null}"
th:with="version=${statusData.application.version}">
<div class="card-body">
<h5 class="card-title">Version</h5>
<div class="card-text">
<div>Version: <span th:text="${version.version}"></span></div>
<div>Commit: <span th:text="${version.commit}"></span></div>
<div class="card-text-item">
<div class="card-text-item-title">Branch</div>
<div th:text="${version.branch}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Commit Id</div>
<div th:text="${version.commitId}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Time</div>
<div th:text="${#dates.format(version.time, 'dd-MM-yyyy HH:mm:ss')}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Author</div>
<div th:text="${version.user}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Message</div>
<div th:text="${version.message}"></div>
</div>
<div class="card-text-item">
<a class="btn btn-info" th:href="${version.repositoryUrl}">Go to Git-Repository</a>
</div>
</div>
</div>
</div>
Expand All @@ -21,13 +44,26 @@ <h5 class="card-title">Version</h5>
<div class="card-body">
<h5 class="card-title">System</h5>
<div class="card-text">
<div>Started at: <span th:text="${#temporals.format(system.startTime, 'dd-MM-yyyy HH:mm')}"></span>
<div class="card-text-item">
<div class="card-text-item-title">Started at</div>
<div th:text="${#temporals.format(system.startTime, 'dd-MM-yyyy HH:mm')}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Up-Time</div>
<div th:text="${system.upTime}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">System-Time</div>
<div th:text="${#temporals.format(system.systemTime, 'dd-MM-yyyy HH:mm')}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Port</div>
<div th:text="${system.port}"></div>
</div>
<div class="card-text-item">
<div class="card-text-item-title">Hostname</div>
<div th:text="${system.hostname}"></div>
</div>
<div>Up-Time: <span th:text="${system.upTime}"></span></div>
<div>System-Time: <span
th:text="${#temporals.format(system.systemTime, 'dd-MM-yyyy HH:mm')}"></span></div>
<div>Port: <span th:text="${system.port}"></span></div>
<div>Hostname: <span th:text="${system.hostname}"></span></div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package de.otto.babbage.core.version

import de.otto.babbage.core.status.version.GitVersion
import de.otto.babbage.core.status.version.GitVersionProvider
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.springframework.boot.info.GitProperties
import java.util.*

class GitVersionProviderTest {

@Test
fun `should convert git properties to git version`() {
// given
val properties = Properties()
val branchName = "someBranch-${UUID.randomUUID()}"
properties.setProperty("branch", branchName)
val commitId = "someCommitId-${UUID.randomUUID()}"
properties.setProperty("commit.id", commitId)
val commitTime = "1714137126540"
properties.setProperty("commit.time", commitTime)
val user = "someUser-${UUID.randomUUID()}"
properties.setProperty("commit.user.name", user)
val messageShort = "someMessageShort-${UUID.randomUUID()}"
properties.setProperty("commit.message.short", messageShort)
val remoteUrl = "https://github.com/otto-de/babbage-microservice.git"
properties.setProperty("remote.origin.url", remoteUrl)
val gitProperties = GitProperties(properties)

val versionProvider = GitVersionProvider(gitProperties)

// when
val gitVersion = versionProvider.getGitVersion()

// then
gitVersion shouldBe GitVersion(
branch = branchName,
commitId = commitId,
time = commitTime.toLong() * 1000,
user = user,
message = messageShort,
repositoryUrl = remoteUrl
)
}

@ParameterizedTest
@CsvSource(
"[email protected]:otto-de/babbage-microservice.git, https://github.com/otto-de/babbage-microservice.git",
"[email protected]:otto-de/babbage-microservice.git, https://gitlab.com/otto-de/babbage-microservice.git",
"https://github.com/otto-de/babbage-microservice.git, https://github.com/otto-de/babbage-microservice.git"
)
fun `should parse remote url to https url to link directly to web page of repository`(
gitRemoteUrl: String,
expectedRepositoryUrl: String
) {
// given
val properties = Properties()
properties.setProperty("branch", "someBranch-${UUID.randomUUID()}")
properties.setProperty("commit.id", "someCommitId-${UUID.randomUUID()}")
properties.setProperty("commit.time", "123456789")
properties.setProperty("commit.user.name", "someUser-${UUID.randomUUID()}")
properties.setProperty("commit.message.short", "someMessageShort-${UUID.randomUUID()}")
properties.setProperty("remote.origin.url", gitRemoteUrl)
val gitProperties = GitProperties(properties)

val versionProvider = GitVersionProvider(gitProperties)

// when
val gitVersion = versionProvider.getGitVersion()

// then
gitVersion!!.repositoryUrl shouldBe expectedRepositoryUrl
}

@Test
fun `should catch missing git properties and return null`() {
// given
val gitProperties = GitProperties(Properties())
val versionProvider = GitVersionProvider(gitProperties)

// when
val gitVersion = versionProvider.getGitVersion()

// then
gitVersion shouldBe null
}
}
Loading

0 comments on commit 5b4700b

Please sign in to comment.