Skip to content

Commit b88e155

Browse files
author
Glyn Normington
committed
Add Spring Boot CLI container
This incorporates detection logic which carefully avoids overlapping that of the Groovy and Tomcat containers. (The detection criteria are all documented.) Compile downloads the Spring Boot CLI zip from a repository, unzips it, and symlinks any classpath JARs into the lib subdirectory of the unzipped file. Release builds a command line which drives the spring executable from the downloaded zip setting JAVA_HOME and JAVA_OPTS appropriately and overriding the server port with $PORT. For non-web applications, the use of $PORT should be harmless. The target files are all the Groovy files in the application root directory. Add Spring Boot CLI container to buildpack configuration Rename variables in spec tests to avoid clashes. [#54204032]
1 parent 420e35e commit b88e155

16 files changed

Lines changed: 427 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The buildpack supports configuration and extension through the use of Git reposi
2525
* [Groovy](docs/container-groovy.md) ([Configuration](docs/container-groovy.md#configuration))
2626
* [Java Main Class](docs/container-java-main.md) ([Configuration](docs/container-java-main.md#configuration))
2727
* [Play](docs/container-play.md)
28+
* [Spring Boot CLI](docs/container-spring-boot-cli.md) ([Configuration](docs/container-spring-boot-cli.md#configuration))
2829
* [Tomcat](docs/container-tomcat.md) ([Configuration](docs/container-tomcat.md#configuration))
2930
* Standard Frameworks
3031
* [Play Auto Reconfiguration](docs/framework-play-auto-reconfiguration.md) ([Configuration](docs/framework-play-auto-reconfiguration.md#configuration))

config/components.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
containers:
1919
- "JavaBuildpack::Container::Groovy"
2020
- "JavaBuildpack::Container::Main"
21+
- "JavaBuildpack::Container::SpringBootCli"
2122
- "JavaBuildpack::Container::Tomcat"
2223
- "JavaBuildpack::Container::Play"
2324
jres:

config/springbootcli.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Cloud Foundry Java Buildpack
2+
# Copyright (c) 2013 the original author or authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Configuration for the Spring Auto Reconfiguration framework.
17+
# Note that the repository is shared with the Play Auto Reconfiguration framework and should be kept in step to
18+
# avoid conflicts.
19+
---
20+
version: 0.5.0_+
21+
repository_root: "http://download.pivotal.io.s3.amazonaws.com/spring-boot-cli/lucid/x86_64"

docs/container-spring-boot-cli.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Spring Boot CLI Container
2+
The Spring Boot CLI Container runs one or more Groovy (i.e. `*.groovy`) files using Spring Boot CLI.
3+
4+
<table>
5+
<tr>
6+
<td><strong>Detection Criteria</strong></td><td><ul>
7+
<li>The application has one or more <tt>.groovy</tt> files in the root directory, and</li>
8+
<li>All the application's <tt>.groovy</tt> files in the root directory are POGOs (a POGO contains one or more classes), and</li>
9+
<li>None of the application's <tt>.groovy</tt> files in the root directory contain a <tt>main</tt> method, and</li>
10+
<li>The application does not have a <tt>WEB-INF</tt> subdirectory of its root directory.</li>
11+
</ul></td>
12+
</tr>
13+
<tr>
14+
<td><strong>Tags</strong></td><td><tt>spring-boot-cli-&lang;version&rang;</tt></td>
15+
</tr>
16+
</table>
17+
Tags are printed to standard output by the buildpack detect script
18+
19+
20+
## Configuration
21+
The container can be configured by modifying the [`config/springbootcli.yml`][springbootcli_yml] file. The container uses the [`Repository` utility support][util_repositories] and so it supports the [version syntax][version_syntax] defined there.
22+
23+
[springbootcli_yml]: ../config/springbootcli.yml
24+
[util_repositories]: util-repositories.md
25+
[version_syntax]: util-repositories.md#version-syntax-and-ordering
26+
27+
| Name | Description
28+
| ---- | -----------
29+
| `repository_root` | The URL of the Spring Boot CLI repository index ([details][util_repositories]).
30+
| `version` | The version of Spring Boot CLI to use. Candidate versions can be found in [this listing][spring_boot_cli_index_yml].
31+
32+
[spring_boot_cli_index_yml]: http://download.pivotal.io.s3.amazonaws.com/spring-boot-cli/lucid/x86_64/index.yml
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Encoding: utf-8
2+
# Cloud Foundry Java Buildpack
3+
# Copyright (c) 2013 the original author or authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require 'java_buildpack/container'
18+
require 'java_buildpack/container/container_utils'
19+
require 'java_buildpack/repository/configured_item'
20+
require 'java_buildpack/util/application_cache'
21+
require 'java_buildpack/util/format_duration'
22+
require 'fileutils'
23+
require 'pathname'
24+
require 'set'
25+
require 'tmpdir'
26+
27+
module JavaBuildpack::Container
28+
29+
# Encapsulates the detect, compile, and release functionality for applications running Spring Boot CLI
30+
# applications.
31+
class SpringBootCli
32+
33+
# Creates an instance, passing in an arbitrary collection of options.
34+
#
35+
# @param [Hash] context the context that is provided to the instance
36+
# @option context [String] :app_dir the directory that the application exists in
37+
# @option context [String] :java_home the directory that acts as +JAVA_HOME+
38+
# @option context [Array<String>] :java_opts an array that Java options can be added to
39+
# @option context [String] :lib_directory the directory that additional libraries are placed in
40+
# @option context [Hash] :configuration the properties provided by the user
41+
def initialize(context = {})
42+
@app_dir = context[:app_dir]
43+
@java_home = context[:java_home]
44+
@java_opts = context[:java_opts]
45+
@lib_directory = context[:lib_directory]
46+
@configuration = context[:configuration]
47+
@version, @uri = SpringBootCli.find_spring_boot_cli(@app_dir, @configuration)
48+
end
49+
50+
# Detects whether this application is a Spring Boot CLI application.
51+
#
52+
# @return [String] returns +spring-boot-cli-<version>+ if and only if:
53+
# * The application has one or more +.groovy+ files in the root directory, and
54+
# * All the application's +.groovy+ files in the root directory are POGOs (a POGO contains one or more classes), and
55+
# * None of the application's +.groovy+ files in the root directory contain a +main+ method, and
56+
# * The application does not have a +WEB-INF+ subdirectory of its root directory
57+
# otherwise it returns +nil+.
58+
def detect
59+
@version ? id(@version) : nil
60+
end
61+
62+
# Downloads and unpacks a Spring Boot CLI distribution and copies classpath JARs to its +lib+ directory.
63+
#
64+
# @return [void]
65+
def compile
66+
download_start_time = Time.now
67+
print "-----> Downloading Spring Boot CLI #{@version} from #{@uri} "
68+
69+
JavaBuildpack::Util::ApplicationCache.new.get(@uri) do |file| # TODO: Use global cache #50175265
70+
puts "(#{(Time.now - download_start_time).duration})"
71+
expand(file, @configuration)
72+
end
73+
link_classpath_jars
74+
end
75+
76+
# Creates the command to run the Java +main()+ application.
77+
#
78+
# @return [String] the command to run the application.
79+
def release
80+
java_home_string = "JAVA_HOME=#{@java_home}"
81+
java_opts_string = ContainerUtils.space("JAVA_OPTS=\"#{ContainerUtils.to_java_opts_s(@java_opts)}\"")
82+
spring_boot_script = ContainerUtils.space(File.join SPRING_BOOT_CLI_HOME, 'bin', 'spring')
83+
84+
"#{java_home_string}#{java_opts_string}#{spring_boot_script} run *.groovy -- --server.port=$PORT"
85+
end
86+
87+
private
88+
89+
GROOVY_FILE_PATTERN = '*.groovy'.freeze
90+
91+
SPRING_BOOT_CLI_HOME = '.spring-boot-cli'.freeze
92+
93+
def link_classpath_jars
94+
ContainerUtils.libs(@app_dir, @lib_directory).each do |lib|
95+
system "ln -nsf ../../#{lib} #{spring_lib_dir}"
96+
end
97+
end
98+
99+
def spring_lib_dir
100+
File.join(spring_boot_cli_home, 'lib')
101+
end
102+
103+
def expand(file, configuration)
104+
expand_start_time = Time.now
105+
print " Expanding Spring Boot CLI to #{SPRING_BOOT_CLI_HOME} "
106+
107+
Dir.mktmpdir do |tmpdir_root|
108+
system "rm -rf #{spring_boot_cli_home}"
109+
system "mkdir -p #{File.dirname spring_boot_cli_home}"
110+
system "unzip -qq #{file.path} -d #{tmpdir_root} 2>&1"
111+
system "mv #{tmpdir_root}/$(ls #{tmpdir_root}) #{spring_boot_cli_home}"
112+
end
113+
114+
puts "(#{(Time.now - expand_start_time).duration})"
115+
end
116+
117+
def self.find_spring_boot_cli(app_dir, configuration)
118+
if spring_boot_cli app_dir
119+
version, uri = JavaBuildpack::Repository::ConfiguredItem.find_item(configuration)
120+
else
121+
version = nil
122+
uri = nil
123+
end
124+
125+
return version, uri # rubocop:disable RedundantReturn
126+
rescue => e
127+
raise RuntimeError, "Spring Boot CLI container error: #{e.message}", e.backtrace
128+
end
129+
130+
def self.groovy_files(root)
131+
root_directory = Pathname.new(root)
132+
Dir[File.join root, GROOVY_FILE_PATTERN].reject { |file| File.directory? file }.map { |file| Pathname.new(file).relative_path_from(root_directory).to_s }
133+
end
134+
135+
def spring_boot_cli_home
136+
File.join @app_dir, SPRING_BOOT_CLI_HOME
137+
end
138+
139+
def spring_boot_cli_invocation
140+
" -jar #{}"
141+
end
142+
143+
def id(version)
144+
"spring-boot-cli-#{version}"
145+
end
146+
147+
# Determine whether or not the Spring Boot CLI container recognises the application.
148+
def self.spring_boot_cli(app_dir)
149+
gf = groovy_files(app_dir)
150+
gf.length > 0 && all_pogo(app_dir, gf) && no_main_method(app_dir, gf) && !has_web_inf(app_dir)
151+
end
152+
153+
def self.no_main_method(app_dir, groovy_files)
154+
none?(app_dir, groovy_files) { |file| file.read =~ /static void main\(/ } # note that this will scan comments
155+
end
156+
157+
def self.has_web_inf(app_dir)
158+
File.exist?(File.join(app_dir, 'WEB-INF'))
159+
end
160+
161+
def self.all_pogo(app_dir, groovy_files)
162+
all?(app_dir, groovy_files) { |file| file.read =~ /class [\w]+ {/ } # note that this will scan comments
163+
end
164+
165+
def self.all?(app_dir, groovy_files, &block)
166+
groovy_files.all? { |file| open(app_dir, file, &block) }
167+
end
168+
169+
def self.none?(app_dir, groovy_files, &block)
170+
groovy_files.none? { |file| open(app_dir, file, &block) }
171+
end
172+
173+
def self.open(app_dir, file, &block)
174+
File.open(File.join(app_dir, file), 'r', external_encoding: 'UTF-8', &block)
175+
end
176+
177+
end
178+
179+
end

spec/fixtures/container_spring_boot_cli_groovy_with_web_inf/WEB-INF/.gitignore

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class X {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
static void main(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class X {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x

0 commit comments

Comments
 (0)