Update spring.io metadata strategy to use the new API

Prior to this commit, the Spring Boot metadata reader would use a legacy
API endpoint on spring.io to fetch the Spring Boot versions.
The current website is about to be retired for a different
infrastructure and we should take this opportunity to update the
endpoint we're using.

This commit updates the metadata reader strategy to now use the
`https://spring.io/api/projects/spring-boot/releases` endpoint to fetch
the relevant information. The JSON format itself changed slightly and
this commit adapts to the new format as well.

Closes gh-1369
This commit is contained in:
Brian Clozel
2023-01-18 15:02:42 +01:00
parent d9fb4b05b4
commit c1e222901d
15 changed files with 341 additions and 316 deletions

View File

@@ -389,7 +389,7 @@ to redeploy the application every time. Implementing your own
`InitializrMetadataUpdateStrategy` bean allows you to update the metadata at runtime. `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 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 Boot], the latest versions are displayed. `SpringIoInitializrMetadataUpdateStrategy` is an
implementation of that strategy that fetches the latest Spring Boot versions and update implementation of that strategy that fetches the latest Spring Boot versions and update
the metadata to make sure a running instance using those conventions always get the latest the metadata to make sure a running instance using those conventions always get the latest
available versions. available versions.
@@ -399,7 +399,7 @@ scenes, you can define a `RestTemplateCustomizer` bean in your configuration. Fo
details, {spring-boot-reference}/#boot-features-restclient-customization[check the details, {spring-boot-reference}/#boot-features-restclient-customization[check the
documentation]. documentation].
NOTE: If you opt-in for `SaganInitializrMetadataUpdateStrategy`, you have to NOTE: If you opt-in for `SpringIoInitializrMetadataUpdateStrategy`, you have to
<<create-instance-advanced-config-caching,configure caching>> to avoid requesting that <<create-instance-advanced-config-caching,configure caching>> to avoid requesting that
service too often. service too often.
@@ -1153,7 +1153,7 @@ automatically:
[[create-instance-advanced-config-caching]] [[create-instance-advanced-config-caching]]
=== Caching configuration === Caching configuration
If you use the service, you'll notice that the logs have lots of entries with the message If you use the service, you'll notice that the logs have lots of entries with the message
`Fetching boot metadata from https://spring.io/project_metadata/spring-boot`. To avoid `Fetching boot metadata from https://spring.io/api/projects/spring-boot/releases`. To avoid
checking for the latest Spring Boot versions too often, you should enable caching on your checking for the latest Spring Boot versions too often, you should enable caching on your
service. Spring Initializr has some auto-configuration to apply the proper caches if you service. Spring Initializr has some auto-configuration to apply the proper caches if you
are willing to use a JCache (JSR-107) implementation. are willing to use a JCache (JSR-107) implementation.

View File

