From 0d9d71a17e7290c8c9f41075b3a8141912c6948c Mon Sep 17 00:00:00 2001 From: Kiril Keranov <114745615+kiril-keranov@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:43:31 +0200 Subject: [PATCH 01/12] Add tomcatConfig struct with various configurations --- src/java/containers/tomcat.go | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index c820fbd56..0fa5c3262 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -693,3 +693,41 @@ func (t *TomcatContainer) Release() (string, error) { return cmd, nil } + +type tomcatConfig struct { + Tomcat struct { + Version string `yaml:"version"` + VersionLines []string `yaml:"version_lines"` + RepositoryRoot string `yaml:"repository_root"` + ContextPath string `yaml:"context_path"` + ExternalConfigurationEnabled bool `yaml:"external_configuration_enabled"` + } `yaml:"tomcat"` + ExternalConfiguration struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + } `yaml:"external_configuration"` + LifecycleSupport struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + } `yaml:"lifecycle_support"` + LoggingSupport struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + } `yaml:"logging_support"` + AccessLoggingSupport struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + AccessLogging string `yaml:"access_logging"` + } `yaml:"access_logging_support"` + RedisStore struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + Database int `yaml:"database"` + Timeout int `yaml:"timeout"` + ConnectionPoolSize int `yaml:"connection_pool_size"` + } `yaml:"redis_store"` + GeodeStore struct { + Version string `yaml:"version"` + RepositoryRoot string `yaml:"repository_root"` + } `yaml:"geode_store"` +} From babab0ee60eae8afb93e8dc4c4bb6fb303bd1fbd Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Mar 2026 14:43:17 +0200 Subject: [PATCH 02/12] Refactor tomcat container to use yaml parsing --- src/java/containers/tomcat.go | 196 +++++++++-------------------- src/java/containers/tomcat_test.go | 18 +-- 2 files changed, 63 insertions(+), 151 deletions(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 0fa5c3262..402490653 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -6,7 +6,6 @@ import ( "net/http" "os" "path/filepath" - "regexp" "strings" "github.com/cloudfoundry/java-buildpack/src/java/common" @@ -18,6 +17,7 @@ import ( // TomcatContainer handles servlet/WAR applications type TomcatContainer struct { context *common.Context + config *tomcatConfig } // NewTomcatContainer creates a new Tomcat container @@ -58,10 +58,15 @@ func (t *TomcatContainer) Supply() error { var dep libbuildpack.Dependency var err error + t.config, err = t.loadConfig() + if err != nil { + return fmt.Errorf("failed to load tomcat config: %w", err) + } + if javaHome != "" { javaMajorVersion, versionErr := common.DetermineJavaVersion(javaHome) if versionErr == nil { - tomcatVersion := determineTomcatVersion(os.Getenv("JBP_CONFIG_TOMCAT")) + tomcatVersion := determineTomcatVersion(t.config.Tomcat.Version) t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion) // Select Tomcat version pattern based on Java version @@ -459,8 +464,8 @@ func getKeys(m map[string]string) []string { // DetermineTomcatVersion is an exported wrapper around determineTomcatVersion. // It exists primarily to allow unit tests in the containers_test package to // verify Tomcat version parsing behavior without changing production semantics. -func DetermineTomcatVersion(raw string) string { - return determineTomcatVersion(raw) +func DetermineTomcatVersion(version string) string { + return determineTomcatVersion(version) } // determineTomcatVersion determines the version of the tomcat @@ -468,21 +473,10 @@ func DetermineTomcatVersion(raw string) string { // It looks for a tomcat block with a version of the form ".+" (e.g. "9.+", "10.+", "10.1.+"). // Returns the pattern with "+" replaced by "*" (e.g. "9.*", "10.*", "10.1.*") so libbuildpack can resolve it. // Masterminds/semver treats x, X, and * as equivalent wildcards. -func determineTomcatVersion(raw string) string { - raw = strings.TrimSpace(raw) - if raw == "" { - return "" - } - - re := regexp.MustCompile(`(?i)tomcat\s*:\s*\{[\s\S]*?version\s*:\s*["']?([\d.]+\.\+)`) - match := re.FindStringSubmatch(raw) - if len(match) < 2 { - return "" - } - +func determineTomcatVersion(version string) string { // Replace "+" with "*" so libbuildpack's FindMatchingVersion can resolve it. // e.g. "9.+" -> "9.*", "10.+" -> "10.*", "10.1.+" -> "10.1.*" - return strings.ReplaceAll(match[1], "+", "*") + return strings.ReplaceAll(version, "+", "*") } // isAccessLoggingEnabled checks if access logging is enabled in configuration @@ -491,29 +485,11 @@ func determineTomcatVersion(raw string) string { // Can be enabled via: JBP_CONFIG_TOMCAT='{access_logging_support: {access_logging: enabled}}' func (t *TomcatContainer) isAccessLoggingEnabled() string { // Check for JBP_CONFIG_TOMCAT environment variable - configEnv := os.Getenv("JBP_CONFIG_TOMCAT") - if configEnv != "" { - t.context.Log.Debug("Checking access logging configuration in JBP_CONFIG_TOMCAT") - - // Look for access_logging_support section with access_logging: enabled - // Format: {access_logging_support: {access_logging: enabled}} - if strings.Contains(configEnv, "access_logging_support") { - // Check if access_logging is set to enabled - if strings.Contains(configEnv, "access_logging") && - (strings.Contains(configEnv, "enabled") || strings.Contains(configEnv, "true")) { - t.context.Log.Info("Access logging enabled via JBP_CONFIG_TOMCAT") - return "true" - } - // Check if explicitly disabled - if strings.Contains(configEnv, "access_logging") && - (strings.Contains(configEnv, "disabled") || strings.Contains(configEnv, "false")) { - t.context.Log.Debug("Access logging explicitly disabled via JBP_CONFIG_TOMCAT") - return "false" - } - } + if t.config.AccessLoggingSupport.AccessLogging == "enabled" || t.config.AccessLoggingSupport.AccessLogging == "true" { + t.context.Log.Info("Access logging enabled via JBP_CONFIG_TOMCAT") + return "true" } - // Default to disabled (matches Ruby buildpack default) t.context.Log.Info("Access logging disabled by default (use JBP_CONFIG_TOMCAT to enable)") return "false" } @@ -524,75 +500,16 @@ func (t *TomcatContainer) isExternalConfigurationEnabled() (bool, string, string // Read buildpack configuration from environment or config file // The libbuildpack Stager provides access to buildpack config - // Check for JBP_CONFIG_TOMCAT environment variable - configEnv := os.Getenv("JBP_CONFIG_TOMCAT") - if configEnv != "" { - // Parse the configuration to check external_configuration_enabled - // For now, we'll do a simple string check - // A full implementation would parse the YAML/JSON - t.context.Log.Debug("JBP_CONFIG_TOMCAT: %s", configEnv) - - // Simple check for external_configuration_enabled: true - if strings.Contains(configEnv, "external_configuration_enabled") && - (strings.Contains(configEnv, "true") || strings.Contains(configEnv, "True")) { - - // Extract repository_root and version if present - repositoryRoot := extractRepositoryRoot(configEnv) - version := extractVersion(configEnv) - return true, repositoryRoot, version - } + if t.config.Tomcat.ExternalConfigurationEnabled { + repositoryRoot := t.config.ExternalConfiguration.RepositoryRoot + version := t.config.ExternalConfiguration.Version + return true, repositoryRoot, version } // Default to false (disabled) return false, "", "" } -// extractRepositoryRoot extracts the repository_root value from config string -func extractRepositoryRoot(config string) string { - // Simple extraction - look for repository_root: "value" - // This is a basic implementation; a full parser would use YAML/JSON libraries - - // Look for repository_root: "..." - if idx := strings.Index(config, "repository_root"); idx != -1 { - remaining := config[idx:] - // Find the opening quote - if startQuote := strings.Index(remaining, "\""); startQuote != -1 { - remaining = remaining[startQuote+1:] - // Find the closing quote - if endQuote := strings.Index(remaining, "\""); endQuote != -1 { - return remaining[:endQuote] - } - } - } - - return "" -} - -// extractVersion extracts the version value from config string -func extractVersion(config string) string { - // Look for version: "value" in the external_configuration section - // This is a basic implementation; a full parser would use YAML/JSON libraries - - // Find external_configuration section first - if idx := strings.Index(config, "external_configuration"); idx != -1 { - remaining := config[idx:] - // Look for version: "..." - if versionIdx := strings.Index(remaining, "version"); versionIdx != -1 { - remaining = remaining[versionIdx:] - // Find the opening quote - if startQuote := strings.Index(remaining, "\""); startQuote != -1 { - remaining = remaining[startQuote+1:] - // Find the closing quote - if endQuote := strings.Index(remaining, "\""); endQuote != -1 { - return remaining[:endQuote] - } - } - } - } - - return "" -} - func injectDocBase(xmlContent string, docBase string) string { idx := strings.Index(xmlContent, " Date: Wed, 25 Mar 2026 17:57:29 +0200 Subject: [PATCH 03/12] Change default version --- src/java/containers/tomcat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 402490653..b72e73263 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -618,7 +618,7 @@ func (t *TomcatContainer) loadConfig() (*tomcatConfig, error) { ExternalConfigurationEnabled: false, }, ExternalConfiguration: ExternalConfiguration{ - Version: "1.+", + Version: "1.*", RepositoryRoot: "", }, AccessLoggingSupport: AccessLoggingSupport{ From 6bbdbf0d041f6cd8dd38f8cae65114b79943121c Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Mar 2026 18:00:15 +0200 Subject: [PATCH 04/12] Change default version --- src/java/containers/tomcat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index b72e73263..ba6b20e4d 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -618,7 +618,7 @@ func (t *TomcatContainer) loadConfig() (*tomcatConfig, error) { ExternalConfigurationEnabled: false, }, ExternalConfiguration: ExternalConfiguration{ - Version: "1.*", + Version: "", RepositoryRoot: "", }, AccessLoggingSupport: AccessLoggingSupport{ From 2ede8b2d73ef4de7ceb00feb80893eddcb4af93d Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Mar 2026 18:42:40 +0200 Subject: [PATCH 05/12] Debug log --- src/java/containers/tomcat.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index ba6b20e4d..cafba5aa7 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -485,6 +485,7 @@ func determineTomcatVersion(version string) string { // Can be enabled via: JBP_CONFIG_TOMCAT='{access_logging_support: {access_logging: enabled}}' func (t *TomcatContainer) isAccessLoggingEnabled() string { // Check for JBP_CONFIG_TOMCAT environment variable + t.context.Log.Info("Access logging: " + t.config.AccessLoggingSupport.AccessLogging) if t.config.AccessLoggingSupport.AccessLogging == "enabled" || t.config.AccessLoggingSupport.AccessLogging == "true" { t.context.Log.Info("Access logging enabled via JBP_CONFIG_TOMCAT") return "true" From 01c6d60113d4130e191baf4d86a455b4a3f9ce7e Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Mar 2026 18:44:28 +0200 Subject: [PATCH 06/12] Fix yml struct --- src/java/containers/tomcat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index cafba5aa7..2506f50a6 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -640,7 +640,7 @@ func (t *TomcatContainer) loadConfig() (*tomcatConfig, error) { type tomcatConfig struct { Tomcat Tomcat `yaml:"tomcat"` ExternalConfiguration ExternalConfiguration `yaml:"external_configuration"` - AccessLoggingSupport AccessLoggingSupport `yaml:"access_logging"` + AccessLoggingSupport AccessLoggingSupport `yaml:"access_logging_support"` } type Tomcat struct { From 165071278d296f74b5e7229b7f4b084b6eba2a42 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Thu, 26 Mar 2026 09:26:54 +0200 Subject: [PATCH 07/12] Fix tests --- src/integration/tomcat_test.go | 12 ++++++------ src/java/containers/tomcat.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/integration/tomcat_test.go b/src/integration/tomcat_test.go index 05c58d2d3..01dd4ec47 100644 --- a/src/integration/tomcat_test.go +++ b/src/integration/tomcat_test.go @@ -40,7 +40,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_TOMCAT": "{tomcat: { version: \"9.+\" }, access_logging_support: {access_logging: enabled}}", + "JBP_CONFIG_TOMCAT": "{ tomcat: { version: \"9.+\" }, access_logging_support: { access_logging: enabled } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_javax")) @@ -75,7 +75,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_TOMCAT": "{access_logging_support: {access_logging: enabled}}", + "JBP_CONFIG_TOMCAT": "{ access_logging_support: { access_logging: enabled } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_jakarta")) @@ -139,7 +139,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_TOMCAT": "{access_logging_support: {access_logging: enabled}}", + "JBP_CONFIG_TOMCAT": "{ access_logging_support: { access_logging: enabled } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_jakarta")) @@ -179,7 +179,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_TOMCAT": "{tomcat: { version: \"9.+\" }", + "JBP_CONFIG_TOMCAT": "{ tomcat: { version: \"9.+\" }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_javax")) Expect(err).NotTo(HaveOccurred(), logs.String) @@ -192,7 +192,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, it("deploys with default Java (Tomcat 9 + javax.servlet)", func() { deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ - "JBP_CONFIG_TOMCAT": "{ tomcat: { version: 9.+ } }", + "JBP_CONFIG_TOMCAT": "{ tomcat: { version: \"9.+\" } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_javax")) Expect(err).NotTo(HaveOccurred(), logs.String) @@ -251,7 +251,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "JBP_CONFIG_OPEN_JDK_JRE": "{ jre: { version: 17.+ } }", - "JBP_CONFIG_TOMCAT": "{tomcat: { version: 10.1.+ }}", + "JBP_CONFIG_TOMCAT": "{ tomcat: { version: 10.1.+ } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_jakarta")) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 2506f50a6..39234bb5d 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -87,7 +87,7 @@ func (t *TomcatContainer) Supply() error { } if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 { - return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) + return fmt.Errorf("tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) } // Resolve the version pattern to actual version using libbuildpack @@ -615,7 +615,7 @@ func (t *TomcatContainer) Release() (string, error) { func (t *TomcatContainer) loadConfig() (*tomcatConfig, error) { tConfig := tomcatConfig{ Tomcat: Tomcat{ - Version: "10.1.+", + Version: "", ExternalConfigurationEnabled: false, }, ExternalConfiguration: ExternalConfiguration{ From c42ccc2319164c45b0591e7fdc6b3ab535724187 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Thu, 26 Mar 2026 10:11:59 +0200 Subject: [PATCH 08/12] Refactoring --- src/java/containers/tomcat.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 39234bb5d..1e85ec17b 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -66,7 +66,7 @@ func (t *TomcatContainer) Supply() error { if javaHome != "" { javaMajorVersion, versionErr := common.DetermineJavaVersion(javaHome) if versionErr == nil { - tomcatVersion := determineTomcatVersion(t.config.Tomcat.Version) + tomcatVersion := DetermineTomcatVersion(t.config.Tomcat.Version) t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion) // Select Tomcat version pattern based on Java version @@ -115,7 +115,7 @@ func (t *TomcatContainer) Supply() error { // Install Tomcat with strip components to remove the top-level directory // Apache Tomcat tarballs extract to apache-tomcat-X.Y.Z/ subdirectory - tomcatDir := filepath.Join(t.context.Stager.DepDir(), "tomcat") + tomcatDir := t.tomcatDir() if err := t.context.Installer.InstallDependencyWithStrip(dep, tomcatDir, 1); err != nil { return fmt.Errorf("failed to install Tomcat: %w", err) } @@ -191,7 +191,7 @@ func (t *TomcatContainer) installTomcatLifecycleSupport() error { // InstallDependency for JAR files (non-archives) copies the file to the target directory // The JAR will be placed in tomcat/lib/ as tomcat/lib/tomcat-lifecycle-support-X.Y.Z.RELEASE.jar - tomcatDir := filepath.Join(t.context.Stager.DepDir(), "tomcat") + tomcatDir := filepath.Join(t.tomcatDir()) libDir := filepath.Join(tomcatDir, "lib") // Ensure lib directory exists @@ -216,7 +216,7 @@ func (t *TomcatContainer) installTomcatAccessLoggingSupport() error { // InstallDependency for JAR files (non-archives) copies the file to the target directory // The JAR will be placed in tomcat/lib/ as tomcat/lib/tomcat-access-logging-support-X.Y.Z.RELEASE.jar - tomcatDir := filepath.Join(t.context.Stager.DepDir(), "tomcat") + tomcatDir := filepath.Join(t.tomcatDir()) libDir := filepath.Join(tomcatDir, "lib") // Ensure lib directory exists @@ -243,7 +243,7 @@ func (t *TomcatContainer) installTomcatLoggingSupport() (string, error) { // InstallDependency for JAR files (non-archives) copies the file to the target directory // The JAR will be placed in tomcat/bin/ as tomcat/bin/tomcat-logging-support-X.Y.Z.RELEASE.jar - tomcatDir := filepath.Join(t.context.Stager.DepDir(), "tomcat") + tomcatDir := filepath.Join(t.tomcatDir()) binDir := filepath.Join(tomcatDir, "bin") // Ensure bin directory exists @@ -461,19 +461,12 @@ func getKeys(m map[string]string) []string { return keys } -// DetermineTomcatVersion is an exported wrapper around determineTomcatVersion. -// It exists primarily to allow unit tests in the containers_test package to -// verify Tomcat version parsing behavior without changing production semantics. -func DetermineTomcatVersion(version string) string { - return determineTomcatVersion(version) -} - -// determineTomcatVersion determines the version of the tomcat +// DetermineTomcatVersion determines the version of the tomcat // based on the JBP_CONFIG_TOMCAT field from manifest. // It looks for a tomcat block with a version of the form ".+" (e.g. "9.+", "10.+", "10.1.+"). // Returns the pattern with "+" replaced by "*" (e.g. "9.*", "10.*", "10.1.*") so libbuildpack can resolve it. // Masterminds/semver treats x, X, and * as equivalent wildcards. -func determineTomcatVersion(version string) string { +func DetermineTomcatVersion(version string) string { // Replace "+" with "*" so libbuildpack's FindMatchingVersion can resolve it. // e.g. "9.+" -> "9.*", "10.+" -> "10.*", "10.1.+" -> "10.1.*" return strings.ReplaceAll(version, "+", "*") @@ -498,9 +491,6 @@ func (t *TomcatContainer) isAccessLoggingEnabled() string { // isExternalConfigurationEnabled checks if external configuration is enabled in config // Returns: (enabled bool, repositoryRoot string, version string) func (t *TomcatContainer) isExternalConfigurationEnabled() (bool, string, string) { - // Read buildpack configuration from environment or config file - // The libbuildpack Stager provides access to buildpack config - if t.config.Tomcat.ExternalConfigurationEnabled { repositoryRoot := t.config.ExternalConfiguration.RepositoryRoot version := t.config.ExternalConfiguration.Version @@ -561,7 +551,7 @@ func (t *TomcatContainer) Finalize() error { t.context.Log.BeginStep("Finalizing Tomcat") buildDir := t.context.Stager.BuildDir() - contextXMLPath := filepath.Join(t.context.Stager.DepDir(), "tomcat", "conf", "Catalina", "localhost", "ROOT.xml") + contextXMLPath := filepath.Join(t.tomcatDir(), "conf", "Catalina", "localhost", "ROOT.xml") webInf := filepath.Join(buildDir, "WEB-INF") if _, err := os.Stat(webInf); err == nil { @@ -612,6 +602,10 @@ func (t *TomcatContainer) Release() (string, error) { return cmd, nil } +func (t *TomcatContainer) tomcatDir() string { + return filepath.Join(t.context.Stager.DepDir(), "tomcat") +} + func (t *TomcatContainer) loadConfig() (*tomcatConfig, error) { tConfig := tomcatConfig{ Tomcat: Tomcat{ From d405721c6a414d3589b2ccde1bf85ec625559b32 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Thu, 26 Mar 2026 15:09:11 +0200 Subject: [PATCH 09/12] Fix tests --- src/integration/tomcat_test.go | 2 +- src/java/containers/tomcat.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/integration/tomcat_test.go b/src/integration/tomcat_test.go index 01dd4ec47..eb8bddecf 100644 --- a/src/integration/tomcat_test.go +++ b/src/integration/tomcat_test.go @@ -179,7 +179,7 @@ func testTomcat(platform switchblade.Platform, fixtures string) func(*testing.T, deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_TOMCAT": "{ tomcat: { version: \"9.+\" }", + "JBP_CONFIG_TOMCAT": "{ tomcat: { version: \"9.+\" } }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat_javax")) Expect(err).NotTo(HaveOccurred(), logs.String) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 1e85ec17b..b50aa342f 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -87,7 +87,7 @@ func (t *TomcatContainer) Supply() error { } if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 { - return fmt.Errorf("tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) + return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) } // Resolve the version pattern to actual version using libbuildpack From 688e3d423c2bfd158dcdf5e06c0377cbb1d3f21d Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Thu, 26 Mar 2026 15:35:36 +0200 Subject: [PATCH 10/12] Use yamlHandler --- src/java/containers/tomcat.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index b50aa342f..0c897cc28 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -11,7 +11,6 @@ import ( "github.com/cloudfoundry/java-buildpack/src/java/common" "github.com/cloudfoundry/java-buildpack/src/java/resources" "github.com/cloudfoundry/libbuildpack" - yaml "gopkg.in/yaml.v2" ) // TomcatContainer handles servlet/WAR applications @@ -361,7 +360,8 @@ func (t *TomcatContainer) downloadExternalConfiguration(repositoryRoot, version, // Parse YAML as map[string]string (version -> URL) var index map[string]string - if err := yaml.Unmarshal(indexData, &index); err != nil { + yamlHandler := common.YamlHandler{} + if err := yamlHandler.Unmarshal(indexData, &index); err != nil { return fmt.Errorf("failed to parse index.yml: %w", err) } From 9c2104af7bfb47063d21de6b1965f1bce3d82259 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Fri, 27 Mar 2026 11:04:21 +0200 Subject: [PATCH 11/12] Use yamlHandler in frameworks --- go.mod | 2 +- src/java/frameworks/java_cf_env.go | 8 ++++---- src/java/frameworks/java_opts.go | 11 +++++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 1bfbb80e1..37077bf0b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/onsi/gomega v1.38.2 github.com/sclevine/spec v1.4.0 go.yaml.in/yaml/v3 v3.0.4 - gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -50,6 +49,7 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/tools v0.39.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) // Replace directives to fix OpenTelemetry dependency conflicts from docker/docker test dependencies diff --git a/src/java/frameworks/java_cf_env.go b/src/java/frameworks/java_cf_env.go index f1a0294ef..9ced99932 100644 --- a/src/java/frameworks/java_cf_env.go +++ b/src/java/frameworks/java_cf_env.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/cloudfoundry/libbuildpack" - "gopkg.in/yaml.v2" ) // JavaCfEnvFramework implements java-cfenv support for Cloud Foundry @@ -97,11 +96,12 @@ func (j *JavaCfEnvFramework) Finalize() error { // isEnabled checks if java-cfenv is enabled in configuration func (j *JavaCfEnvFramework) isEnabled() bool { // Check JBP_CONFIG_JAVA_CF_ENV environment variable + yamlHandler := common.YamlHandler{} configOverride := os.Getenv("JBP_CONFIG_JAVA_CF_ENV") if configOverride != "" { // Parse YAML configuration var yamlContent interface{} - if err := yaml.Unmarshal([]byte(configOverride), &yamlContent); err != nil { + if err := yamlHandler.Unmarshal([]byte(configOverride), &yamlContent); err != nil { j.context.Log.Warning("Failed to parse JBP_CONFIG_JAVA_CF_ENV, treating as enabled: %s", err) return true } @@ -113,7 +113,7 @@ func (j *JavaCfEnvFramework) isEnabled() bool { configData = []byte(v) case map[interface{}]interface{}: var err error - configData, err = yaml.Marshal(v) + configData, err = yamlHandler.Marshal(v) if err != nil { j.context.Log.Warning("Failed to marshal config, treating as enabled: %s", err) return true @@ -127,7 +127,7 @@ func (j *JavaCfEnvFramework) isEnabled() bool { var config struct { Enabled bool `yaml:"enabled"` } - if err := yaml.Unmarshal(configData, &config); err != nil { + if err := yamlHandler.Unmarshal(configData, &config); err != nil { j.context.Log.Warning("Failed to parse config structure, treating as enabled: %s", err) return true } diff --git a/src/java/frameworks/java_opts.go b/src/java/frameworks/java_opts.go index 805aa5203..a0bb45fb1 100644 --- a/src/java/frameworks/java_opts.go +++ b/src/java/frameworks/java_opts.go @@ -6,8 +6,6 @@ import ( "os" "strings" "unicode" - - "gopkg.in/yaml.v2" ) // JavaOptsFramework implements custom JAVA_OPTS configuration @@ -249,13 +247,14 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { FromEnvironment: true, // Default to true (matches config file) JavaOpts: []string{}, } + yamlHandler := common.YamlHandler{} // Check for JBP_CONFIG_JAVA_OPTS override configOverride := os.Getenv("JBP_CONFIG_JAVA_OPTS") if configOverride != "" { // First, parse the outer YAML string (handles single-quoted format like '{...}') var yamlContent interface{} - if err := yaml.Unmarshal([]byte(configOverride), &yamlContent); err != nil { + if err := yamlHandler.Unmarshal([]byte(configOverride), &yamlContent); err != nil { return nil, fmt.Errorf("failed to parse JBP_CONFIG_JAVA_OPTS: %w", err) } @@ -268,7 +267,7 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { case map[interface{}]interface{}: // It's already a parsed YAML structure - marshal it back to bytes var err error - configData, err = yaml.Marshal(v) + configData, err = yamlHandler.Marshal(v) if err != nil { return nil, fmt.Errorf("failed to marshal config map: %w", err) } @@ -284,7 +283,7 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { } } var err error - configData, err = yaml.Marshal(mergedMap) + configData, err = yamlHandler.Marshal(mergedMap) if err != nil { return nil, fmt.Errorf("failed to marshal merged config map: %w", err) } @@ -294,7 +293,7 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { // Parse into a generic map first to handle both string and array formats for java_opts var rawConfig map[string]interface{} - if err := yaml.Unmarshal(configData, &rawConfig); err != nil { + if err := yamlHandler.Unmarshal(configData, &rawConfig); err != nil { return nil, fmt.Errorf("failed to parse JBP_CONFIG_JAVA_OPTS structure: %w", err) } From e45505c94041d993160085b927e54651515b6bd8 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Fri, 27 Mar 2026 15:26:41 +0200 Subject: [PATCH 12/12] Adapt to yaml.v3, add tests, remove debug log --- src/java/containers/tomcat.go | 1 - src/java/frameworks/java_cf_env.go | 2 +- src/java/frameworks/java_opts.go | 6 ++-- src/java/frameworks/java_opts_test.go | 47 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index 0c897cc28..678249acd 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -478,7 +478,6 @@ func DetermineTomcatVersion(version string) string { // Can be enabled via: JBP_CONFIG_TOMCAT='{access_logging_support: {access_logging: enabled}}' func (t *TomcatContainer) isAccessLoggingEnabled() string { // Check for JBP_CONFIG_TOMCAT environment variable - t.context.Log.Info("Access logging: " + t.config.AccessLoggingSupport.AccessLogging) if t.config.AccessLoggingSupport.AccessLogging == "enabled" || t.config.AccessLoggingSupport.AccessLogging == "true" { t.context.Log.Info("Access logging enabled via JBP_CONFIG_TOMCAT") return "true" diff --git a/src/java/frameworks/java_cf_env.go b/src/java/frameworks/java_cf_env.go index 9ced99932..63f4576ad 100644 --- a/src/java/frameworks/java_cf_env.go +++ b/src/java/frameworks/java_cf_env.go @@ -111,7 +111,7 @@ func (j *JavaCfEnvFramework) isEnabled() bool { switch v := yamlContent.(type) { case string: configData = []byte(v) - case map[interface{}]interface{}: + case map[string]interface{}: var err error configData, err = yamlHandler.Marshal(v) if err != nil { diff --git a/src/java/frameworks/java_opts.go b/src/java/frameworks/java_opts.go index a0bb45fb1..b92aed6c5 100644 --- a/src/java/frameworks/java_opts.go +++ b/src/java/frameworks/java_opts.go @@ -264,7 +264,7 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { case string: // It's a YAML string literal - parse the content configData = []byte(v) - case map[interface{}]interface{}: + case map[string]interface{}: // It's already a parsed YAML structure - marshal it back to bytes var err error configData, err = yamlHandler.Marshal(v) @@ -274,9 +274,9 @@ func (j *JavaOptsFramework) loadConfig() (*JavaOptsConfig, error) { case []interface{}: // Handle legacy format: [from_environment: false, java_opts: ...] // This parses as an array of maps, so we need to merge them - mergedMap := make(map[interface{}]interface{}) + mergedMap := make(map[string]interface{}) for _, item := range v { - if m, ok := item.(map[interface{}]interface{}); ok { + if m, ok := item.(map[string]interface{}); ok { for k, val := range m { mergedMap[k] = val } diff --git a/src/java/frameworks/java_opts_test.go b/src/java/frameworks/java_opts_test.go index 31cc86e39..e8b298fc9 100644 --- a/src/java/frameworks/java_opts_test.go +++ b/src/java/frameworks/java_opts_test.go @@ -1,6 +1,7 @@ package frameworks import ( + "os" "strings" . "github.com/onsi/ginkgo/v2" @@ -87,4 +88,50 @@ var _ = Describe("JavaOpts", func() { "-DtestJBPConfig1=test test -DtestJBPConfig2=$PATH"), ) }) + + Describe("loadConfig", func() { + framework := &JavaOptsFramework{context: nil} + + AfterEach(func() { + os.Unsetenv("JBP_CONFIG_JAVA_OPTS") + }) + + It("returns default config when env var is not set", func() { + config, err := framework.loadConfig() + Expect(err).NotTo(HaveOccurred()) + Expect(config.FromEnvironment).To(BeTrue()) + Expect(config.JavaOpts).To(BeEmpty()) + }) + + It("parses a plain YAML map (map[string]interface{} from yaml.v3)", func() { + os.Setenv("JBP_CONFIG_JAVA_OPTS", "{from_environment: false, java_opts: [\"-Xmx512m\", \"-Xms256m\"]}") + config, err := framework.loadConfig() + Expect(err).NotTo(HaveOccurred()) + Expect(config.FromEnvironment).To(BeFalse()) + Expect(config.JavaOpts).To(Equal([]string{"-Xmx512m", "-Xms256m"})) + }) + + It("parses from_environment: true with java_opts array", func() { + os.Setenv("JBP_CONFIG_JAVA_OPTS", "{from_environment: true, java_opts: [\"-Xmx1g\"]}") + config, err := framework.loadConfig() + Expect(err).NotTo(HaveOccurred()) + Expect(config.FromEnvironment).To(BeTrue()) + Expect(config.JavaOpts).To(Equal([]string{"-Xmx1g"})) + }) + + It("parses legacy space-separated string java_opts", func() { + os.Setenv("JBP_CONFIG_JAVA_OPTS", "{from_environment: false, java_opts: \"-Xmx512m -Xms256m\"}") + config, err := framework.loadConfig() + Expect(err).NotTo(HaveOccurred()) + Expect(config.JavaOpts).To(Equal([]string{"-Xmx512m", "-Xms256m"})) + }) + + It("parses legacy array-of-maps format ([]interface{} from yaml.v3)", func() { + os.Setenv("JBP_CONFIG_JAVA_OPTS", "[{from_environment: false}, {java_opts: [\"-Xmx256m\"]}]") + config, err := framework.loadConfig() + Expect(err).NotTo(HaveOccurred()) + Expect(config.FromEnvironment).To(BeFalse()) + Expect(config.JavaOpts).To(Equal([]string{"-Xmx256m"})) + }) + }) })