Skip to content

Commit

Permalink
Add new REST API for project metadata
Browse files Browse the repository at this point in the history
Now that the previous Web API has been removed, this commit
adds a new REST API for various resources managed by this
application.
  • Loading branch information
bclozel committed Sep 21, 2020
1 parent 1815137 commit 604c842
Show file tree
Hide file tree
Showing 30 changed files with 1,623 additions and 4 deletions.
25 changes: 24 additions & 1 deletion sagan-site/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 9,7 @@ buildscript {

plugins {
id 'java'
id "org.asciidoctor.convert" version "1.5.3"
id "org.asciidoctor.convert" version "1.5.9.2"
id "com.gorylenko.gradle-git-properties" version "1.5.2"
}

Expand All @@ -19,6 19,10 @@ group = 'io.spring.sagan'
version = "1.0.0.BUILD-SNAPSHOT"
sourceCompatibility = '1.8'

ext {
snippetsDir = file('build/generated-snippets')
}

repositories {
mavenCentral()
}
Expand Down Expand Up @@ -83,6 87,8 @@ dependencies {
compile 'org.springframework.cloud:spring-cloud-spring-service-connector'
compile 'org.springframework.cloud:spring-cloud-cloudfoundry-connector'

testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc'

// datasource and connection pool dependencies
runtime 'org.postgresql:postgresql:9.4.1212'
runtime 'com.h2database:h2:1.4.200'
Expand All @@ -99,6 105,23 @@ dependencies {

}

test {
outputs.dir snippetsDir
}

asciidoctor {
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
dependsOn test
}

jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static/restdocs'
}
}

