Redirect Spring cli distribution bundle

This commit adds the '/spring' endpoint that is used to download the
Spring CLI distribution bundle. Instead of relying on the presence of
a local 'spring.zip' file uploaded as part of the application, a
redirect to a configurable repository is used.

It is possible to download both the zip and the tar.gz distribution
by specifying the extension in the url (i.e. /spring.tar.gz provides
the tar.gz distribution.

Fixes gh-31
This commit is contained in:
Stephane Nicoll
2014-08-19 16:25:42 +02:00
parent 97df5393e9
commit 95441ef19c
11 changed files with 174 additions and 30 deletions

View File

@@ -71,11 +71,9 @@ An example Cloud Foundry `manifest.yml` file is provided. You should ensure that
the application name and URL (name and host values) are suitable for your environment
before running `cf push`.
You can jar up the app and make it executable in any environment. Care is needed with the includes and excludes:
You can jar up the app and make it executable in any environment.
$ version=1.1.5.RELEASE
$ wget -O spring.zip https://repo.spring.io/org/springframework/boot/spring-boot-cli/${version}/spring-boot-cli-${version}-bin.zip
$ spring jar --include '+spring.zip' start.jar app.groovy
$ spring jar start.jar app.groovy
To deploy on Cloudfoundry:

View File

@@ -21,6 +21,8 @@ import javax.annotation.PostConstruct
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import groovy.transform.ToString
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties
@@ -33,6 +35,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
* <li>Supported Java versions</li>
* <li>Supported language</li>
* <li>Supported Spring Boot versions</li>
* <li>Default settings used to generate the project</li>
* <li>Environment related settings</li>
* </ul>
*
* @author Stephane Nicoll
@@ -41,6 +45,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false)
class InitializrMetadata {
private static final Logger logger = LoggerFactory.getLogger(InitializrMetadata)
final List<DependencyGroup> dependencies = new ArrayList<DependencyGroup>()
final List<Type> types = new ArrayList<Type>()
@@ -55,6 +61,9 @@ class InitializrMetadata {
final Defaults defaults = new Defaults()
@JsonIgnore
final Env env = new Env()
@JsonIgnore
final Map<String, Dependency> indexedDependencies = new HashMap<String, Dependency>()
@@ -76,11 +85,6 @@ class InitializrMetadata {
request[key] = value
}
}
request.type = getDefault(types, request.type)
request.packaging = getDefault(packagings, request.packaging)
request.javaVersion = getDefault(javaVersions, request.javaVersion)
request.language = getDefault(languages, request.language)
request.bootVersion = getDefault(bootVersions, request.bootVersion)
request
}
@@ -98,6 +102,13 @@ class InitializrMetadata {
}
}
}
env.validate()
defaults.type = getDefault(types)
defaults.packaging = getDefault(packagings)
defaults.javaVersion = getDefault(javaVersions)
defaults.language = getDefault(languages)
defaults.bootVersion = getDefault(bootVersions)
}
private void indexDependency(String id, Dependency dependency) {
@@ -135,13 +146,14 @@ class InitializrMetadata {
}
}
static def getDefault(List elements, String defaultValue) {
static def getDefault(List elements) {
for (DefaultIdentifiableElement element : elements) {
if (element.default) {
return element.id
}
}
return defaultValue
logger.warn('No default found amongst' + elements)
return (elements.isEmpty() ? null : elements.get(0).id)
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -225,6 +237,11 @@ class InitializrMetadata {
String name = 'demo'
String description = 'Demo project for Spring Boot'
String packageName
String type
String packaging
String javaVersion
String language
String bootVersion
/**
* Return the artifactId or the name of the project if none is set.
@@ -242,6 +259,28 @@ class InitializrMetadata {
}
/**
* Defines additional environment settings
*/
static class Env {
String artifactRepository = 'https://repo.spring.io/release/'
/**
* Create an URL suitable to download Spring Boot cli for the specified version and extension.
*/
String createCliDistributionURl(String version, String extension) {
artifactRepository + "org/springframework/boot/spring-boot-cli/$version/spring-boot-cli-$version-bin.$extension"
}
void validate() {
if (!artifactRepository.endsWith('/')) {
artifactRepository = artifactRepository + '/'
}
}
}
static class DefaultIdentifiableElement extends IdentifiableElement {
@JsonIgnore

View File

@@ -74,6 +74,16 @@ class MainController {
template 'home.html', model
}
@RequestMapping('/spring')
String spring() {
'redirect:' + metadata.env.createCliDistributionURl(metadata.defaults.bootVersion, 'zip')
}
@RequestMapping(value = ['/spring.tar.gz', 'spring.tgz'])
String springTgz() {
'redirect:' + metadata.env.createCliDistributionURl(metadata.defaults.bootVersion, 'tar.gz')
}
@RequestMapping('/pom')
@ResponseBody
ResponseEntity<byte[]> pom(ProjectRequest request) {
@@ -124,4 +134,6 @@ class MainController {
result
}
}

View File

@@ -120,7 +120,7 @@ class InitializrMetadataTests {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('foo', dependency, dependency2).get()
.addDependencyGroup('foo', dependency, dependency2).validateAndGet()
assertSame dependency, metadata.getDependency('first')
assertSame dependency2, metadata.getDependency('second')
@@ -137,7 +137,7 @@ class InitializrMetadataTests {
thrown.expect(IllegalArgumentException)
thrown.expectMessage('conflict')
builder.get()
builder.validateAndGet()
}
@Test
@@ -147,7 +147,7 @@ class InitializrMetadataTests {
dependency.aliases.add('alias2')
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('foo', dependency).get()
.addDependencyGroup('foo', dependency).validateAndGet()
assertSame dependency, metadata.getDependency('first')
assertSame dependency, metadata.getDependency('alias1')
@@ -168,28 +168,42 @@ class InitializrMetadataTests {
thrown.expect(IllegalArgumentException)
thrown.expectMessage('alias2')
builder.get()
builder.validateAndGet()
}
@Test
void createProjectRequest() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().get()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()
ProjectRequest request = doCreateProjectRequest(metadata)
assertEquals metadata.defaults.groupId, request.groupId
}
@Test
void validateArtifactRepository() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults().instance()
metadata.env.artifactRepository = 'http://foo/bar'
metadata.validate()
assertEquals 'http://foo/bar/', metadata.env.artifactRepository
}
@Test
void getDefaultNoDefault() {
List elements = []
elements << createJavaVersion('one', false) << createJavaVersion('two', false)
assertEquals 'three', InitializrMetadata.getDefault(elements, 'three')
assertEquals 'one', InitializrMetadata.getDefault(elements)
}
@Test
void getDefaultWithDefault() {
void getDefaultEmpty() {
List elements = []
assertNull InitializrMetadata.getDefault(elements)
}
@Test
void getDefault() {
List elements = []
elements << createJavaVersion('one', false) << createJavaVersion('two', true)
assertEquals 'two', InitializrMetadata.getDefault(elements, 'three')
assertEquals 'two', InitializrMetadata.getDefault(elements)
}
private static ProjectRequest doCreateProjectRequest(InitializrMetadata metadata) {

View File

@@ -31,7 +31,7 @@ class ProjectGeneratorTests {
@Before
void setup() {
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('test', 'web', 'security', 'data-jpa', 'aop', 'batch', 'integration').get()
.addDependencyGroup('test', 'web', 'security', 'data-jpa', 'aop', 'batch', 'integration').validateAndGet()
projectGenerator.metadata = metadata
}
@@ -59,7 +59,7 @@ class ProjectGeneratorTests {
dependency.facets << 'web'
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('core', 'web', 'security', 'data-jpa')
.addDependencyGroup('test', dependency).get()
.addDependencyGroup('test', dependency).validateAndGet()
projectGenerator.metadata = metadata
ProjectRequest request = createProjectRequest('thymeleaf')
@@ -78,7 +78,7 @@ class ProjectGeneratorTests {
dependency.facets << 'web'
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('core', 'web', 'security', 'data-jpa')
.addDependencyGroup('test', dependency).get()
.addDependencyGroup('test', dependency).validateAndGet()
projectGenerator.metadata = metadata
ProjectRequest request = createProjectRequest('thymeleaf')

View File

@@ -35,7 +35,7 @@ class ProjectRequestTests {
void resolve() {
ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'web', 'security', 'spring-data').get()
.addDependencyGroup('code', 'web', 'security', 'spring-data').validateAndGet()
request.style << 'web' << 'spring-data'
request.resolve(metadata)
@@ -47,7 +47,7 @@ class ProjectRequestTests {
void resolveFullMetadata() {
ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', createDependency('org.foo', 'acme', '1.2.0')).get()
.addDependencyGroup('code', createDependency('org.foo', 'acme', '1.2.0')).validateAndGet()
request.style << 'org.foo:acme'
request.resolve(metadata)
assertDependency(request.dependencies.get(0), 'org.foo', 'acme', '1.2.0')
@@ -57,7 +57,7 @@ class ProjectRequestTests {
void resolveUnknownSimpleIdAsSpringBootStarter() {
ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'org.foo:bar').get()
.addDependencyGroup('code', 'org.foo:bar').validateAndGet()
request.style << 'org.foo:bar' << 'foo-bar'
request.resolve(metadata)
@@ -69,7 +69,7 @@ class ProjectRequestTests {
void resolveUnknownDependency() {
ProjectRequest request = new ProjectRequest()
InitializrMetadata metadata = InitializrMetadataBuilder.withDefaults()
.addDependencyGroup('code', 'org.foo:bar').get()
.addDependencyGroup('code', 'org.foo:bar').validateAndGet()
request.style << 'org.foo:acme' // does not exist and

View File

@@ -33,8 +33,12 @@ class InitializrMetadataBuilder {
new InitializrMetadataBuilder().addDefaults()
}
InitializrMetadata get() {
InitializrMetadata validateAndGet() {
metadata.validate()
instance()
}
InitializrMetadata instance() {
metadata
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2014 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 io.spring.initializr.web
import org.junit.Test
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.test.context.ActiveProfiles
import static org.junit.Assert.assertEquals
/**
* @author Stephane Nicoll
*/
@ActiveProfiles(['test-default', 'test-custom-env'])
class MainControllerEnvIntegrationTests extends AbstractMainControllerIntegrationTests {
@Test
void downloadCliWithCustomRepository() {
HttpEntity entity = restTemplate.getForEntity(createUrl('/spring'), HttpEntity.class)
assertEquals HttpStatus.FOUND, entity.getStatusCode()
assertEquals new URI('https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/1.1.5.RELEASE' +
'/spring-boot-cli-1.1.5.RELEASE-bin.zip'), entity.getHeaders().getLocation()
}
}

View File

@@ -62,6 +62,34 @@ class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTe
.isGradleProject()
}
@Test
void downloadCli() {
assertSpringCliRedirect('/spring', 'zip')
}
@Test
void downloadCliAsZip() {
assertSpringCliRedirect('/spring.zip', 'zip')
}
@Test
void downloadCliAsTarGz() {
assertSpringCliRedirect('/spring.tar.gz', 'tar.gz')
}
@Test
void downloadCliAsTgz() {
assertSpringCliRedirect('/spring.tgz', 'tar.gz')
}
private void assertSpringCliRedirect(String context, String extension) {
ResponseEntity<?> entity = restTemplate.getForEntity(createUrl(context), ResponseEntity.class)
assertEquals HttpStatus.FOUND, entity.getStatusCode()
assertEquals new URI('https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.1.5.RELEASE' +
'/spring-boot-cli-1.1.5.RELEASE-bin.'+extension), entity.getHeaders().getLocation()
}
@Test // Test that the current output is exactly what we expect
void validateCurrentProjectMetadata() {
String json = restTemplate.getForObject(createUrl('/'), String.class)

View File

@@ -0,0 +1,3 @@
initializr:
env:
artifactRepository: https://repo.spring.io/lib-release

View File

@@ -114,6 +114,11 @@
"version": "0.0.1-SNAPSHOT",
"name": "demo",
"description": "Demo project for Spring Boot",
"packageName": "demo"
"packageName": "demo",
"type": "starter.zip",
"packaging": "jar",
"javaVersion": "1.7",
"language": "java",
"bootVersion": "1.1.5.RELEASE"
}
}