-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathspring_boot_cli.go
More file actions
212 lines (173 loc) · 6.42 KB
/
spring_boot_cli.go
File metadata and controls
212 lines (173 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package containers
import (
"fmt"
"github.com/cloudfoundry/java-buildpack/src/java/common"
"os"
"path/filepath"
"strings"
)
// SpringBootCLIContainer handles Spring Boot CLI applications
type SpringBootCLIContainer struct {
context *common.Context
groovyFiles []string
groovyUtils *GroovyUtils
}
// NewSpringBootCLIContainer creates a new Spring Boot CLI container
func NewSpringBootCLIContainer(ctx *common.Context) *SpringBootCLIContainer {
return &SpringBootCLIContainer{
context: ctx,
groovyUtils: &GroovyUtils{},
}
}
// Detect checks if this is a Spring Boot CLI application
func (s *SpringBootCLIContainer) Detect() (string, error) {
buildDir := s.context.Stager.BuildDir()
// Find all Groovy files (excluding logback config files)
allGroovyFiles, err := s.groovyUtils.FindGroovyFiles(buildDir)
if err != nil {
return "", err
}
var groovyFiles []string
for _, file := range allGroovyFiles {
if !s.groovyUtils.IsLogbackConfigFile(file) && isValidGroovyFile(file) {
groovyFiles = append(groovyFiles, file)
}
}
// Must have at least one Groovy file
if len(groovyFiles) == 0 {
return "", nil
}
// All Groovy files must be POGO or beans configuration
if !s.allPOGOOrConfiguration(groovyFiles) {
return "", nil
}
// No Groovy file should have a main() method
if !s.noMainMethod(groovyFiles) {
return "", nil
}
// No Groovy file should have a shebang
if !s.noShebang(groovyFiles) {
return "", nil
}
// All checks passed - this is a Spring Boot CLI application
s.groovyFiles = groovyFiles
s.context.Log.Debug("Detected Spring Boot CLI application with %d Groovy file(s)", len(groovyFiles))
return "Spring Boot CLI", nil
}
// Supply installs Spring Boot CLI
func (s *SpringBootCLIContainer) Supply() error {
s.context.Log.BeginStep("Supplying Spring Boot CLI")
// Install Spring Boot CLI runtime
dep, err := s.context.Manifest.DefaultVersion("spring-boot-cli")
if err != nil {
s.context.Log.Warning("Unable to determine default Spring Boot CLI version: %s", err.Error())
// Fallback version
dep.Name = "spring-boot-cli"
dep.Version = "2.7.0"
}
springBootCLIDir := filepath.Join(s.context.Stager.DepDir(), "spring-boot-cli")
// Strip top-level directory from archive (e.g., spring-2.7.0/)
if err := s.context.Installer.InstallDependencyWithStrip(dep, springBootCLIDir, 1); err != nil {
return fmt.Errorf("failed to install Spring Boot CLI: %w", err)
}
s.context.Log.Info("Installed Spring Boot CLI version %s", dep.Version)
// Write profile.d script to set SPRING_BOOT_CLI_HOME at runtime
// At runtime, CF sets $DEPS_DIR (e.g., /home/vcap/deps) and makes dependencies available at $DEPS_DIR/<idx>/
depsIdx := s.context.Stager.DepsIdx()
envContent := fmt.Sprintf(`export SPRING_BOOT_CLI_HOME=$DEPS_DIR/%s/spring-boot-cli
`, depsIdx)
if err := s.context.Stager.WriteProfileD("spring-boot-cli.sh", envContent); err != nil {
s.context.Log.Warning("Could not write spring-boot-cli.sh profile.d script: %s", err.Error())
} else {
s.context.Log.Debug("Created profile.d script: spring-boot-cli.sh")
}
return nil
}
// Finalize performs final Spring Boot CLI configuration
func (s *SpringBootCLIContainer) Finalize() error {
s.context.Log.BeginStep("Finalizing Spring Boot CLI")
// $JAVA_OPTS and $PORT are runtime variables — WriteProfileD ensures they are
// expanded at container startup rather than stored as literal strings.
if err := s.context.Stager.WriteProfileD("spring_boot_cli_java_opts.sh", "export JAVA_OPTS=$JAVA_OPTS\n"); err != nil {
return fmt.Errorf("failed to write JAVA_OPTS profile.d script: %w", err)
}
if err := s.context.Stager.WriteProfileD("spring_boot_cli_server_port.sh", "export SERVER_PORT=$PORT\n"); err != nil {
return fmt.Errorf("failed to write SERVER_PORT profile.d script: %w", err)
}
return nil
}
// Release returns the Spring Boot CLI startup command
func (s *SpringBootCLIContainer) Release() (string, error) {
buildDir := s.context.Stager.BuildDir()
// Use environment variable set by profile.d script (created during Supply)
springBootCLIDir := "$SPRING_BOOT_CLI_HOME"
// Build classpath from additional libraries and root libraries
var classpathParts []string
// Add additional libraries (if any)
additionalLibs := filepath.Join(buildDir, ".additional_libs")
if info, err := os.Stat(additionalLibs); err == nil && info.IsDir() {
classpathParts = append(classpathParts, "$HOME/.additional_libs/*")
}
// Add root libraries (lib/ directory)
rootLibs := filepath.Join(buildDir, "lib")
if info, err := os.Stat(rootLibs); err == nil && info.IsDir() {
classpathParts = append(classpathParts, "$HOME/lib/*")
}
classpath := "${CLASSPATH}${CONTAINER_SECURITY_PROVIDER:+:$CONTAINER_SECURITY_PROVIDER}"
if len(classpathParts) > 0 {
classpath = strings.Join(classpathParts, ":")
}
// Get relative paths for Groovy files
var relativeGroovyFiles []string
for _, file := range s.groovyFiles {
relPath, err := filepath.Rel(buildDir, file)
if err != nil {
relPath = filepath.Base(file)
}
relativeGroovyFiles = append(relativeGroovyFiles, relPath)
}
// Build the spring run command
springBin := fmt.Sprintf("%s/bin/spring", springBootCLIDir)
var cmdParts []string
cmdParts = append(cmdParts, springBin, "run")
// Add classpath if present
if classpath != "" {
cmdParts = append(cmdParts, "-cp", classpath)
}
// Add Groovy files
cmdParts = append(cmdParts, relativeGroovyFiles...)
cmd := strings.Join(cmdParts, " ")
s.context.Log.Debug("Spring Boot CLI command: %s", cmd)
return cmd, nil
}
// Helper methods
// allPOGOOrConfiguration checks if all Groovy files are POGO or beans configuration
func (s *SpringBootCLIContainer) allPOGOOrConfiguration(files []string) bool {
for _, file := range files {
if !s.groovyUtils.IsPOGO(file) && !s.groovyUtils.IsBeans(file) {
s.context.Log.Debug("File %s is neither POGO nor beans configuration", file)
return false
}
}
return true
}
// noMainMethod checks that no Groovy file has a main() method
func (s *SpringBootCLIContainer) noMainMethod(files []string) bool {
for _, file := range files {
if s.groovyUtils.HasMainMethod(file) {
s.context.Log.Debug("File %s has a main() method", file)
return false
}
}
return true
}
// noShebang checks that no Groovy file has a shebang
func (s *SpringBootCLIContainer) noShebang(files []string) bool {
for _, file := range files {
if s.groovyUtils.HasShebang(file) {
s.context.Log.Debug("File %s has a shebang", file)
return false
}
}
return true
}