Commit 3995c16b authored by Eddú Meléndez's avatar Eddú Meléndez Committed by Phillip Webb

Add 'flyway' and 'liquibase' actuator endpoints

Add `/flyway` and `/liquibase` actuator endpoints to provide details of
any database migrations that have been applied.

Fixes gh-3434
Closes gh-3435
parent 7e58483e
......@@ -112,6 +112,11 @@
<artifactId>activemq-broker</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
......@@ -122,6 +127,11 @@
<artifactId>infinispan-spring4</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
......
......@@ -25,6 +25,9 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import liquibase.integration.spring.SpringLiquibase;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
......@@ -33,8 +36,10 @@ import org.springframework.boot.actuate.endpoint.ConfigurationPropertiesReportEn
import org.springframework.boot.actuate.endpoint.DumpEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.FlywayEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.InfoEndpoint;
import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
......@@ -45,12 +50,15 @@ import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -70,8 +78,10 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
* @author Greg Turnquist
* @author Christian Dupuis
* @author Stephane Nicoll
* @author Eddú Meléndez
*/
@Configuration
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
public class EndpointAutoConfiguration {
@Autowired
......@@ -161,6 +171,32 @@ public class EndpointAutoConfiguration {
return new ConfigurationPropertiesReportEndpoint();
}
@Configuration
@ConditionalOnBean(Flyway.class)
@ConditionalOnClass(Flyway.class)
static class FlywayEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
public FlywayEndpoint flywayEndpoint(Flyway flyway) {
return new FlywayEndpoint(flyway);
}
}
@Configuration
@ConditionalOnBean(SpringLiquibase.class)
@ConditionalOnClass(SpringLiquibase.class)
static class LiquibaseEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
public LiquibaseEndpoint liquibaseEndpoint(SpringLiquibase liquibase) {
return new LiquibaseEndpoint(liquibase);
}
}
@Configuration
@ConditionalOnClass(AbstractHandlerMethodMapping.class)
protected static class RequestMappingEndpointConfiguration {
......
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationType;
import org.springframework.boot.actuate.endpoint.FlywayEndpoint.FlywayMigration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose flyway info.
*
* @author Eddú Meléndez
* @author Phillip Webb
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "endpoints.flyway", ignoreUnknownFields = true)
public class FlywayEndpoint extends AbstractEndpoint<List<FlywayMigration>> {
private final Flyway flyway;
public FlywayEndpoint(Flyway flyway) {
super("flyway");
Assert.notNull(flyway, "Flyway must not be null");
this.flyway = flyway;
}
@Override
public List<FlywayMigration> invoke() {
List<FlywayMigration> migrations = new ArrayList<FlywayMigration>();
for (MigrationInfo info : this.flyway.info().all()) {
migrations.add(new FlywayMigration(info));
}
return migrations;
}
public static class FlywayMigration {
private MigrationType type;
private Integer checksum;
private String version;
private String description;
private String script;
private MigrationState state;
private Date installedOn;
private Integer executionTime;
public FlywayMigration(MigrationInfo info) {
this.type = info.getType();
this.checksum = info.getChecksum();
this.version = info.getVersion().toString();
this.description = info.getDescription();
this.script = info.getScript();
this.state = info.getState();
this.installedOn = info.getInstalledOn();
this.executionTime = info.getExecutionTime();
}
public MigrationType getType() {
return this.type;
}
public Integer getChecksum() {
return this.checksum;
}
public String getVersion() {
return this.version;
}
public String getDescription() {
return this.description;
}
public String getScript() {
return this.script;
}
public MigrationState getState() {
return this.state;
}
public Date getInstalledOn() {
return this.installedOn;
}
public Integer getExecutionTime() {
return this.executionTime;
}
}
}
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import liquibase.changelog.StandardChangeLogHistoryService;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose liquibase info.
*
* @author Eddú Meléndez
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "endpoints.liquibase", ignoreUnknownFields = true)
public class LiquibaseEndpoint extends AbstractEndpoint<List<Map<String, ?>>> {
private final SpringLiquibase liquibase;
public LiquibaseEndpoint(SpringLiquibase liquibase) {
super("liquibase");
Assert.notNull(liquibase, "Liquibase must not be null");
this.liquibase = liquibase;
}
@Override
public List<Map<String, ?>> invoke() {
StandardChangeLogHistoryService service = new StandardChangeLogHistoryService();
try {
DatabaseFactory factory = DatabaseFactory.getInstance();
DataSource dataSource = this.liquibase.getDataSource();
JdbcConnection connection = new JdbcConnection(dataSource.getConnection());
Database database = factory.findCorrectDatabaseImplementation(connection);
return service.queryDatabaseChangeLogTable(database);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to get Liquibase changelog", ex);
}
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -26,8 +26,10 @@ import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint
import org.springframework.boot.actuate.endpoint.BeansEndpoint;
import org.springframework.boot.actuate.endpoint.DumpEndpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.FlywayEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.InfoEndpoint;
import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
......@@ -36,7 +38,9 @@ import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
......@@ -55,6 +59,7 @@ import static org.junit.Assert.assertTrue;
* @author Greg Turnquist
* @author Christian Dupuis
* @author Stephane Nicoll
* @author Eddú Meléndez
*/
public class EndpointAutoConfigurationTests {
......@@ -156,6 +161,28 @@ public class EndpointAutoConfigurationTests {
assertNull(endpoint.invoke().get("git"));
}
@Test
public void testFlywayEndpoint() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(EmbeddedDataSourceConfiguration.class,
FlywayAutoConfiguration.class, EndpointAutoConfiguration.class);
this.context.refresh();
FlywayEndpoint endpoint = this.context.getBean(FlywayEndpoint.class);
assertNotNull(endpoint);
assertEquals(1, endpoint.invoke().size());
}
@Test
public void testLiquibaseEndpoint() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class);
this.context.refresh();
LiquibaseEndpoint endpoint = this.context.getBean(LiquibaseEndpoint.class);
assertNotNull(endpoint);
assertEquals(1, endpoint.invoke().size());
}
private void load(Class<?>... config) {
this.context = new AnnotationConfigApplicationContext();
this.context.register(config);
......
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import org.flywaydb.core.Flyway;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link FlywayEndpoint}.
*
* @author Eddú Meléndez
*/
public class FlywayEndpointTests extends AbstractEndpointTests<FlywayEndpoint> {
public FlywayEndpointTests() {
super(Config.class, FlywayEndpoint.class, "flyway", true, "endpoints.flyway");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke().size(), is(1));
}
@Configuration
@Import({ EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class })
public static class Config {
@Autowired
private Flyway flyway;
@Bean
public FlywayEndpoint endpoint() {
return new FlywayEndpoint(this.flyway);
}
}
}
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link LiquibaseEndpoint}.
*
* @author Eddú Meléndez
*/
public class LiquibaseEndpointTests extends AbstractEndpointTests<LiquibaseEndpoint> {
public LiquibaseEndpointTests() {
super(Config.class, LiquibaseEndpoint.class, "liquibase", true,
"endpoints.liquibase");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke().size(), is(1));
}
@Configuration
@Import({ EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class })
public static class Config {
@Autowired
private SpringLiquibase liquibase;
@Bean
public LiquibaseEndpoint endpoint() {
return new LiquibaseEndpoint(this.liquibase);
}
}
}
databaseChangeLog:
- changeSet:
id: 1
author: marceloverdijk
changes:
- createTable:
tableName: customer
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(50)
constraints:
nullable: false
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