feat: rewrite java-buildpack in Go using libbuildpack#1217
Merged
Conversation
During the finalize phase, frameworks like ContainerSecurityProvider and
LunaSecurityProvider need to detect the Java version by reading JAVA_HOME
from the process environment. However, only OpenJDK was setting JAVA_HOME
via os.Setenv() in its Finalize() method.
This caused warnings when using other JREs:
**WARNING** Unable to detect Java version, assuming Java 8: JAVA_HOME not set
Note: profile.d scripts (which export JAVA_HOME) are only sourced at runtime,
not during the staging/finalize phase, so frameworks cannot rely on them.
Changes:
- Added os.Setenv("JAVA_HOME", javaHome) to Finalize() methods of:
* SapMachine JRE
* Zulu JRE
* GraalVM JRE
* IBM JRE
* Oracle JRE
* Zing JRE
All JREs now follow the same pattern established by OpenJDK, setting
JAVA_HOME in the process environment during finalize so that frameworks
can detect Java version correctly.
Fixes: Container Security Provider warning when using non-OpenJDK JREs
This commit activates 5 fully-implemented but unregistered frameworks that were previously unreachable dead code, and eliminates significant code duplication by centralizing component registration logic. Activated Frameworks: - ContainerCustomizer: Tomcat configuration customization for Spring Boot WAR apps - JavaMemoryAssistant: Memory leak detection and heap dump management - MetricWriter: Micrometer metrics export for Spring Boot applications - ProtectAppSecurityProvider: Key management and security certificates - SeekerSecurityProvider: Synopsys Seeker IAST agent Code Duplication Eliminated: - Created RegisterStandardFrameworks() method in framework.go to centralize 43 framework registrations previously duplicated in supply.go and finalize.go - Created RegisterStandardJREs() method in jre.go to centralize 7 JRE registrations previously duplicated in supply.go and finalize.go - Removed ~146 lines of duplicate registration code across both phases JRE Detection Logic Fixed: - Fixed Registry.Detect() to return error when no JRE found (not nil) - Fixed OpenJDK.Detect() to only detect when explicitly configured via env vars - Added test coverage for explicit JRE selection (SapMachine over OpenJDK default) - Updated tests to properly use SetDefault() mechanism Impact: - Net reduction: ~66 lines of code - All 28 JRE unit tests pass - All Java buildpack unit tests pass (containers, frameworks, supply, finalize) - 81/87 integration tests pass (5 pre-existing Groovy failures unrelated to changes) - No new dead code introduced
Add comprehensive integration tests to verify that multiple frameworks can coexist without overwriting each other's JAVA_OPTS at runtime. This ensures the centralized .opts file system works correctly. Key test scenarios: - Verify configured JAVA_OPTS are applied at runtime with from_environment=false - Verify user JAVA_OPTS are preserved with from_environment=true - Verify 4 simultaneous frameworks (Java Opts, Container Security Provider, Debug, JRebel) all contribute their options without conflicts - Verify runtime paths use $DEPS_DIR variables, not staging paths like /tmp/contents Test fixtures: - spring_boot_multi_framework: New fixture with rebel-remote.xml to auto-trigger JRebel agent, plus Debug and Container Security Provider - spring_boot_staged: Enhanced with /jvm-args endpoint to expose actual JVM arguments and system properties at runtime for verification
Add dynamic JAR detection to locate javaagent.jar which may be extracted into versioned subdirectories (e.g., ver4.5.18/javaagent.jar) rather than directly in the agent root directory. Problem: AppDynamics agent downloads extract into versioned directory structures, but the buildpack was hardcoded to look for javaagent.jar at the root of the extraction directory, causing agent initialization to fail. Solution: - Add findAppDynamicsAgent() helper that searches multiple paths: 1. Direct path: app_dynamics_agent/javaagent.jar 2. Glob pattern: app_dynamics_agent/ver*/javaagent.jar 3. Recursive walk as fallback for any directory structure - Move agent path resolution from Supply() to Finalize() to ensure the correct path is used at runtime - Convert absolute staging paths to runtime paths using $DEPS_DIR/0/... for proper path resolution when the application starts This ensures AppDynamics works regardless of the archive structure used by different agent versions.
…iders Preserve existing JVM security providers (SUN, SunRsaSign, SunEC, etc.) instead of replacing them entirely with CloudFoundryContainerProvider. This fixes SSL/TLS and cryptographic operations that depend on default providers. Problem: The buildpack was writing a java.security file with ONLY the CloudFoundryContainerProvider, removing all default JVM security providers. This broke SSL/TLS connections and cryptographic operations because essential providers like sun.security.provider.Sun and com.sun.crypto.provider.SunJCE were no longer available. Solution: - Add readExistingSecurityProviders() to read providers from the JRE's java.security file (supports both Java 8 and Java 9+ locations) - Add parseSecurityProviders() to extract security.provider.N entries - Add getDefaultSecurityProviders() as fallback for OpenJDK/HotSpot defaults - Insert CloudFoundryContainerProvider at position 1, then append all existing providers starting at position 2 This ensures CloudFoundryContainerProvider is prioritized while maintaining full JVM security functionality. Runtime path changes: - Convert staging paths to runtime paths using $DEPS_DIR/0/... for proper path resolution when the application starts
Change JAR detection to specifically search for sl-test-listener*.jar instead of any sl-*.jar, ensuring the correct agent is loaded at runtime. Problem: Sealights packages contain multiple JARs (sl-build-scanner.jar, sl-test-listener.jar, etc.). The buildpack was using a glob pattern "sl-*.jar" which would match the wrong JAR (typically sl-build-scanner.jar) instead of the runtime agent (sl-test-listener.jar), causing the agent to fail at application startup. Solution: - Change glob pattern from "sl-*.jar" to "sl-test-listener*.jar" to match both exact (sl-test-listener.jar) and versioned (sl-test-listener-4.0.jar) filenames - Add recursive fallback search that specifically looks for files starting with "sl-test-listener" prefix - Improve error messages to indicate we're looking for test-listener specifically Runtime path changes: - Convert staging paths to runtime paths using $DEPS_DIR/0/... for proper path resolution when the application starts
Add dynamic JAR detection to locate newrelic.jar which may be extracted into nested subdirectories (e.g., newrelic/newrelic.jar) rather than directly in the agent root directory. Problem: New Relic agent downloads can extract into nested directory structures, but the buildpack was hardcoded to look for newrelic.jar at the root of the extraction directory, causing agent initialization to fail. Solution: - Add findNewRelicAgent() helper that searches multiple paths: 1. Direct path: new_relic_agent/newrelic.jar 2. Nested path: new_relic_agent/newrelic/newrelic.jar 3. Recursive walk as fallback for any directory structure - Move agent path resolution from Supply() to Finalize() to ensure the correct path is used at runtime - Convert absolute staging paths to runtime paths using $DEPS_DIR/0/... for proper path resolution when the application starts This ensures New Relic works regardless of the archive structure used by different agent versions.
Change JaCoCo agent property format from concatenated equals signs to proper comma-separated key=value pairs as required by the JaCoCo agent. Problem: JaCoCo agent properties were being formatted as: -javaagent:jacocoagent.jar=key1=val1=key2=val2=key3=val3 This format is invalid. JaCoCo expects comma-separated properties: -javaagent:jacocoagent.jar=key1=val1,key2=val2,key3=val3 This caused the agent to fail parsing configuration, breaking code coverage collection in Cloud Foundry deployments. Solution: - Add first/else logic to properly format properties with commas - First property: =key=value (no leading comma) - Subsequent properties: ,key=value (comma separator) - Example result: -javaagent:path.jar=address=localhost,port=6300,output=file Runtime path changes: - Convert staging paths to runtime paths using $DEPS_DIR/0/... for proper path resolution when the application starts Minor cleanup: - Remove redundant BeginStep log call from Finalize() (already logged during detection/supply phases)
Switch from InstallOnlyVersion to InstallDependency and add dynamic JAR
detection to properly locate the agent after extraction.
Problem:
The buildpack was using InstallOnlyVersion() which downloads the dependency
but doesn't extract it. This left a .tar.gz or .zip file instead of the
actual JAR, causing the agent to fail at runtime with "file not found" errors.
Additionally, the code assumed a specific JAR filename pattern
(contrast-security-{version}.jar) which might not match the actual extracted
filename.
Solution:
- Change from InstallOnlyVersion to InstallDependency for proper extraction
- Add findContrastAgent() helper that searches multiple patterns:
1. Exact match if version known: contrast-security-{version}.jar
2. Glob pattern: contrast-security-*.jar
3. Broader pattern: contrast*.jar
4. Recursive walk as fallback for any JAR containing "contrast"
- Update both Supply() and Finalize() to use dynamic detection
- Add debug logging to track which JAR was found
This ensures Contrast Security works regardless of the archive structure
or filename variations across different agent versions.
Add support for nested jrebel/ directory structure in extracted archives, checking multiple path variations to locate libjrebel64.so. Problem: JRebel agent downloads extract with a nested directory structure where the agent library is located at jrebel/lib/libjrebel64.so, but the buildpack was only checking the flat lib/libjrebel64.so path, causing agent initialization to fail with "library not found" errors. Solution: - Check nested path first: jrebel/jrebel/lib/libjrebel64.so (current versions) - Fall back to flat path: jrebel/lib/libjrebel64.so (older versions) - Final fallback: jrebel/libjrebel64.so (legacy layout) - Apply same logic in both Supply() and Finalize() phases to handle separate buildpack instances Runtime path preparation: - Compute relative path from framework directory to agent library - Convert to runtime path using $DEPS_DIR/0/jrebel/... format for use in subsequent configuration steps This ensures JRebel works across different archive structures used by various agent versions.
Replace per-framework env/JAVA_OPTS file writes with a centralized .opts file system that allows multiple frameworks to coexist without overwriting each other's options. This completes the migration started with the infrastructure commit. Architecture: - Build time (Finalize phase): Each framework writes JAVA_OPTS to a numbered .opts file in $DEPS_DIR/0/java_opts/ (e.g., 17_container_security.opts) - Runtime: Single profile.d/00_java_opts.sh script reads ALL .opts files in numerical order and assembles them into one JAVA_OPTS variable Priority ordering (based on Ruby buildpack precedence): 05 - JRE (memory calculator, JVMKill agent) 11 - AppDynamics Agent 12 - AspectJ Weaver Agent 13 - Azure Application Insights Agent 14 - Checkmarx IAST Agent 15 - Contrast Security Agent 17 - Container Security Provider 18 - Datadog Javaagent 19 - Elastic APM Agent 20 - Debug (JDWP) 21 - Google Stackdriver Debugger 22 - Google Stackdriver Profiler 26 - JaCoCo Agent 27 - Introscope Agent 28 - Java Memory Assistant 29 - JMX 30 - JProfiler Profiler 31 - JRebel Agent 32 - Luna Security Provider 35 - New Relic Agent 36 - OpenTelemetry Javaagent 37 - Riverbed AppInternals Agent 38 - ProtectApp Security Provider 39 - Sealights Agent 40 - Seeker Security Provider 41 - SkyWalking Agent 42 - Splunk OTEL Java Agent 45 - YourKit Profiler 46 - Takipi Agent 99 - User JAVA_OPTS (always last) Migration pattern applied to 30 frameworks + JRE: 1. Convert staging paths to runtime paths using $DEPS_DIR/0/... or $HOME/... 2. Build all JAVA_OPTS options (agents, system properties, etc.) 3. Call writeJavaOptsFile(ctx, PRIORITY, "name", javaOpts) 4. Remove old AppendToJavaOpts() or WriteProfileD() calls Key changes by component: Core infrastructure: - finalize.go: Call CreateJavaOptsAssemblyScript() after all frameworks - java_opts_writer.go: Already committed in infrastructure commit JRE (priority 05): - jre.go: Write memory calculator and JVMKill opts to 05_jre.opts - Use $DEPS_DIR for buildpack-installed components App-provided frameworks: - AspectJ: Use $HOME for app-provided JARs (not buildpack-installed) Buildpack-installed frameworks (all others): - Convert staging paths like /tmp/contents/deps/0/... to $DEPS_DIR/0/... - Replace AppendToJavaOpts() with writeJavaOptsFile() - Maintain priority order from Ruby buildpack Special cases: - java_opts.go: Priority 99 ensures user opts always applied last - Container Security Provider: Preserves from_environment flag behavior - JProfiler: Simplified nested directory handling for linux-x64 only Runtime script features: - Expands $DEPS_DIR, $HOME, and $JAVA_OPTS variables using sed - Preserves user-provided JAVA_OPTS when from_environment: true - Processes .opts files in numerical order for predictable precedence Documentation: - docs/framework-ordering.md: Documents all framework priorities and rationale for ordering decisions Tests updated: - framework_test.go: Update expected JAVA_OPTS behavior - container_test.go: Verify runtime path conversion - jre_test.go: Test JRE .opts file generation This migration enables multiple frameworks (e.g., New Relic + AppDynamics + Debug + Container Security) to all contribute their JAVA_OPTS without conflicts. Previously, only the last framework's options would survive.
Change external Tomcat configuration download to fetch index.yml first and
look up the download URL, instead of directly constructing the URL. This
matches Ruby buildpack behavior and allows flexible URL patterns.
Problem: The buildpack was constructing download URLs as:
{repository_root}/tomcat-external-configuration-{version}.tar.gz
This hardcoded pattern caused 404 errors when users' repositories used
different naming conventions. The buildpack should fetch an index.yml
file that maps versions to their actual download URLs.
Solution:
- Modify downloadExternalConfiguration() to:
1. Download {repository_root}/index.yml
2. Parse YAML as map[string]string (version -> URL mapping)
3. Look up requested version in index
4. Download configuration from the URL specified in index.yml
- Add getKeys() helper to list available versions in error messages
- Update documentation with index.yml format and examples
- Update integration test to verify index.yml fetch behavior
Expected index.yml format:
1.0.0: https://example.com/tomcat-config/tomcat-config-1.0.0.tar.gz
1.1.0: https://example.com/tomcat-config/tomcat-config-1.1.0.tar.gz
1.4.0: https://example.com/tomcat-config/tomcat-config-1.4.0.tar.gz
This provides flexibility in archive naming and hosting while maintaining
backward compatibility with forked buildpacks that include external
configuration in their manifest.yml.
- Add shared utility functions to framework.go: * GetApplicationName(includeSpace) - parse VCAP_APPLICATION with optional space prefix * GetJavaMajorVersion() - detect Java version from JAVA_HOME/release * FindFileInDirectory() - smart file finding with common paths + recursive fallback * FindFileInDirectoryWithArchFilter() - architecture-aware file finding for native libraries * FindFileByPattern() - glob pattern-based file finding - Remove duplicate helper methods across frameworks: * getApplicationName from Datadog (20 lines) * getApplicationNameWithSpace from SkyWalking (27 lines) * getJavaMajorVersion from Container Security Provider (78 lines) * getJavaMajorVersion from Luna Security Provider (23 lines) * Simplified findAgent methods in 6 frameworks (AppDynamics, JaCoCo, NewRelic, JProfiler, YourKit, Contrast) - Fix priority collision: Datadog changed from priority 18 to 19 (Contrast Security already uses 18) - Fix architecture detection bug in JProfiler and YourKit: * Now correctly finds x86-64 native libraries, avoiding ARM64 versions (linux-aarch64) * Prevents "cannot load AARCH64-bit .so on a AMD 64-bit platform" errors - Add comprehensive priority documentation to java_opts_writer.go Net change: -103 lines while adding powerful reusable functionality 12 files changed, 277 insertions(+), 380 deletions(-)
This commit fixes Java option parsing by implementing proper shell escaping
that matches the Ruby buildpack's behavior.
Key changes:
- Add shellSplit() to parse quoted strings (like Ruby's Shellwords.shellsplit)
- Add rubyStyleEscape() to escape Java options by splitting on first '='
and escaping only the VALUE part (matches Ruby's escape_value method)
- Add comprehensive tests for shell parsing and escaping edge cases
- Update integration tests to verify correct JAVA_OPTS handling
Implementation details:
- Split on FIRST '=' only to separate key from value
- Escape only the value part using Ruby's character set: A-Za-z0-9_-.,:/@$\
- All other characters (including = in values) are backslash-escaped
- Special handling for newlines and empty values
Examples:
-Dkey=value with spaces → -Dkey=value\ with\ spaces
-XX:OnOutOfMemoryError=kill -9 %p → -XX:OnOutOfMemoryError=kill\ -9\ \%p
-Dtest=(value) → -Dtest=\(value\)
This fixes the original issue where parentheses and special characters
in JAVA_OPTS values were breaking shell execution.
Note: Currently using custom implementation to match Ruby exactly.
Future consideration: investigate if go-shellquote or similar
libraries could provide equivalent functionality with less code,
though exact Ruby compatibility must be maintained.
Consolidate duplicate Context definitions from containers, frameworks, and jres packages into a single shared common.Context type. This eliminates type conversion issues and establishes common package as the location for shared types and utilities. Changes: - Create src/java/common/context.go with shared Context definition - Remove duplicate Context structs from containers/container.go, frameworks/framework.go, and jres/jre.go - Update all packages to use *common.Context instead of package-specific Context types - Update 60 files across containers (9), frameworks (39), jres (10), and build phases (2) Benefits: - Single source of truth for Context structure - No type conversions needed when passing context between packages - Cleaner architecture with explicit common dependencies - Establishes pattern for future shared types All three Context definitions were identical before consolidation, ensuring this is a pure refactoring with no behavior changes.
Consolidate duplicate Java version detection functions from jres and frameworks packages into common package. This follows the same consolidation pattern established for Context. Changes: - Add DetermineJavaVersion(javaHome string) to common package Takes explicit JAVA_HOME path, used by JREs and containers - Add GetJavaMajorVersion() to common package Reads JAVA_HOME from environment, used by frameworks - Remove duplicate DetermineJavaVersion from src/java/jres/jre.go - Remove duplicate GetJavaMajorVersion from src/java/frameworks/framework.go - Update 7 JRE implementations to use common.DetermineJavaVersion(): graalvm, ibm, openjdk, oracle, sapmachine, zulu - Update Tomcat container to use common.DetermineJavaVersion() - Update 2 frameworks to use common.GetJavaMajorVersion(): container_security_provider, luna_security_provider - Update test files to use common.DetermineJavaVersion() Benefits: - Single source of truth for Java version detection logic - Consistent behavior across all packages - Common package as centralized utilities location - Follows idiomatic Go pattern (explicit vs environment-reading functions)
AppendToJavaOpts is dead code that is never called anywhere in the codebase. It was replaced by the centralized JAVA_OPTS assembly system using WriteJavaOpts and .opts files. The current approach (WriteJavaOpts) writes numbered .opts files that are assembled at runtime, which is more robust than the old approach of accumulating JAVA_OPTS in environment variables during build. This removes 44 lines of unused code including documentation.
Move duplicate VCAP_SERVICES types and parsing logic from frameworks and jres packages into common package, establishing it as the single source of truth for Cloud Foundry service binding utilities. Changes: - Add VCAPServices and VCAPService types to src/java/common/context.go - Add GetVCAPServices() and helper methods (HasService, GetService, HasTag, etc.) to common - Add ContainsIgnoreCase() utility for case-insensitive string matching - Remove duplicate VCAPServices/VCAPService types from frameworks/framework.go - Remove incomplete GetVCAPServices() stub from jres/jvmkill.go - Replace duplicate 'Service' type in jres with common.VCAPService - Update frameworks/framework.go to use type aliases for backward compatibility - Update 3 framework files to use common.ContainsIgnoreCase(): - contrast_security_agent.go - jprofiler_profiler.go - your_kit_profiler.go - Update jres/jvmkill.go to call common.GetVCAPServices() Benefits: - Removes ~130 lines of duplicate VCAP parsing code - Fixes incomplete jvmkill.go implementation that was returning empty results - Single source of truth for service binding queries - Consistent case-insensitive pattern matching across all frameworks - Type aliases in frameworks package maintain backward compatibility Net change: +102 lines in common, -177 lines elsewhere (53 line reduction)
Implement Ruby buildpack parity by invoking memory calculator at container runtime to calculate optimal JVM memory settings based on available memory. Key changes: - Add MemoryCalculatorCommand() interface method to all JRE implementations to return shell command snippet for runtime memory calculation - Update memory calculator to use v4.x double-dash flag format: --total-memory, --loaded-class-count, --thread-count, --head-room, --jvm-options (replacing v3.x single-dash format) - Remove --pool-type flag (not used in v4.x) - Prepend memory calculator command to container startup in release.yml - Add base JAVA_OPTS: -Djava.io.tmpdir, -XX:ActiveProcessorCount, -Djava.ext.dirs (Ruby buildpack parity) - Escape startup command with single quotes in YAML to preserve shell special characters ($, quotes, etc.) - Use default class count (6,300) when class counting fails or returns 0 (v4 calculator requires this parameter) Integration test updates: - Reduce memory settings to fit within 1G container limit used by tests - Memory calculator v4 has stricter defaults (240M code cache, 250 threads) compared to v3.x used by Ruby buildpack - Updated 8 integration tests with reduced heap (-Xmx384m or -Xmx256m), smaller code cache (-XX:ReservedCodeCacheSize=120M), and reduced thread stack (-Xss512k) The memory calculator now runs inline in startup command (after profile.d scripts assemble JAVA_OPTS) to read runtime $MEMORY_LIMIT and calculate optimal memory flags, matching Ruby buildpack behavior. All 99 integration tests pass.
…pport libraries This commit adds full support for Tomcat external configuration in the Go buildpack, matching the Ruby buildpack behavior. External configurations can now reference Cloud Foundry-specific Tomcat classes for enhanced logging and lifecycle management. Changes: - Install tomcat-lifecycle-support JAR to tomcat/lib/ for ApplicationStartupFailureDetectingLifecycleListener - Install tomcat-access-logging-support JAR to tomcat/lib/ for CloudFoundryAccessLoggingValve - Install tomcat-logging-support JAR to tomcat/bin/ for CloudFoundryConsoleHandler - Create setenv.sh in tomcat/bin/ to add logging JAR to CLASSPATH before Tomcat starts - Add -Dhttp.port=$PORT to JAVA_OPTS so Tomcat uses the Cloud Foundry assigned port - Add tomcat-access-logging-support to manifest.yml default_versions - Enable external configuration when JBP_CONFIG_TOMCAT includes external_configuration_enabled: true - Download and extract external configuration from repository_root URL - Update integration tests to verify external configuration with real repository - Update documentation with external configuration usage Technical Details: - Tomcat's catalina.sh automatically sources setenv.sh if present, allowing early CLASSPATH setup - Support JARs are installed directly using InstallDependency (non-archive files are copied as-is) - External configuration archives extract to tomcat/ directory with structure: ./conf/... - HTTP port configuration uses system property http.port, expected by external server.xml configs Fixes: Tomcat external configuration support Tested: Integration test passes with real external repository at tomcat-config.cfapps.eu12.hana.ondemand.com
Implement Go's embed directive to include Cloud Foundry-optimized Tomcat
configuration files and framework resources directly in the buildpack binary,
replacing the Ruby buildpack's approach of bundled resource files.
Changes:
1. New resources package with Go embed:
- src/java/resources/embed.go: Core embed package with helper functions
* GetResource(path): Read embedded files
* ExtractToDir(dir): Extract all resources
* ListResources(): List embedded resources
* Exists(path): Check resource existence
- Embeds 8 resource files from Ruby buildpack using //go:embed directive
2. Embedded Tomcat configurations (from Ruby buildpack):
- tomcat/conf/server.xml: CF-optimized with:
* Dynamic port binding via ${http.port} variable
* HTTP/2 support (UpgradeProtocol)
* RemoteIpValve for X-Forwarded-* headers
* CloudFoundryAccessLoggingValve with vcap_request_id tracking
* ApplicationStartupFailureDetectingLifecycleListener
* Security-hardened ErrorReportValve
- tomcat/conf/logging.properties: CloudFoundryConsoleHandler for stdout
- tomcat/conf/context.xml: Minimal context configuration
3. Embedded framework resources (from Ruby buildpack):
- luna_security_provider/Chrystoki.conf
- protect_app_security_provider/IngrianNAE.properties
- new_relic_agent/newrelic.yml
- app_dynamics_agent/defaults/conf/app-agent-config.xml
- azure_application_insights_agent/AI-Agent.xml
4. Tomcat container updates:
- Add installDefaultConfiguration() to install embedded configs
- Install configs before external configuration (external overrides defaults)
- Add -Daccess.logging.enabled=true to JAVA_OPTS for CloudFoundryAccessLoggingValve
- Comprehensive logging of installed features
5. Integration test enhancements:
- Verify embedded configs are installed during staging
- Use Eventually() pattern to avoid flaky access log checks
- Verify HTTP/2 support in runtime logs
- Verify CloudFoundryAccessLoggingValve with [ACCESS] prefix and vcap_request_id
- Verified test stability with 3 consecutive successful runs
Benefits:
- Eliminates need for separate resource distribution
- Configs embedded at compile time, guaranteed availability
- Matches Ruby buildpack functionality exactly
- Simpler deployment (single binary includes all resources)
- Maintains backward compatibility with external configuration
Verification:
- Embedded configs match Ruby buildpack exactly (byte-for-byte comparison)
- Integration tests confirm all CF features work:
* Dynamic port binding via $PORT
* HTTP/2 enabled
* Access logging with vcap_request_id
* X-Forwarded-* header support
* CloudFoundry console logging
- Tested with intentional config breakage to prove embed is being used
- Compared with stock Tomcat to verify 8 CF-specific optimizations
- Standardize agent naming: 'new-relic' -> 'newrelic', 'app-dynamics' -> 'appdynamics' - Add newrelic to default_versions for caching - Remove dependencies from default_versions that lack manifest entries: - appdynamics: requires authentication to AppDynamics repository - dynatrace: downloaded from customer-specific environment - azure-application-insights-agent: duplicate of azure-application-insights - protect-app-security-provider: requires proprietary infrastructure - introscope-agent: requires customer CA APM server - riverbed-appinternals-agent: downloaded from service broker - sky-walking-agent: duplicate of skywalking-agent - splunk-otel-java-agent: duplicate of splunk-otel-javaagent - google-stackdriver-debugger: requires GCP credentials - container-customizer: deprecated download source - metric-writer: on-demand download only - java-memory-assistant: disabled by default - java-memory-assistant-cleanup: bundled with java-memory-assistant These agents will be downloaded on-demand during staging when service bindings are detected, matching the original Ruby buildpack behavior. This enables successful buildpack packaging with --cached flag.
Implement installDefaultConfiguration() methods for five frameworks: 1. AppDynamics Agent - Installs embedded app-agent-config.xml to defaults/conf/ - Provides sensible agent settings and filters 2. New Relic Agent - Installs embedded newrelic.yml with ERB template processing - Replaces placeholders for generated_for_user and license_key - License key configured via JAVA_OPTS at runtime 3. Azure Application Insights Agent - Installs embedded AI-Agent.xml - Provides instrumentation settings 4. Luna Security Provider - Installs Luna HSM configuration from embedded resources 5. ProtectApp Security Provider - Installs ProtectApp configuration from embedded resources Common behavior across all frameworks: - Check if configuration exists before installing (allows user override) - Load configuration from embedded resources via resources.GetResource() - Log warnings on failure (non-fatal, continues build) - Add proper imports: os, resources, strings (where needed) This matches the Ruby buildpack pattern of providing working defaults while allowing users to supply custom configurations.
Add comprehensive unit tests for five frameworks: - app_dynamics_test.go: Tests AppDynamics config file installation - new_relic_test.go: Tests New Relic config with ERB template processing - azure_application_insights_agent_test.go: Tests Azure AI config - luna_security_provider_test.go: Tests Luna HSM configuration - protect_app_security_provider_test.go: Tests ProtectApp configuration Each test suite verifies: - Embedded configuration files exist in resources - Configuration content is valid - Files are created in correct locations - Existing configurations are not overwritten - Error handling works correctly These tests ensure default configurations are properly installed during the supply phase.
Change framework installation error handling from warnings to fatal errors: - Framework installation failures now abort the build - Previously logged warning and continued with other frameworks - New behavior matches Ruby buildpack (fail fast on framework errors) Rationale: Framework installation failures indicate serious issues (missing dependencies, configuration problems, resource constraints) that should not be silently ignored. Applications deployed with failed framework installations may lack critical APM monitoring, security features, or container customizations. This ensures build failures are visible and actionable rather than resulting in partially configured applications.
- Remove .Focus() from Tomcat test to run all integration tests - Update test comments for accuracy: 'detected and installed' -> 'detected' (installation verification is implicit in the framework supply phase) This ensures the full integration test suite runs during CI/CD rather than only focused tests.
Changes:
- Make access logging disabled by default (was hardcoded to enabled)
- Add isAccessLoggingEnabled() method to parse JBP_CONFIG_TOMCAT
- Support configuration via: JBP_CONFIG_TOMCAT='{access_logging_support: {access_logging: enabled}}'
- Default: -Daccess.logging.enabled=false (Ruby buildpack parity)
- Enabled: -Daccess.logging.enabled=true (when explicitly configured)
This matches the Ruby buildpack default behavior where access logging
is disabled by default to reduce log volume and I/O overhead. Users
can opt-in to access logs when needed for debugging or compliance.
The CloudFoundryAccessLoggingValve in server.xml uses the system
property to enable/disable access logging at runtime.
Integration tests:
- Add test for default behavior (disabled, no [ACCESS] logs)
- Add test for enabled behavior (via JBP_CONFIG_TOMCAT)
- Update existing test to explicitly enable access logging
- Remove test focus from init_test.go
Resolves Ruby buildpack parity issue for -Daccess.logging.enabled
for stack(s) cflinuxfs4, cflinuxfs5
for stack(s) cflinuxfs4, cflinuxfs5
for stack(s) cflinuxfs4, cflinuxfs5
Updating version for openjdk for 11.X.X
Updating version for openjdk for 17.X.X
Updating version for openjdk for 21.X.X
Updating version for openjdk for 25.X.X
for stack(s) cflinuxfs4, cflinuxfs5
Updating version for zulu for 8.X.X
for stack(s) cflinuxfs4, cflinuxfs5
for stack(s) cflinuxfs4, cflinuxfs5
Updating version for zulu for 11.X.X
Updating version for zulu for 17.X.X
for stack(s) cflinuxfs4, cflinuxfs5
for stack(s) cflinuxfs4, cflinuxfs5
Updating version for sapmachine for 21.X.X
Updating version for sapmachine for 25.X.X
for stack(s) cflinuxfs4, cflinuxfs5
Updating version for sapmachine for 17.X.X
#1208) Framework JAR dependencies (e.g. MariaDB JDBC, PostgreSQL JDBC, Spring Auto-reconfiguration, Java CF Env, Client Certificate Mapper) were written to deps/index/env/CLASSPATH during staging but that file is never sourced at runtime, so the dependencies were silently dropped from the application classpath. - Replace WriteEnvFile("CLASSPATH", ...) with WriteProfileD() scripts in all framework finalizers so CLASSPATH is assembled correctly at runtime when profile.d scripts are sourced - Extract container_security_provider JAR path into a CONTAINER_SECURITY_PROVIDER env var (set via profile.d) and thread it through tomcat (setenv.sh CLASSPATH) and spring-boot (via -cp / -Dloader.path flags) instead of using -Xbootclasspath/a - Add a zzz_classpath_symlinks.sh profile.d script for tomcat (WEB-INF/lib) and spring-boot (BOOT-INF/lib) containers that symlinks every entry on CLASSPATH into the container's lib directory so deps are subject to application class-loading - Allow symlinked resources in Tomcat context.xml (allowLinking='true') - Remove redundant context struct construction in finalizeFrameworks; reuse the ctx already built in Run()
…refactor spring-boot container (#1209) * Refactor Spring Boot launcher class handling * Fix tests
[go-migration] Updated distZip container functionality.
…lasspath (#1215) * Refactor Groovy command construction with classpath * Add container security provider * Adjust profile.d for additional classpath * Consider CLASSPATH * Refactoring + comment * Adjust groovy container * Add unit tests * Add integration test
* Just show JMX name for consistent framework naming, not jmx=<port>. Note: "JMX enabled on port %d" shows port already. * Consistent frameworks logging, with brackets and commas.
# Conflicts: # bin/run # config/jprofiler_profiler.yml # config/ruby.yml
kiril-keranov
approved these changes
Mar 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR is a complete rewrite of the java-buildpack from Ruby to Go, aligning it with
the rest of the Cloud Foundry buildpack ecosystem. The buildpack now uses the standard
libbuildpack shared library and follows
the same architecture, conventions, and lifecycle contracts as the go-buildpack,
python-buildpack, ruby-buildpack, and all other modern CF buildpacks.
What Changed
Architecture
libbuildpack— leverages the shared CF buildpack library for manifest parsing,dependency installation (3-tier caching), staging directory management, and structured logging
switchblade— integration tests now use the standard CF buildpack testing frameworkbin/detect,bin/supply,bin/finalize,bin/releaseall follow the standard bash-wrapper → Go binary pattern used across CF buildpacks
manifest.yml— dependency declarations follow the same format as all otherCF buildpacks, with full
cf_stacksfiltering and SHA256 checksumsSource Structure
Preserved Functionality
All existing Java buildpack functionality has been preserved and ported:
JaCoCo, JMX, jvmkill, memory-calculator, auto-reconfiguration, and more
Why This Rewrite
The previous Ruby implementation was the last remaining non-Go buildpack in the CF ecosystem.
This rewrite:
Backup
The previous Ruby-based
mainis preserved atbackup/main-before-go-migrationfor reference.