@@ -179,7 +179,7 @@ public class InitializrConfiguration {
/** /**
* The metadata url of the Spring Boot project. * The metadata url of the Spring Boot project.
*/ */
private String springBootMetadataUrl = "https://spring.io/project_metadata/spring-boot"; private String springBootMetadataUrl = "https://spring.io/api/projects/spring-boot/releases";
/** /**
* Tracking code for Google Analytics. Only enabled if a value is explicitly * Tracking code for Google Analytics. Only enabled if a value is explicitly

View File

@@ -46,12 +46,12 @@ Finally, you can configure your IDE to point to another service instance.
There are two main customizations to the sample. There are two main customizations to the sample.
=== Metadata Update Strategy === Metadata Update Strategy
First, `ServiceApplication` has a dedicated strategy to update the metadata with the available Spring Boot versions from Sagan (`spring.io`). First, `ServiceApplication` has a dedicated strategy to update the metadata with the available Spring Boot versions from `https://spring.io`.
When you issue your first request, you should see something like the following: When you issue your first request, you should see something like the following:
[indent=0] [indent=0]
---- ----
Fetching Spring Boot metadata from https://spring.io/project_metadata/spring-boot Fetching Spring Boot metadata from https://spring.io/api/projects/spring-boot/releases
---- ----
On further request, you won't see this log entry as the content is cached. On further request, you won't see this log entry as the content is cached.

View File

@@ -17,7 +17,7 @@
package sample.service; package sample.service;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.web.support.SaganInitializrMetadataUpdateStrategy; import io.spring.initializr.web.support.SpringIoInitializrMetadataUpdateStrategy;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -40,11 +40,11 @@ public class ServiceApplication {
SpringApplication.run(ServiceApplication.class, args); SpringApplication.run(ServiceApplication.class, args);
} }
// This bean opt-in for fetching available Spring Boot versions from Sagan (spring.io) // This bean opt-in for fetching available Spring Boot versions from https://spring.io
@Bean @Bean
SaganInitializrMetadataUpdateStrategy saganInitializrMetadataUpdateStrategy(RestTemplateBuilder restTemplateBuilder, SpringIoInitializrMetadataUpdateStrategy springIoInitializrMetadataUpdateStrategy(
ObjectMapper objectMapper) { RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) {
return new SaganInitializrMetadataUpdateStrategy(restTemplateBuilder.build(), objectMapper); return new SpringIoInitializrMetadataUpdateStrategy(restTemplateBuilder.build(), objectMapper);
} }
} }

View File

@@ -59,7 +59,7 @@ class SpringBootMetadataReader {
* @return the versions * @return the versions
*/ */
List<DefaultMetadataElement> getBootVersions() { List<DefaultMetadataElement> getBootVersions() {
ArrayNode releases = (ArrayNode) this.content.get("projectReleases"); ArrayNode releases = (ArrayNode) this.content.get("_embedded").get("releases");
List<DefaultMetadataElement> list = new ArrayList<>(); List<DefaultMetadataElement> list = new ArrayList<>();
for (JsonNode node : releases) { for (JsonNode node : releases) {
DefaultMetadataElement versionMetadata = parseVersionMetadata(node); DefaultMetadataElement versionMetadata = parseVersionMetadata(node);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -35,15 +35,15 @@ import org.springframework.web.client.RestTemplate;
* @author Stephane Nicoll * @author Stephane Nicoll
* @see Env#getSpringBootMetadataUrl() * @see Env#getSpringBootMetadataUrl()
*/ */
public class SaganInitializrMetadataUpdateStrategy implements InitializrMetadataUpdateStrategy { public class SpringIoInitializrMetadataUpdateStrategy implements InitializrMetadataUpdateStrategy {
private static final Log logger = LogFactory.getLog(SaganInitializrMetadataUpdateStrategy.class); private static final Log logger = LogFactory.getLog(SpringIoInitializrMetadataUpdateStrategy.class);
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public SaganInitializrMetadataUpdateStrategy(RestTemplate restTemplate, ObjectMapper objectMapper) { public SpringIoInitializrMetadataUpdateStrategy(RestTemplate restTemplate, ObjectMapper objectMapper) {
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -52,28 +52,30 @@ class SpringBootMetadataReaderTests {
@Test @Test
void readAvailableVersions() throws IOException { void readAvailableVersions() throws IOException {
this.server.expect(requestTo("https://spring.io/project_metadata/spring-boot")).andRespond( this.server.expect(requestTo("https://spring.io/api/projects/spring-boot/releases")).andRespond(
withSuccess(new ClassPathResource("metadata/sagan/spring-boot.json"), MediaType.APPLICATION_JSON)); withSuccess(new ClassPathResource("metadata/springio/spring-boot.json"), MediaType.APPLICATION_JSON));
List<DefaultMetadataElement> versions = new SpringBootMetadataReader(this.objectMapper, this.restTemplate, List<DefaultMetadataElement> versions = new SpringBootMetadataReader(this.objectMapper, this.restTemplate,
this.metadata.getConfiguration().getEnv().getSpringBootMetadataUrl()).getBootVersions(); this.metadata.getConfiguration().getEnv().getSpringBootMetadataUrl()).getBootVersions();
assertThat(versions).hasSize(5); assertThat(versions).hasSize(7);
assertSpringBootVersion(versions.get(0), "2.5.0-M1", "2.5.0 (M1)", false); assertSpringBootVersion(versions.get(0), "3.0.2-SNAPSHOT", "3.0.2 (SNAPSHOT)", false);
assertSpringBootVersion(versions.get(1), "2.4.1-SNAPSHOT", "2.4.1 (SNAPSHOT)", false); assertSpringBootVersion(versions.get(1), "3.0.1", "3.0.1", true);
assertSpringBootVersion(versions.get(2), "2.4.0", "2.4.0", true); assertSpringBootVersion(versions.get(2), "2.7.8-SNAPSHOT", "2.7.8 (SNAPSHOT)", false);
assertSpringBootVersion(versions.get(3), "2.3.8.BUILD-SNAPSHOT", "2.3.8 (SNAPSHOT)", false); assertSpringBootVersion(versions.get(3), "2.7.7", "2.7.7", false);
assertSpringBootVersion(versions.get(4), "2.3.7.RELEASE", "2.3.7", false); assertSpringBootVersion(versions.get(4), "2.6.14", "2.6.14", false);
assertSpringBootVersion(versions.get(5), "2.5.14", "2.5.14", false);
assertSpringBootVersion(versions.get(6), "2.4.13", "2.4.13", false);
this.server.verify(); this.server.verify();
} }
@Test @Test
void readAvailableVersionsWithInvalidVersion() throws IOException { void readAvailableVersionsWithInvalidVersion() throws IOException {
this.server.expect(requestTo("https://spring.io/project_metadata/spring-boot")).andRespond(withSuccess( this.server.expect(requestTo("https://spring.io/api/projects/spring-boot/releases"))
new ClassPathResource("metadata/sagan/spring-boot-invalid-version.json"), MediaType.APPLICATION_JSON)); .andRespond(withSuccess(new ClassPathResource("metadata/springio/spring-boot-invalid-version.json"),
MediaType.APPLICATION_JSON));
List<DefaultMetadataElement> versions = new SpringBootMetadataReader(this.objectMapper, this.restTemplate, List<DefaultMetadataElement> versions = new SpringBootMetadataReader(this.objectMapper, this.restTemplate,
this.metadata.getConfiguration().getEnv().getSpringBootMetadataUrl()).getBootVersions(); this.metadata.getConfiguration().getEnv().getSpringBootMetadataUrl()).getBootVersions();
assertThat(versions).hasSize(2); assertThat(versions).hasSize(1);
assertSpringBootVersion(versions.get(0), "2.5.0-M1", "2.5.0 (M1)", false); assertSpringBootVersion(versions.get(0), "3.0.1", "3.0.1", true);
assertSpringBootVersion(versions.get(1), "2.4.0", "2.4.0", true);
this.server.verify(); this.server.verify();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -39,11 +39,11 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
/** /**
* Tests for {@link SaganInitializrMetadataUpdateStrategy}. * Tests for {@link SpringIoInitializrMetadataUpdateStrategy}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
class SaganInitializrMetadataUpdateStrategyTests { class SpringIoInitializrMetadataUpdateStrategyTests {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@@ -62,19 +62,22 @@ class SaganInitializrMetadataUpdateStrategyTests {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true) InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true)
.addBootVersion("0.0.8.RELEASE", false).build(); .addBootVersion("0.0.8.RELEASE", false).build();
assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE"); assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE");
SaganInitializrMetadataUpdateStrategy provider = new SaganInitializrMetadataUpdateStrategy(this.restTemplate, SpringIoInitializrMetadataUpdateStrategy provider = new SpringIoInitializrMetadataUpdateStrategy(
objectMapper); this.restTemplate, objectMapper);
expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(), "metadata/sagan/spring-boot.json"); expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(),
"metadata/springio/spring-boot.json");
InitializrMetadata updatedMetadata = provider.update(metadata); InitializrMetadata updatedMetadata = provider.update(metadata);
assertThat(updatedMetadata.getBootVersions()).isNotNull(); assertThat(updatedMetadata.getBootVersions()).isNotNull();
List<DefaultMetadataElement> updatedBootVersions = updatedMetadata.getBootVersions().getContent(); List<DefaultMetadataElement> updatedBootVersions = updatedMetadata.getBootVersions().getContent();
assertThat(updatedBootVersions).hasSize(5); assertThat(updatedBootVersions).hasSize(7);
assertBootVersion(updatedBootVersions.get(0), "2.5.0 (M1)", false); assertBootVersion(updatedBootVersions.get(0), "3.0.2 (SNAPSHOT)", false);
assertBootVersion(updatedBootVersions.get(1), "2.4.1 (SNAPSHOT)", false); assertBootVersion(updatedBootVersions.get(1), "3.0.1", true);
assertBootVersion(updatedBootVersions.get(2), "2.4.0", true); assertBootVersion(updatedBootVersions.get(2), "2.7.8 (SNAPSHOT)", false);
assertBootVersion(updatedBootVersions.get(3), "2.3.8 (SNAPSHOT)", false); assertBootVersion(updatedBootVersions.get(3), "2.7.7", false);
assertBootVersion(updatedBootVersions.get(4), "2.3.7", false); assertBootVersion(updatedBootVersions.get(4), "2.6.14", false);
assertBootVersion(updatedBootVersions.get(5), "2.5.14", false);
assertBootVersion(updatedBootVersions.get(6), "2.4.13", false);
} }
@Test @Test
@@ -82,20 +85,22 @@ class SaganInitializrMetadataUpdateStrategyTests {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true) InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("0.0.9.RELEASE", true)
.addBootVersion("0.0.8.RELEASE", false).build(); .addBootVersion("0.0.8.RELEASE", false).build();
assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE"); assertThat(metadata.getBootVersions().getDefault().getId()).isEqualTo("0.0.9.RELEASE");
SaganInitializrMetadataUpdateStrategy provider = new SaganInitializrMetadataUpdateStrategy(this.restTemplate, SpringIoInitializrMetadataUpdateStrategy provider = new SpringIoInitializrMetadataUpdateStrategy(
objectMapper); this.restTemplate, objectMapper);
expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(), expectJson(metadata.getConfiguration().getEnv().getSpringBootMetadataUrl(),
"metadata/sagan/spring-boot-no-default.json"); "metadata/springio/spring-boot-no-default.json");
InitializrMetadata updatedMetadata = provider.update(metadata); InitializrMetadata updatedMetadata = provider.update(metadata);
assertThat(updatedMetadata.getBootVersions()).isNotNull(); assertThat(updatedMetadata.getBootVersions()).isNotNull();
List<DefaultMetadataElement> updatedBootVersions = updatedMetadata.getBootVersions().getContent(); List<DefaultMetadataElement> updatedBootVersions = updatedMetadata.getBootVersions().getContent();
assertThat(updatedBootVersions).hasSize(5); assertThat(updatedBootVersions).hasSize(7);
assertBootVersion(updatedBootVersions.get(0), "2.5.0 (M1)", true); assertBootVersion(updatedBootVersions.get(0), "3.0.2 (SNAPSHOT)", true);
assertBootVersion(updatedBootVersions.get(1), "2.4.1 (SNAPSHOT)", false); assertBootVersion(updatedBootVersions.get(1), "3.0.1", false);
assertBootVersion(updatedBootVersions.get(2), "2.4.0", false); assertBootVersion(updatedBootVersions.get(2), "2.7.8 (SNAPSHOT)", false);
assertBootVersion(updatedBootVersions.get(3), "2.3.8 (SNAPSHOT)", false); assertBootVersion(updatedBootVersions.get(3), "2.7.7", false);
assertBootVersion(updatedBootVersions.get(4), "2.3.7", false); assertBootVersion(updatedBootVersions.get(4), "2.6.14", false);
assertBootVersion(updatedBootVersions.get(5), "2.5.14", false);
assertBootVersion(updatedBootVersions.get(6), "2.4.13", false);
} }
private static void assertBootVersion(DefaultMetadataElement actual, String name, boolean defaultVersion) { private static void assertBootVersion(DefaultMetadataElement actual, String name, boolean defaultVersion) {

View File

@@ -137,7 +137,7 @@
"version": "1.0.0.RELEASE" "version": "1.0.0.RELEASE"
} }
}, },
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot" "springBootMetadataUrl": "https://spring.io/api/projects/spring-boot/releases"
}}, }},
"dependencies": { "dependencies": {
"content": [ "content": [

View File

@@ -1,66 +0,0 @@
{
"id": "spring-boot",
"name": "Spring Boot",
"repoUrl": "https://github.com/spring-projects/spring-boot",
"siteUrl": "https://projects.spring.io/spring-boot",
"category": "active",
"stackOverflowTags": "spring-boot",
"projectReleases": [
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-milestones",
"name": "Spring Milestones",
"url": "https://repo.spring.io/libs-milestone",
"snapshotsEnabled": false
},
"preRelease": true,
"generalAvailability": false,
"versionDisplayName": "2.5.0-M1",
"current": false,
"snapshot": false,
"version": "2.5.0-M1"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-snapshots",
"name": "Spring Snapshots",
"url": "https://repo.spring.io/libs-snapshot",
"snapshotsEnabled": true
},
"preRelease": false,
"generalAvailability": false,
"versionDisplayName": "2.4.1",
"current": false,
"snapshot": true,
"version": "Not a valid version"
},
{
"releaseStatus": "GENERAL_AVAILABILITY",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": null,
"preRelease": false,
"generalAvailability": true,
"versionDisplayName": "2.4.0",
"current": true,
"snapshot": false,
"version": "2.4.0"
}
],
"aggregator": false,
"stackOverflowTagList": [
"spring-boot"
]
}

View File

@@ -1,99 +0,0 @@
{
"id": "spring-boot",
"name": "Spring Boot",
"repoUrl": "https://github.com/spring-projects/spring-boot",
"siteUrl": "https://projects.spring.io/spring-boot",
"category": "active",
"stackOverflowTags": "spring-boot",
"projectReleases": [
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-milestones",
"name": "Spring Milestones",
"url": "https://repo.spring.io/libs-milestone",
"snapshotsEnabled": false
},
"preRelease": true,
"generalAvailability": false,
"versionDisplayName": "2.5.0-M1",
"current": false,
"snapshot": false,
"version": "2.5.0-M1"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-snapshots",
"name": "Spring Snapshots",
"url": "https://repo.spring.io/libs-snapshot",
"snapshotsEnabled": true
},
"preRelease": false,
"generalAvailability": false,
"versionDisplayName": "2.4.1",
"current": false,
"snapshot": true,
"version": "2.4.1-SNAPSHOT"
},
{
"releaseStatus": "GENERAL_AVAILABILITY",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": null,
"preRelease": false,
"generalAvailability": true,
"versionDisplayName": "2.4.0",
"current": false,
"snapshot": false,
"version": "2.4.0"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.3.8.BUILD-SNAPSHOT/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.3.8.BUILD-SNAPSHOT/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-snapshots",
"name": "Spring Snapshots",
"url": "https://repo.spring.io/libs-snapshot",
"snapshotsEnabled": true
},
"preRelease": false,
"generalAvailability": false,
"versionDisplayName": "2.3.8",
"current": false,
"snapshot": true,
"version": "2.3.8.BUILD-SNAPSHOT"
},
{
"releaseStatus": "GENERAL_AVAILABILITY",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/current/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": null,
"preRelease": false,
"generalAvailability": true,
"versionDisplayName": "2.3.7",
"current": false,
"snapshot": false,
"version": "2.3.7.RELEASE"
}
],
"aggregator": false,
"stackOverflowTagList": [
"spring-boot"
]
}

View File

@@ -1,99 +0,0 @@
{
"id": "spring-boot",
"name": "Spring Boot",
"repoUrl": "https://github.com/spring-projects/spring-boot",
"siteUrl": "https://projects.spring.io/spring-boot",
"category": "active",
"stackOverflowTags": "spring-boot",
"projectReleases": [
{
"releaseStatus": "GENERAL_AVAILABILITY",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.0/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": null,
"preRelease": false,
"generalAvailability": true,
"versionDisplayName": "2.4.0",
"current": true,
"snapshot": false,
"version": "2.4.0"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.0-M1/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-milestones",
"name": "Spring Milestones",
"url": "https://repo.spring.io/libs-milestone",
"snapshotsEnabled": false
},
"preRelease": true,
"generalAvailability": false,
"versionDisplayName": "2.5.0-M1",
"current": false,
"snapshot": false,
"version": "2.5.0-M1"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.1-SNAPSHOT/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-snapshots",
"name": "Spring Snapshots",
"url": "https://repo.spring.io/libs-snapshot",
"snapshotsEnabled": true
},
"preRelease": false,
"generalAvailability": false,
"versionDisplayName": "2.4.1",
"current": false,
"snapshot": true,
"version": "2.4.1-SNAPSHOT"
},
{
"releaseStatus": "SNAPSHOT",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/2.3.8.BUILD-SNAPSHOT/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.3.8.BUILD-SNAPSHOT/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": {
"id": "spring-snapshots",
"name": "Spring Snapshots",
"url": "https://repo.spring.io/libs-snapshot",
"snapshotsEnabled": true
},
"preRelease": false,
"generalAvailability": false,
"versionDisplayName": "2.3.8",
"current": false,
"snapshot": true,
"version": "2.3.8.BUILD-SNAPSHOT"
},
{
"releaseStatus": "GENERAL_AVAILABILITY",
"refDocUrl": "https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/current/api/",
"groupId": "org.springframework.boot",
"artifactId": "spring-boot",
"repository": null,
"preRelease": false,
"generalAvailability": true,
"versionDisplayName": "2.3.7",
"current": false,
"snapshot": false,
"version": "2.3.7.RELEASE"
}
],
"aggregator": false,
"stackOverflowTagList": [
"spring-boot"
]
}

View File

@@ -0,0 +1,44 @@
{
"_embedded": {
"releases": [
{
"version": "invalid version",
"status": "SNAPSHOT",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.2-SNAPSHOT"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-snapshots"
}
}
},
{
"version": "3.0.1",
"status": "GENERAL_AVAILABILITY",
"current": true,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/current/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/current/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.1"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
}
]
},
"_links": {
"current": {
"href": "https://spring.io/api/projects/spring-boot/releases/current"
},
"project": {
"href": "https://spring.io/api/projects/spring-boot"
}
}
}

View File

@@ -0,0 +1,119 @@
{
"_embedded": {
"releases": [
{
"version": "3.0.2-SNAPSHOT",
"status": "SNAPSHOT",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.2-SNAPSHOT"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-snapshots"
}
}
},
{
"version": "3.0.1",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/current/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/current/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.1"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.7.8-SNAPSHOT",
"status": "SNAPSHOT",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.8-SNAPSHOT/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.8-SNAPSHOT/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.7.8-SNAPSHOT"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-snapshots"
}
}
},
{
"version": "2.7.7",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.7/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.7/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.7.7"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.6.14",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.6.14/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.6.14/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.6.14"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.5.14",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.14/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.14/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.5.14"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.4.13",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.13/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.4.13"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
}
]
},
"_links": {
"current": {
"href": "https://spring.io/api/projects/spring-boot/releases/current"
},
"project": {
"href": "https://spring.io/api/projects/spring-boot"
}
}
}

View File

@@ -0,0 +1,119 @@
{
"_embedded": {
"releases": [
{
"version": "3.0.2-SNAPSHOT",
"status": "SNAPSHOT",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/3.0.2-SNAPSHOT/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.2-SNAPSHOT"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-snapshots"
}
}
},
{
"version": "3.0.1",
"status": "GENERAL_AVAILABILITY",
"current": true,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/current/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/current/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/3.0.1"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.7.8-SNAPSHOT",
"status": "SNAPSHOT",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.8-SNAPSHOT/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.8-SNAPSHOT/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.7.8-SNAPSHOT"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-snapshots"
}
}
},
{
"version": "2.7.7",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.7/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.7.7/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.7.7"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.6.14",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.6.14/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.6.14/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.6.14"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.5.14",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.14/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.5.14/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.5.14"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
},
{
"version": "2.4.13",
"status": "GENERAL_AVAILABILITY",
"current": false,
"referenceDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/",
"apiDocUrl": "https://docs.spring.io/spring-boot/docs/2.4.13/api/",
"_links": {
"self": {
"href": "https://spring.io/api/projects/spring-boot/releases/2.4.13"
},
"repository": {
"href": "https://spring.io/api/repositories/spring-releases"
}
}
}
]
},
"_links": {
"current": {
"href": "https://spring.io/api/projects/spring-boot/releases/current"
},
"project": {
"href": "https://spring.io/api/projects/spring-boot"
}
}
}