clean.doFirst {
delete "${projectDir}/data/"
println "Deleted ${projectDir}/data/"
Expand Down
199 changes: 199 additions & 0 deletions sagan-site/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 1,199 @@
= Project Metadata Service
Brian Clozel;
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:

[[overview]]
== Overview

Spring projects are https://spring.io/projects[listed and documented on the spring.io website].
We can find there information about releases, samples, support and more.

Spring.io also exposes a RESTful web service under https://spring.io/api for fetching and
updating Spring Projects Metadata. It uses hypermedia to describe the relationships between
resources and to allow navigation between them.

The source of this application is available in the https://github.com/spring-io/sagan[Sagan project].

[[mediatype]]
=== Media Type
This web service exposes resources with the `application/hal json` Media Type.
Requests sent to this service should `Accept` this type, like:

include::{snippets}/show-index/http-request.adoc[]


[[authentication]]
=== Authentication
This web service is publicly available here: https://spring.io/api.

Fetching resources using the HTTP `GET` method is permitted to all clients.
Other HTTP verbs like `POST`, `PUT`, `PATCH` and `DELETE` require authentication.

Authenticated requests should send
https://developer.github.com/v3/auth/#via-oauth-and-personal-access-tokens[a GitHub personal access token as basic authentication].
The user associated with this token *must be a member of the Spring team on GitHub*.


[[index-endpoint]]
=== Index Endpoint
The root endpoint of this web service lists the main resources: <<project, Projects>> and <<repository, Repositories>>:

include::{snippets}/show-index/http-response.adoc[]

The links allow you to access other resources:

include::{snippets}/show-index/links.adoc[]


[[project]]
== Projects
Spring Projects are listed on https://spring.io/projects[the official projects page].
A Spring Project has an official name in the Spring portfolio.
Its sources can be found in a git repository.
The team in charge of this project will build the sources and release the resulting artifacts in an <<repository, Artifact Repository>>.

Some projects are part of a Release train, i.e. a set of project releases that are known to be compatible.
Such projects are gathered under an umbrella project (or parent project).

We can `GET` the full collection of Spring Projects using the `"projects"` link provided at the root of the service:

include::{snippets}/list-projects/http-response.adoc[]

We can then fetch an individual project using its `"self"` link when listed in the full collection, for example for the Spring Boot project:

include::{snippets}/show-project/http-response.adoc[]

=== Response structure

include::{snippets}/show-project/response-fields.adoc[]

=== Links
Project responses provide links to other related resources:

include::{snippets}/show-project/links.adoc[]


[[project-status]]
=== Project Support Status
Each Project has an official support status; the goal here is to set expectations about the type of support you can expect from the Spring team:

[horizontal]
Incubating:: an experiment which might/might no be officially supported in the future.
Active:: actively and officially supported by the Spring team.
Community:: actively supported by the Spring community with limited involvement from the Spring team.
End Of Life:: not supported anymore; there won't be new releases for this project.


[[release]]
== Releases
The Project team selects the currently relevant releases; they're often releases that belong to active <<generation, Project Generations>>.
We can get the list of releases for a given project by following the `"releases"` link on the Project resource:

include::{snippets}/list-releases/http-response.adoc[]

[[fetch-release]]
=== Fetching a single Release
We can fetch a single release by following its canonical link:

include::{snippets}/show-release/http-response.adoc[]

==== Response structure

include::{snippets}/show-release/response-fields.adoc[]

[[release-status]]
==== Release version status

SNAPSHOT:: Unstable release with limited support; SNAPSHOT versions are released continuously
PRERELEASE:: Also known as Milestone, this a release meant to be tested by the community
GENERAL_AVAILABILITY:: Release Generally Available on public artifact repositories and getting full support from maintainers

==== Links
Release responses provide links to other related resources:

include::{snippets}/show-release/links.adoc[]



[[create-release]]
=== Adding a new Release
We can add a new Release to a Project:

include::{snippets}/create-release/http-request.adoc[]

include::{snippets}/create-release/http-response.adoc[]

NOTE: This request requires <<authentication>>.

==== Request structure

include::{snippets}/create-release/request-fields.adoc[]


[[delete-release]]
=== Deleting an existing Release
We can delete an existing Release from a Project:

include::{snippets}/delete-release/http-request.adoc[]

include::{snippets}/delete-release/http-response.adoc[]

NOTE: This request requires <<authentication>>.



[[generation]]
== Generations
Each project has an official <<project-status, Support Status>>, but not all releases are supported at any time.
Releases are grouped as Generations. Depending on the project and its release policy, a Generation usually
regroups all maintenance Releases for a given minor version or a specific release train.

Developers should upgrade to the latest Release at their earliest convenience;
the Spring team helps drive that decision by providing end of support dates for each generation.

Each project generation has two periods of active support:
https://tanzu.vmware.com/support/oss[Open Source support] and https://tanzu.vmware.com/support/lifecycle_policy[Commercial support].
All releases cut during these support periods are publicly available in the <<repository, artifact repositories>>.

We can get the list of generations for a given project by following the `"generations"` link on the Project resource:

include::{snippets}/list-generations/http-response.adoc[]

We can of course fetch a single generation by following its canonical link:

include::{snippets}/show-generation/http-response.adoc[]

=== Response structure

include::{snippets}/show-generation/response-fields.adoc[]



[[repository]]
== Artifact Repositories
Releases are hosted on public Artifact Repositories.
The Spring team deploys artifacts to different repositories, depending on the <<releases-status, Release Status>>.
You can configure your build system to resolve dependencies from the artifact repositories listed by this service.
"Generally Available" releases are also available on Maven Central and its mirrors.

Each <<release, Project Release>> has a link to an Artifact Repository resource.
You can fetch the full list of repositories managed by the Spring team by following the `"repositories"` link on the root endpoint:

include::{snippets}/list-repositories/http-response.adoc[]

And get a single repository by following its canonical link:

include::{snippets}/show-repository/http-response.adoc[]

=== Response structure

include::{snippets}/show-repository/response-fields.adoc[]

=== Links

include::{snippets}/show-repository/links.adoc[]
12 changes: 12 additions & 0 deletions sagan-site/src/main/java/sagan/MvcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 24,9 @@
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
Expand Down Expand Up @@ -60,6 62,16 @@ public StaticPagePathFinder staticPagePathFinder(ResourcePatternResolver resourc
public void handleException(ResourceNotFoundException ex) {
}

@Override
public void configurePathMatch(PathMatchConfigurer matcher) {
matcher.setUseSuffixPatternMatch(false);
}

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
try {
Expand Down
6 changes: 3 additions & 3 deletions sagan-site/src/main/java/sagan/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 107,9 @@ public void setEnvironment(Environment environment) {
@Override
protected void configure(HttpSecurity http) throws Exception {
configureHeaders(http.headers());
http.requestMatchers().antMatchers("/project_metadata/**")
.and().authorizeRequests().antMatchers(HttpMethod.HEAD, "/project_metadata/*").permitAll()
.antMatchers(HttpMethod.GET, "/project_metadata/*").permitAll()
http.requestMatchers().antMatchers("/api/**")
.and().authorizeRequests().antMatchers(HttpMethod.HEAD, "/api/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterAfter(githubBasicAuthFilter(), BasicAuthenticationFilter.class)
Expand Down
27 changes: 27 additions & 0 deletions sagan-site/src/main/java/sagan/site/webapi/IndexController.java
Original file line number Diff line number Diff line change
@@ -0,0 1,27 @@
package sagan.site.webapi;

import sagan.site.webapi.project.ProjectMetadataController;
import sagan.site.webapi.repository.RepositoryMetadataController;

import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

/**
* Lists all resources at the root of the Web API
*/
@RestController
class IndexController {

@GetMapping(path = "/api", produces = MediaTypes.HAL_JSON_VALUE)
public ResourceSupport index() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(methodOn(ProjectMetadataController.class).listProjects()).withRel("projects"));
resource.add(linkTo(methodOn(RepositoryMetadataController.class).listRepositories()).withRel("repositories"));
return resource;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,19 @@
package sagan.site.webapi;

import sagan.site.webapi.release.InvalidReleaseException;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
*
*/
@ControllerAdvice(basePackageClasses = IndexController.class)
public class WebApiControllerAdvice {

@ExceptionHandler(InvalidReleaseException.class)
public ResponseEntity<String> handleInvalidReleases(InvalidReleaseException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
}
Loading

0 comments on commit 604c842

Please sign in to comment.