Do not fetch latest Spring Boot versions from spring.io by default

Closes gh-1082
This commit is contained in:
Stephane Nicoll
2020-06-19 16:08:18 +02:00
parent 42b701ab1a
commit f109114c0e
6 changed files with 59 additions and 96 deletions

View File

@@ -359,33 +359,40 @@ defaults can be overridden as shown below:
[[create-instance-boot-versions]]
=== Configuring available Spring Boot versions
If you look at https://projects.spring.io/spring-boot[the project home page for Spring
Boot], the latest versions are displayed. And you've probably noticed that they match the
drop down list that you automatically get with a default instance of Spring Initializr.
The reason for that is that Spring Initializr calls an API on https://spring.io to
retrieve the latest versions automatically. This makes sure that you always get the latest
available versions.
You can configure the available Spring Boot versions the same way you configure other
capabilities.
[source,yaml,indent=0]
----
initializr:
bootVersions:
- id: 2.4.0-SNAPSHOT
name: 2.4.0 (SNAPSHOT)
default: false
- id: 2.3.3.BUILD-SNAPSHOT
name: 2.3.3 (SNAPSHOT)
default: false
- id: 2.3.2.RELEASE
name: 2.3.2
default: true
----
The configuration above provides three versions, with `2.3.2` being used by default. In
practice though, you may want to upgrade the available Spring Boot versions without having
to redeploy the application every time. Implementing your own
`InitializrMetadataUpdateStrategy` bean allows you to update the metadata at runtime.
If you look at https://spring.io/projects/spring-boot[the project home page for Spring
Boot], the latest versions are displayed. `SaganInitializrMetadataUpdateStrategy` is an
implementation of that strategy that fetches the latest Spring Boot versions and update
the metadata to make sure a running instance always get the latest available versions.
If you are behind a proxy, or need to customize the `RestTemplate` that is used behind the
scenes, you can define a `RestTemplateCustomizer` bean in your configuration. For more
details, {spring-boot-reference}/#boot-features-restclient-customization[check the
documentation].
If you don't want the version to be upgraded automatically, you need to override the
`InitializrMetadataUpdateStrategy` bean to provide your own strategy when the metadata has
to be refreshed. For instance, you could swap to an implementation that always returns the
contents of static `application.yml`:
[source,java,indent=0]
----
@Bean
public InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy() {
return (metadata) -> metadata;
}
----
The thing to remember is that, by default, you don't have to worry about upgrading your
instance when a new Spring Boot version is released. However, you may need to
NOTE: If you opt-in for `SaganInitializrMetadataUpdateStrategy`, you have to
<<create-instance-advanced-config-caching,configure caching>> to avoid requesting that
service too often.

View File

@@ -22,7 +22,6 @@ import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.io.SimpleIndentStrategy;
import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
@@ -45,7 +44,6 @@ import io.spring.initializr.web.project.ProjectRequest;
import io.spring.initializr.web.project.ProjectRequestPlatformVersionTransformer;
import io.spring.initializr.web.support.DefaultDependencyMetadataProvider;
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider;
import io.spring.initializr.web.support.DefaultInitializrMetadataUpdateStrategy;
import io.spring.initializr.web.support.InitializrMetadataUpdateStrategy;
import org.springframework.beans.factory.ObjectProvider;
@@ -58,7 +56,6 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.NoOpCache;
@@ -110,19 +107,13 @@ public class InitializrAutoConfiguration {
return new NoOpCache("templates");
}
@Bean
@ConditionalOnMissingBean
public InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy(RestTemplateBuilder restTemplateBuilder,
ObjectMapper objectMapper) {
return new DefaultInitializrMetadataUpdateStrategy(restTemplateBuilder.build(), objectMapper);
}
@Bean
@ConditionalOnMissingBean(InitializrMetadataProvider.class)
public InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties,
InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy) {
ObjectProvider<InitializrMetadataUpdateStrategy> initializrMetadataUpdateStrategy) {
InitializrMetadata metadata = InitializrMetadataBuilder.fromInitializrProperties(properties).build();
return new DefaultInitializrMetadataProvider(metadata, initializrMetadataUpdateStrategy);
return new DefaultInitializrMetadataProvider(metadata,
initializrMetadataUpdateStrategy.getIfAvailable(() -> (current) -> current));
}
@Bean

View File

@@ -20,6 +20,7 @@ import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.metadata.DefaultMetadataElement;
import io.spring.initializr.metadata.InitializrConfiguration.Env;
import io.spring.initializr.metadata.InitializrMetadata;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -28,20 +29,21 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
/**
* A {@link InitializrMetadataUpdateStrategy} that refreshes the metadata with the status
* of the main spring.io site.
* A {@link InitializrMetadataUpdateStrategy} that refreshes the metadata with the latest
* state of the Spring Boot project.
*
* @author Stephane Nicoll
* @see Env#getSpringBootMetadataUrl()
*/
public class DefaultInitializrMetadataUpdateStrategy implements InitializrMetadataUpdateStrategy {
public class SaganInitializrMetadataUpdateStrategy implements InitializrMetadataUpdateStrategy {
private static final Log logger = LogFactory.getLog(DefaultInitializrMetadataUpdateStrategy.class);
private static final Log logger = LogFactory.getLog(SaganInitializrMetadataUpdateStrategy.class);
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public DefaultInitializrMetadataUpdateStrategy(RestTemplate restTemplate, ObjectMapper objectMapper) {
public SaganInitializrMetadataUpdateStrategy(RestTemplate restTemplate, ObjectMapper objectMapper) {
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
}

View File

@@ -37,7 +37,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.generator.test.project.ProjectStructure;
import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config;
import io.spring.initializr.web.mapper.InitializrMetadataVersion;
import io.spring.initializr.web.support.InitializrMetadataUpdateStrategy;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@@ -54,7 +53,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -366,12 +364,6 @@ public abstract class AbstractInitializrIntegrationTests {
@EnableAutoConfiguration
public static class Config {
// Disable metadata fetching from spring.io
@Bean
public InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy() {
return (metadata) -> metadata;
}
}
private enum BitMaskFilePermission {

View File

@@ -23,11 +23,10 @@ import io.spring.initializr.web.controller.CommandLineMetadataController;
import io.spring.initializr.web.controller.ProjectGenerationController;
import io.spring.initializr.web.controller.ProjectMetadataController;
import io.spring.initializr.web.controller.SpringCliDistributionController;
import io.spring.initializr.web.support.DefaultInitializrMetadataUpdateStrategy;
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider;
import io.spring.initializr.web.support.InitializrMetadataUpdateStrategy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -35,11 +34,8 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfigu
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -55,7 +51,7 @@ class InitializrAutoConfigurationTests {
private static final AutoConfigurations BASIC_AUTO_CONFIGURATIONS = AutoConfigurations
.of(RestTemplateAutoConfiguration.class, JacksonAutoConfiguration.class, InitializrAutoConfiguration.class);
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(BASIC_AUTO_CONFIGURATIONS);
@Test
@@ -72,26 +68,23 @@ class InitializrAutoConfigurationTests {
}
@Test
void autoConfigRegistersInitializrMetadataUpdateStrategy() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(InitializrMetadataUpdateStrategy.class));
}
@Test
void autoConfigWhenInitializrMetadataUpdateStrategyPresentDoesNotRegisterInitializrMetadataUpdateStrategy() {
this.contextRunner.withUserConfiguration(CustomInitializrMetadataUpdateStrategyConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(InitializrMetadataUpdateStrategy.class);
assertThat(context).hasBean("testInitializrMetadataUpdateStrategy");
});
}
@Test
void autoConfigRegistersInitializrMetadataProvider() {
void metadataProviderWithNoMetadataUpdateStrategyRegistersDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(InitializrMetadataProvider.class));
}
@Test
void autoConfigWhenInitializrMetadataProviderBeanPresentDoesNotRegisterInitializrMetadataProvider() {
void metadataProviderWithCustomInitializrMetadataUpdateStrategyIsRegistered() {
this.contextRunner.withUserConfiguration(CustomInitializrMetadataUpdateStrategyConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(DefaultInitializrMetadataProvider.class);
assertThat(context.getBean(DefaultInitializrMetadataProvider.class)).hasFieldOrPropertyWithValue(
"initializrMetadataUpdateStrategy",
context.getBean("testInitializrMetadataUpdateStrategy"));
});
}
@Test
void metadataProviderWithCustomInitializrMetadataProvider() {
this.contextRunner.withUserConfiguration(CustomInitializrMetadataProviderConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(InitializrMetadataProvider.class);
assertThat(context).hasBean("testInitializrMetadataProvider");
@@ -111,16 +104,6 @@ class InitializrAutoConfigurationTests {
});
}
@Test
void customRestTemplateBuilderIsUsed() {
this.contextRunner.withUserConfiguration(CustomRestTemplateConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(DefaultInitializrMetadataUpdateStrategy.class);
RestTemplate restTemplate = (RestTemplate) new DirectFieldAccessor(
context.getBean(DefaultInitializrMetadataUpdateStrategy.class)).getPropertyValue("restTemplate");
assertThat(restTemplate.getErrorHandler()).isSameAs(CustomRestTemplateConfiguration.errorHandler);
});
}
@Test
void webConfiguration() {
WebApplicationContextRunner webContextRunner = new WebApplicationContextRunner()
@@ -167,18 +150,6 @@ class InitializrAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(JCacheManagerCustomizer.class));
}
@Configuration
static class CustomRestTemplateConfiguration {
private static final ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
@Bean
RestTemplateCustomizer testRestTemplateCustomizer() {
return (b) -> b.setErrorHandler(errorHandler);
}
}
@Configuration
static class CustomTemplateRendererConfiguration {

View File

@@ -39,11 +39,11 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
/**
* Tests for {@link DefaultInitializrMetadataUpdateStrategy}.
* Tests for {@link SaganInitializrMetadataUpdateStrategy}.
*
* @author Stephane Nicoll
*/
class DefaultInitializrMetadataUpdateStrategyTests {
class SaganInitializrMetadataUpdateStrategyTests {
private static final ObjectMapper objectMapper = new ObjectMapper();
@@ -62,8 +62,8 @@ class DefaultInitializrMetadataUpdateStrategyTests {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true)
.addBootVersion("0.0.8.RELEASE", false).build();
assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE");
DefaultInitializrMetadataUpdateStrategy provider = new DefaultInitializrMetadataUpdateStrategy(
this.restTemplate, objectMapper);
SaganInitializrMetadataUpdateStrategy provider = new SaganInitializrMetadataUpdateStrategy(this.restTemplate,
objectMapper);
expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(), "metadata/sagan/spring-boot.json");
InitializrMetadata updatedMetadata = provider.update(metadata);
@@ -82,8 +82,8 @@ class DefaultInitializrMetadataUpdateStrategyTests {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true)
.addBootVersion("0.0.8.RELEASE", false).build();
assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE");
DefaultInitializrMetadataUpdateStrategy provider = new DefaultInitializrMetadataUpdateStrategy(
this.restTemplate, objectMapper);
SaganInitializrMetadataUpdateStrategy provider = new SaganInitializrMetadataUpdateStrategy(this.restTemplate,
objectMapper);
expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(),
"metadata/sagan/spring-boot-no-default.json");