diff --git a/src/integration/frameworks_test.go b/src/integration/frameworks_test.go index 639e70b82..7ce096e68 100644 --- a/src/integration/frameworks_test.go +++ b/src/integration/frameworks_test.go @@ -536,7 +536,7 @@ func testFrameworks(platform switchblade.Platform, fixtures string) func(*testin deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER": "'{enabled: true}'", + "JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER": "{ enabled: true }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat")) Expect(err).NotTo(HaveOccurred(), logs.String) @@ -550,7 +550,7 @@ func testFrameworks(platform switchblade.Platform, fixtures string) func(*testin deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER": "'{enabled: false}'", + "JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER": "{ enabled: false }", }). Execute(name, filepath.Join(fixtures, "containers", "tomcat")) Expect(err).NotTo(HaveOccurred(), logs.String) @@ -851,7 +851,7 @@ func testFrameworks(platform switchblade.Platform, fixtures string) func(*testin deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", - "JBP_CONFIG_JPROFILER_PROFILER": "'{enabled: true}'", + "JBP_CONFIG_JPROFILER_PROFILER": "{ enabled: true }", }). Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged")) Expect(err).NotTo(HaveOccurred(), logs.String) diff --git a/src/java/common/context.go b/src/java/common/context.go index 92e20c44c..da735415d 100644 --- a/src/java/common/context.go +++ b/src/java/common/context.go @@ -3,12 +3,11 @@ package common import ( "encoding/json" "fmt" + "github.com/cloudfoundry/libbuildpack" "os" "path/filepath" "strconv" "strings" - - "github.com/cloudfoundry/libbuildpack" ) // Context holds shared dependencies for buildpack components diff --git a/src/java/common/yaml_handler.go b/src/java/common/yaml_handler.go new file mode 100644 index 000000000..b85c81483 --- /dev/null +++ b/src/java/common/yaml_handler.go @@ -0,0 +1,26 @@ +package common + +import ( + "bytes" + "go.yaml.in/yaml/v3" +) + +// YamlHandler provides a thin wrapper around yaml.v3's Marshal and Unmarshal. +type YamlHandler struct{} + +// Unmarshal decodes the YAML data into the provided destination. +func (h YamlHandler) Unmarshal(data []byte, out any) error { + return yaml.Unmarshal(data, out) +} + +// Marshal encodes the given value into YAML. +func (h YamlHandler) Marshal(in any) ([]byte, error) { + return yaml.Marshal(in) +} + +// ValidateFields is used to detect unknown fields during parsing of JBP_CONFIG* configurations +func (h YamlHandler) ValidateFields(data []byte, out interface{}) error { + dec := yaml.NewDecoder(bytes.NewReader(data)) + dec.KnownFields(true) + return dec.Decode(out) +} diff --git a/src/java/frameworks/client_certificate_mapper.go b/src/java/frameworks/client_certificate_mapper.go index 52a06afb4..85c097183 100644 --- a/src/java/frameworks/client_certificate_mapper.go +++ b/src/java/frameworks/client_certificate_mapper.go @@ -1,8 +1,8 @@ package frameworks import ( - "github.com/cloudfoundry/java-buildpack/src/java/common" "fmt" + "github.com/cloudfoundry/java-buildpack/src/java/common" "os" "path/filepath" ) @@ -23,7 +23,13 @@ func NewClientCertificateMapperFramework(ctx *common.Context) *ClientCertificate // Enabled by default to support mTLS scenarios, can be disabled via configuration func (c *ClientCertificateMapperFramework) Detect() (string, error) { // Check if explicitly disabled via configuration - if !c.isEnabled() { + config, err := c.loadConfig() + if err != nil { + c.context.Log.Warning("Failed to load ccm config: %s", err.Error()) + return "", nil // Don't fail the build + } + + if !config.isEnabled() { return "", nil } @@ -77,25 +83,31 @@ func (c *ClientCertificateMapperFramework) Finalize() error { return nil } -// isEnabled checks if client certificate mapper is enabled -// Default is true (enabled) to support mTLS scenarios unless explicitly disabled -func (c *ClientCertificateMapperFramework) isEnabled() bool { - // Check JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER environment variable +func (c *ClientCertificateMapperFramework) loadConfig() (*ccmConfig, error) { + // initialize default values + mapperConfig := ccmConfig{ + Enabled: true, + } config := os.Getenv("JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER") - - // Parse the config to check for enabled: false - // For simplicity, if JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER is set and contains "enabled", check its value - // A more robust implementation would parse YAML if config != "" { - // Simple check: if it contains "enabled: false" or "'enabled': false" - if contains(config, "enabled: false") || contains(config, "'enabled': false") { - return false + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &mapperConfig) + if err != nil { + c.context.Log.Warning("Unknown user config values: %s", err.Error()) } - if contains(config, "enabled: true") || contains(config, "'enabled': true") { - return true + // overlay JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER over default values + if err = yamlHandler.Unmarshal([]byte(config), &mapperConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_CLIENT_CERTIFICATE_MAPPER: %w", err) } } + return &mapperConfig, nil +} - // Default to enabled (to support mTLS client certificate authentication) - return true +type ccmConfig struct { + Enabled bool `yaml:"enabled"` +} + +// isEnabled checks if client certificate mapper is enabled +func (c *ccmConfig) isEnabled() bool { + return c.Enabled } diff --git a/src/java/frameworks/container_security_provider.go b/src/java/frameworks/container_security_provider.go index e12685eec..3c060784e 100644 --- a/src/java/frameworks/container_security_provider.go +++ b/src/java/frameworks/container_security_provider.go @@ -95,13 +95,17 @@ func (c *ContainerSecurityProviderFramework) Finalize() error { return fmt.Errorf("failed to write security properties: %w", err) } + config, err := c.loadConfig() + if err != nil { + c.context.Log.Warning("Failed to load container security provider config: %s", err.Error()) + } // Add key manager and trust manager configuration if specified - keyManagerEnabled := c.getKeyManagerEnabled() + keyManagerEnabled := config.getKeyManagerEnabled() if keyManagerEnabled != "" { javaOpts += fmt.Sprintf(" -Dorg.cloudfoundry.security.keymanager.enabled=%s", keyManagerEnabled) } - trustManagerEnabled := c.getTrustManagerEnabled() + trustManagerEnabled := config.getTrustManagerEnabled() if trustManagerEnabled != "" { javaOpts += fmt.Sprintf(" -Dorg.cloudfoundry.security.trustmanager.enabled=%s", trustManagerEnabled) } @@ -214,44 +218,38 @@ func (c *ContainerSecurityProviderFramework) getDefaultSecurityProviders() []str } } -// getKeyManagerEnabled returns the key_manager_enabled configuration value -func (c *ContainerSecurityProviderFramework) getKeyManagerEnabled() string { - config := os.Getenv("JBP_CONFIG_CONTAINER_SECURITY_PROVIDER") - if config == "" { - return "" +func (c *ContainerSecurityProviderFramework) loadConfig() (*cspConfig, error) { + // initialize default values + secConfig := cspConfig{ + KeyManagerEnabled: "", + TrustManagerEnabled: "", } - - // Parse configuration for key_manager_enabled - // Format: {key_manager_enabled: true} or {'key_manager_enabled': 'true'} - if contains(config, "key_manager_enabled") { - if contains(config, "true") { - return "true" + config := os.Getenv("JBP_CONFIG_CONTAINER_SECURITY_PROVIDER") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &secConfig) + if err != nil { + c.context.Log.Warning("Unknown user config values: %s", err.Error()) } - if contains(config, "false") { - return "false" + // overlay JBP_CONFIG_CONTAINER_SECURITY_PROVIDER over default values + if err = yamlHandler.Unmarshal([]byte(config), &secConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_CONTAINER_SECURITY_PROVIDER: %w", err) } } + return &secConfig, nil +} - return "" +// getKeyManagerEnabled returns the key_manager_enabled configuration value +func (c *cspConfig) getKeyManagerEnabled() string { + return c.KeyManagerEnabled } // getTrustManagerEnabled returns the trust_manager_enabled configuration value -func (c *ContainerSecurityProviderFramework) getTrustManagerEnabled() string { - config := os.Getenv("JBP_CONFIG_CONTAINER_SECURITY_PROVIDER") - if config == "" { - return "" - } - - // Parse configuration for trust_manager_enabled - // Format: {trust_manager_enabled: true} or {'trust_manager_enabled': 'true'} - if contains(config, "trust_manager_enabled") { - if contains(config, "true") { - return "true" - } - if contains(config, "false") { - return "false" - } - } +func (c *cspConfig) getTrustManagerEnabled() string { + return c.TrustManagerEnabled +} - return "" +type cspConfig struct { + KeyManagerEnabled string `yaml:"key_manager_enabled"` + TrustManagerEnabled string `yaml:"trust_manager_enabled"` } diff --git a/src/java/frameworks/debug.go b/src/java/frameworks/debug.go index 837432aa9..01af41076 100644 --- a/src/java/frameworks/debug.go +++ b/src/java/frameworks/debug.go @@ -21,23 +21,32 @@ func NewDebugFramework(ctx *common.Context) *DebugFramework { // Detect checks if debugging should be enabled func (d *DebugFramework) Detect() (string, error) { // Check if debug is enabled in configuration - enabled := d.isEnabled() - if !enabled { + config, err := d.loadConfig() + if err != nil { + d.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return "", nil // Don't fail the build + } + if !config.isEnabled() { return "", nil } - port := d.getPort() + port := config.getPort() return fmt.Sprintf("debug=%d", port), nil } // Supply performs debug setup during supply phase func (d *DebugFramework) Supply() error { - if !d.isEnabled() { + config, err := d.loadConfig() + if err != nil { + d.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return nil // Don't fail the build + } + if !config.isEnabled() { return nil } - port := d.getPort() - suspend := d.getSuspend() + port := config.getPort() + suspend := config.getSuspend() suspendMsg := "" if suspend { @@ -50,12 +59,17 @@ func (d *DebugFramework) Supply() error { // Finalize adds debug options to JAVA_OPTS func (d *DebugFramework) Finalize() error { - if !d.isEnabled() { + config, err := d.loadConfig() + if err != nil { + d.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return nil // Don't fail the build + } + if !config.isEnabled() { return nil } - port := d.getPort() - suspend := d.getSuspend() + port := config.getPort() + suspend := config.getSuspend() // Build JDWP agent string suspendValue := "n" @@ -74,7 +88,7 @@ func (d *DebugFramework) Finalize() error { } // isEnabled checks if debugging is enabled -func (d *DebugFramework) isEnabled() bool { +func (d *debugConfig) isEnabled() bool { // Check BPL_DEBUG_ENABLED first (Cloud Native Buildpacks convention) bplEnabled := os.Getenv("BPL_DEBUG_ENABLED") if bplEnabled == "true" || bplEnabled == "1" { @@ -84,28 +98,11 @@ func (d *DebugFramework) isEnabled() bool { return false } - // Check JBP_CONFIG_DEBUG environment variable (Java Buildpack convention) - config := os.Getenv("JBP_CONFIG_DEBUG") - - // Parse the config to check for enabled: true - // For simplicity, if JBP_CONFIG_DEBUG is set and contains "enabled", check its value - // A more robust implementation would parse YAML - if config != "" { - // Simple check: if it contains "enabled: true" or just "true" - if contains(config, "enabled: true") || contains(config, "'enabled': true") { - return true - } - if contains(config, "enabled: false") || contains(config, "'enabled': false") { - return false - } - } - - // Default to disabled (matches Ruby buildpack default) - return false + return d.Enabled } // getPort returns the debug port -func (d *DebugFramework) getPort() int { +func (d *debugConfig) getPort() int { // Check BPL_DEBUG_PORT first (Cloud Native Buildpacks convention) bplPort := os.Getenv("BPL_DEBUG_PORT") if bplPort != "" { @@ -114,35 +111,40 @@ func (d *DebugFramework) getPort() int { } } - // Check JBP_CONFIG_DEBUG for port setting (Java Buildpack convention) - config := os.Getenv("JBP_CONFIG_DEBUG") - if config != "" { - // Simple parsing - look for port: XXXX - // A more robust implementation would parse YAML - if idx := findInString(config, "port:"); idx != -1 { - portStr := extractNumber(config[idx:]) - if port, err := strconv.Atoi(portStr); err == nil && port > 0 { - return port - } - } - } - - // Default port - return 8000 + return d.Port } // getSuspend returns whether to suspend on start -func (d *DebugFramework) getSuspend() bool { - // Check JBP_CONFIG_DEBUG for suspend setting +func (d *debugConfig) getSuspend() bool { + return d.Suspend +} + +type debugConfig struct { + Enabled bool `yaml:"enabled"` + Port int `yaml:"port"` + Suspend bool `yaml:"suspend"` +} + +func (d *DebugFramework) loadConfig() (*debugConfig, error) { + // initialize default values + dbgConfig := debugConfig{ + Enabled: false, + Port: 8000, + Suspend: false, + } config := os.Getenv("JBP_CONFIG_DEBUG") if config != "" { - if contains(config, "suspend: true") || contains(config, "'suspend': true") { - return true + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &dbgConfig) + if err != nil { + d.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_DEBUG over default values + if err = yamlHandler.Unmarshal([]byte(config), &dbgConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_DEBUG: %w", err) } } - - // Default to false - return false + return &dbgConfig, nil } // Helper function to check if string contains substring diff --git a/src/java/frameworks/google_stackdriver_profiler.go b/src/java/frameworks/google_stackdriver_profiler.go index ac1d1c166..176a45ed7 100644 --- a/src/java/frameworks/google_stackdriver_profiler.go +++ b/src/java/frameworks/google_stackdriver_profiler.go @@ -28,6 +28,7 @@ import ( type GoogleStackdriverProfilerFramework struct { context *common.Context agentPath string + config *googleStackDriveConfig } // NewGoogleStackdriverProfilerFramework creates a new Google Stackdriver Profiler framework instance @@ -120,6 +121,12 @@ func (g *GoogleStackdriverProfilerFramework) Finalize() error { // Build agentpath option with arguments var agentArgs []string + err = g.loadConfig() + if err != nil { + g.context.Log.Warning("Failed to load google stack driver profiler config: %s", err.Error()) + return nil // Do not fail the build + } + // Add service name (application name) if appName := g.getApplicationName(); appName != "" { agentArgs = append(agentArgs, fmt.Sprintf("-cprof_service=%s", appName)) @@ -200,6 +207,9 @@ func (g *GoogleStackdriverProfilerFramework) getCredentials() GoogleProfilerCred // getApplicationName returns the application name func (g *GoogleStackdriverProfilerFramework) getApplicationName() string { + if g.config.ApplicationName != "" { + return g.config.ApplicationName + } vcapApp := os.Getenv("VCAP_APPLICATION") if vcapApp == "" { return "" @@ -219,6 +229,9 @@ func (g *GoogleStackdriverProfilerFramework) getApplicationName() string { // getApplicationVersion returns the application version func (g *GoogleStackdriverProfilerFramework) getApplicationVersion() string { + if g.config.ApplicationVersion != "" { + return g.config.ApplicationVersion + } vcapApp := os.Getenv("VCAP_APPLICATION") if vcapApp == "" { return "" @@ -245,3 +258,30 @@ func (g *GoogleStackdriverProfilerFramework) constructAgentPath(profilerDir stri g.agentPath = agentPattern return nil } + +func (g *GoogleStackdriverProfilerFramework) loadConfig() error { + // initialize default values + gsdConfig := googleStackDriveConfig{ + ApplicationName: "", + ApplicationVersion: "", + } + config := os.Getenv("JBP_CONFIG_GOOGLE_STACK_DRIVE_PROFILER") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &gsdConfig) + if err != nil { + g.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_GOOGLE_STACK_DRIVE_PROFILER over default values + if err = yamlHandler.Unmarshal([]byte(config), &gsdConfig); err != nil { + return fmt.Errorf("failed to parse JBP_CONFIG_GOOGLE_STACK_DRIVE_PROFILER: %w", err) + } + } + g.config = &gsdConfig + return nil +} + +type googleStackDriveConfig struct { + ApplicationName string `yaml:"application_name"` + ApplicationVersion string `yaml:"application_version"` +} diff --git a/src/java/frameworks/java_memory_assistant.go b/src/java/frameworks/java_memory_assistant.go index 5e2f53f60..32fa10e02 100644 --- a/src/java/frameworks/java_memory_assistant.go +++ b/src/java/frameworks/java_memory_assistant.go @@ -23,7 +23,12 @@ func NewJavaMemoryAssistantFramework(ctx *common.Context) *JavaMemoryAssistantFr // Must be explicitly enabled via configuration (disabled by default) func (j *JavaMemoryAssistantFramework) Detect() (string, error) { // Check if explicitly enabled via configuration - if !j.isEnabled() { + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load java memory assistant config: %s", err.Error()) + return "", nil // Don't fail the build + } + if !config.isEnabled() { j.context.Log.Debug("Java Memory Assistant is disabled (default)") return "", nil } @@ -115,10 +120,11 @@ func (j *JavaMemoryAssistantFramework) buildAgentConfig() string { var configParts []string // Get configuration from JBP_CONFIG_JAVA_MEMORY_ASSISTANT environment variable - config := os.Getenv("JBP_CONFIG_JAVA_MEMORY_ASSISTANT") - - // Parse configuration (simplified - in production, parse YAML properly) - // For now, we'll use default values that can be overridden + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load java memory assistant config: %s", err.Error()) + return "" // Don't fail the build + } // Heap dump folder (default: $PWD or volume service mount point) heapDumpFolder := j.getHeapDumpFolder() @@ -127,26 +133,26 @@ func (j *JavaMemoryAssistantFramework) buildAgentConfig() string { } // Check interval (default: 5s) - checkInterval := j.getConfigValue(config, "check_interval", "5s") + checkInterval := config.Agent.CheckInterval configParts = append(configParts, fmt.Sprintf("check-interval=%s", checkInterval)) // Max frequency (default: 1/1m) - maxFrequency := j.getConfigValue(config, "max_frequency", "1/1m") + maxFrequency := config.Agent.MaxFrequency configParts = append(configParts, fmt.Sprintf("max-frequency=%s", maxFrequency)) // Log level (use buildpack log level if not specified) - logLevel := j.getConfigValue(config, "log_level", "INFO") + logLevel := config.Agent.LogLevel configParts = append(configParts, fmt.Sprintf("log-level=%s", logLevel)) // Thresholds (default: old_gen >600MB) - thresholds := j.getThresholds(config) + thresholds := config.getThresholds() for memArea, threshold := range thresholds { configParts = append(configParts, fmt.Sprintf("threshold.%s=%s", memArea, threshold)) } // Max dump count (default: 1) - maxDumpCount := j.getConfigValue(config, "max_dump_count", "1") - configParts = append(configParts, fmt.Sprintf("max-dump-count=%s", maxDumpCount)) + maxDumpCount := config.CleanUp.MaxDumpCount + configParts = append(configParts, fmt.Sprintf("max-dump-count=%v", maxDumpCount)) return strings.Join(configParts, ",") } @@ -186,33 +192,97 @@ func (j *JavaMemoryAssistantFramework) getConfigValue(config, key, defaultValue return defaultValue } +func (j *JavaMemoryAssistantFramework) loadConfig() (*jmaConfig, error) { + // initialize default values + jConfig := jmaConfig{ + Enabled: false, + Agent: Agent{ + HeapDumpFolder: "", + CheckInterval: "5s", + MaxFrequency: "1/1m", + LogLevel: "", + Thresholds: Thresholds{ + Heap: "", + CodeCache: "", + Metaspace: "", + PermGen: "", + CompressedClass: "", + Eden: "", + Survivor: "", + OldGen: ">600MB", + TenuredGen: "", + CodeHeapNonNMethods: "", + CodeHeapNonProfiled: "", + CodeHeapProfiledNMethods: "", + }, + }, + CleanUp: CleanUp{ + MaxDumpCount: 1, + }, + } + config := os.Getenv("JBP_CONFIG_JAVA_MEMORY_ASSISTANT") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &jConfig) + if err != nil { + j.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_JAVA_MEMORY_ASSISTANT over default values + if err = yamlHandler.Unmarshal([]byte(config), &jConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_JAVA_MEMORY_ASSISTANT: %w", err) + } + } + return &jConfig, nil +} + // getThresholds extracts memory threshold configuration -func (j *JavaMemoryAssistantFramework) getThresholds(config string) map[string]string { +func (j *jmaConfig) getThresholds() map[string]string { thresholds := make(map[string]string) - // Default threshold: old_gen >600MB - thresholds["old_gen"] = ">600MB" + yamlHandler := common.YamlHandler{} + data, _ := yamlHandler.Marshal(j.Agent.Thresholds) + + var result map[string]string + yamlHandler.Unmarshal(data, &result) - // In production, parse thresholds from config - // For now, use default return thresholds } // isEnabled checks if Java Memory Assistant is enabled // Default is false (disabled) unless explicitly enabled via configuration -func (j *JavaMemoryAssistantFramework) isEnabled() bool { - // Check JBP_CONFIG_JAVA_MEMORY_ASSISTANT environment variable - config := os.Getenv("JBP_CONFIG_JAVA_MEMORY_ASSISTANT") +func (j *jmaConfig) isEnabled() bool { + return j.Enabled +} - // Parse the config to check for enabled: true - if config != "" { - // Simple check: if it contains "enabled: true" or "'enabled': true" - if contains(config, "enabled: true") || contains(config, "'enabled': true") || - contains(config, "enabled : true") || contains(config, "'enabled' : true") { - return true - } - } +type jmaConfig struct { + Enabled bool `yaml:"enabled"` + Agent Agent `yaml:"agent"` + CleanUp CleanUp `yaml:"clean_up"` +} + +type Agent struct { + HeapDumpFolder string `yaml:"heap_dump_folder"` + CheckInterval string `yaml:"check_interval"` + MaxFrequency string `yaml:"max_frequency"` + LogLevel string `yaml:"log_level"` + Thresholds Thresholds `yaml:"thresholds"` +} + +type Thresholds struct { + Heap string `yaml:"heap"` + CodeCache string `yaml:"code_cache"` + Metaspace string `yaml:"metaspace"` + PermGen string `yaml:"perm_gen"` + CompressedClass string `yaml:"compressed_class"` + Eden string `yaml:"eden"` + Survivor string `yaml:"survivor"` + OldGen string `yaml:"old_gen"` + TenuredGen string `yaml:"tenured_gen"` + CodeHeapNonNMethods string `yaml:"code_heap.non_nmethods"` + CodeHeapNonProfiled string `yaml:"code_heap.non_profiled_nmethods"` + CodeHeapProfiledNMethods string `yaml:"code_heap.profiled_nmethods"` +} - // Default to disabled - return false +type CleanUp struct { + MaxDumpCount int `yaml:"max_dump_count"` } diff --git a/src/java/frameworks/jmx.go b/src/java/frameworks/jmx.go index 96e29b630..0414e8928 100644 --- a/src/java/frameworks/jmx.go +++ b/src/java/frameworks/jmx.go @@ -21,33 +21,41 @@ func NewJmxFramework(ctx *common.Context) *JmxFramework { // Detect checks if JMX should be enabled func (j *JmxFramework) Detect() (string, error) { // Check if JMX is enabled in configuration - enabled := j.isEnabled() - if !enabled { + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return "", nil // Don't fail the build + } + if !config.isEnabled() { return "", nil } - port := j.getPort() + port := config.getPort() return fmt.Sprintf("jmx=%d", port), nil } // Supply performs JMX setup during supply phase func (j *JmxFramework) Supply() error { - if !j.isEnabled() { - return nil + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return nil // Don't fail the build } - port := j.getPort() + port := config.getPort() j.context.Log.BeginStep("JMX enabled on port %d", port) return nil } // Finalize adds JMX options to JAVA_OPTS via profile.d script func (j *JmxFramework) Finalize() error { - if !j.isEnabled() { - return nil + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return nil // Don't fail the build } - port := j.getPort() + port := config.getPort() // Build JMX system properties jmxOpts := fmt.Sprintf( @@ -67,8 +75,29 @@ func (j *JmxFramework) Finalize() error { return nil } +func (j *JmxFramework) loadConfig() (*jmxConfig, error) { + // initialize default values + jConfig := jmxConfig{ + Enabled: false, + Port: 5000, + } + config := os.Getenv("JBP_CONFIG_JMX") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &jConfig) + if err != nil { + j.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_JMX over default values + if err = yamlHandler.Unmarshal([]byte(config), &jConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_JMX: %w", err) + } + } + return &jConfig, nil +} + // isEnabled checks if JMX is enabled -func (j *JmxFramework) isEnabled() bool { +func (j *jmxConfig) isEnabled() bool { // Check BPL_JMX_ENABLED first (Cloud Native Buildpacks convention) bplEnabled := os.Getenv("BPL_JMX_ENABLED") if bplEnabled == "true" || bplEnabled == "1" { @@ -79,24 +108,11 @@ func (j *JmxFramework) isEnabled() bool { } // Check JBP_CONFIG_JMX environment variable (Java Buildpack convention) - config := os.Getenv("JBP_CONFIG_JMX") - - // Parse the config to check for enabled: true - if config != "" { - if contains(config, "enabled: true") || contains(config, "'enabled': true") { - return true - } - if contains(config, "enabled: false") || contains(config, "'enabled': false") { - return false - } - } - - // Default to disabled (matches Ruby buildpack default) - return false + return j.Enabled } // getPort returns the JMX port -func (j *JmxFramework) getPort() int { +func (j *jmxConfig) getPort() int { // Check BPL_JMX_PORT first (Cloud Native Buildpacks convention) bplPort := os.Getenv("BPL_JMX_PORT") if bplPort != "" { @@ -105,18 +121,10 @@ func (j *JmxFramework) getPort() int { } } - // Check JBP_CONFIG_JMX for port setting (Java Buildpack convention) - config := os.Getenv("JBP_CONFIG_JMX") - if config != "" { - // Simple parsing - look for port: XXXX - if idx := findInString(config, "port:"); idx != -1 { - portStr := extractNumber(config[idx:]) - if port, err := strconv.Atoi(portStr); err == nil && port > 0 { - return port - } - } - } + return j.Port +} - // Default port - return 5000 +type jmxConfig struct { + Enabled bool `yaml:"enabled"` + Port int `yaml:"port"` } diff --git a/src/java/frameworks/jprofiler_profiler.go b/src/java/frameworks/jprofiler_profiler.go index 5526d63e9..43627e25b 100644 --- a/src/java/frameworks/jprofiler_profiler.go +++ b/src/java/frameworks/jprofiler_profiler.go @@ -38,13 +38,13 @@ func NewJProfilerProfilerFramework(ctx *common.Context) *JProfilerProfilerFramew func (f *JProfilerProfilerFramework) Detect() (string, error) { // JProfiler is disabled by default // Check for JBP_CONFIG_JPROFILER_PROFILER='{enabled: true}' - enabled := os.Getenv("JBP_CONFIG_JPROFILER_PROFILER") - if enabled != "" { - // Check if "enabled:true" in the agent options - // We need case-insensitive check due to inconsistent casing - if common.ContainsIgnoreCase(enabled, "enabled") && common.ContainsIgnoreCase(enabled, "true") { - return "JProfiler Profiler", nil - } + config, err := f.loadConfig() + if err != nil { + f.context.Log.Warning("Failed to load jprofile profiler config: %s", err.Error()) + return "", nil // Don't fail the build + } + if config.isEnabled() { + return "JProfiler Profiler", nil } return "", nil @@ -111,30 +111,27 @@ func (f *JProfilerProfilerFramework) Finalize() error { } runtimeAgentPath := filepath.Join(fmt.Sprintf("$DEPS_DIR/%s", depsIdx), relPath) - // Build agent options - // Default options: port=8849, nowait (don't wait for profiler UI to connect) - port := "8849" - portConfig := os.Getenv("JBP_CONFIG_JPROFILER_PROFILER") - if portConfig != "" && common.ContainsIgnoreCase(portConfig, "port") { - // Simple extraction (would need proper YAML parsing in production) - // For now, use default - } - - // Check for nowait option (default: true) - nowait := "nowait" - if portConfig != "" && common.ContainsIgnoreCase(portConfig, "nowait") && common.ContainsIgnoreCase(portConfig, "false") { - nowait = "" + config, err := f.loadConfig() + if err != nil { + f.context.Log.Warning("Failed to load jprofile profiler config: %s", err.Error()) + return nil // Don't fail the build } + // Build agent options + // Default options: port=8849, nowait (don't wait for profiler UI to connect) + port := config.Port + nowait := config.NoWait + // Build agent path with options var agentOptions string - if nowait != "" { - agentOptions = fmt.Sprintf("port=%s,%s", port, nowait) + if nowait { + agentOptions = fmt.Sprintf("port=%v,%v", port, "nowait") } else { - agentOptions = fmt.Sprintf("port=%s", port) + agentOptions = fmt.Sprintf("port=%v", port) } javaAgent := fmt.Sprintf("-agentpath:%s=%s", runtimeAgentPath, agentOptions) + f.context.Log.Info("JProfiler Profiler java agent options: %s", javaAgent) // Write to .opts file using priority 30 if err := writeJavaOptsFile(f.context, 30, "jprofiler_profiler", javaAgent); err != nil { return fmt.Errorf("failed to write java_opts file: %w", err) @@ -143,3 +140,35 @@ func (f *JProfilerProfilerFramework) Finalize() error { f.context.Log.Info("JProfiler Profiler configured (priority 30)") return nil } + +func (f *JProfilerProfilerFramework) loadConfig() (*jProfilerConfig, error) { + // initialize default values + jpConfig := jProfilerConfig{ + Enabled: false, + NoWait: true, + Port: 8849, + } + config := os.Getenv("JBP_CONFIG_JPROFILER_PROFILER") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &jpConfig) + if err != nil { + f.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_JPROFILER_PROFILER over default values + if err = yamlHandler.Unmarshal([]byte(config), &jpConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_JPROFILER_PROFILER: %w", err) + } + } + return &jpConfig, nil +} + +type jProfilerConfig struct { + Enabled bool `yaml:"enabled"` + NoWait bool `yaml:"nowait"` + Port int `yaml:"port"` +} + +func (j *jProfilerConfig) isEnabled() bool { + return j.Enabled +} diff --git a/src/java/frameworks/jrebel_agent.go b/src/java/frameworks/jrebel_agent.go index 24352689e..e10af4fef 100644 --- a/src/java/frameworks/jrebel_agent.go +++ b/src/java/frameworks/jrebel_agent.go @@ -20,6 +20,16 @@ func NewJRebelAgentFramework(ctx *common.Context) *JRebelAgentFramework { // Detect determines if JRebel configuration exists in the application func (j *JRebelAgentFramework) Detect() (string, error) { + // Check if explicitly disabled via configuration + config, err := j.loadConfig() + if err != nil { + j.context.Log.Warning("Failed to load jrebel config: %s", err.Error()) + return "", nil // Don't fail the build + } + + if !config.isEnabled() { + return "", nil + } // Check for rebel-remote.xml configuration file in the app rebelRemoteXML := filepath.Join(j.context.Stager.BuildDir(), "rebel-remote.xml") if _, err := os.Stat(rebelRemoteXML); err == nil { @@ -118,3 +128,32 @@ func (j *JRebelAgentFramework) Finalize() error { j.context.Log.Info("JRebel Agent configured successfully (priority 31)") return nil } + +func (j *JRebelAgentFramework) loadConfig() (*jrebelConfig, error) { + // initialize default values + jrConfig := jrebelConfig{ + Enabled: true, + } + config := os.Getenv("JBP_CONFIG_JREBEL") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &jrConfig) + if err != nil { + j.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_JREBEL over default values + if err = yamlHandler.Unmarshal([]byte(config), &jrConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_JREBEL: %w", err) + } + } + return &jrConfig, nil +} + +type jrebelConfig struct { + Enabled bool `yaml:"enabled"` +} + +// isEnabled checks if client certificate mapper is enabled +func (j *jrebelConfig) isEnabled() bool { + return j.Enabled +} diff --git a/src/java/frameworks/luna_security_provider.go b/src/java/frameworks/luna_security_provider.go index 6de8dc1d6..7524a40c7 100644 --- a/src/java/frameworks/luna_security_provider.go +++ b/src/java/frameworks/luna_security_provider.go @@ -274,8 +274,14 @@ func (l *LunaSecurityProviderFramework) writeConfiguration(servers []interface{} } defer file.Close() + config, err := l.loadConfig() + if err != nil { + l.context.Log.Warning("Failed to load luna security provider config: %s", err.Error()) + return err + } + // Write prologue (library configuration and client settings) - if err := l.writePrologue(file); err != nil { + if err := l.writePrologue(file, config); err != nil { return err } @@ -298,7 +304,7 @@ func (l *LunaSecurityProviderFramework) writeConfiguration(servers []interface{} } // Write epilogue (HA configuration) - if err := l.writeEpilogue(file, groups); err != nil { + if err := l.writeEpilogue(file, groups, config); err != nil { return err } @@ -306,13 +312,13 @@ func (l *LunaSecurityProviderFramework) writeConfiguration(servers []interface{} } // writePrologue writes library configuration and client settings -func (l *LunaSecurityProviderFramework) writePrologue(file *os.File) error { +func (l *LunaSecurityProviderFramework) writePrologue(file *os.File, config *lunaSecurityProviderConfig) error { lunaDir := filepath.Join(l.context.Stager.DepDir(), "luna_security_provider") // Get configuration values - loggingEnabled := l.getConfigBool("logging_enabled", false) + loggingEnabled := config.getLoggingEnabled() tcpKeepAlive := 0 - if l.getConfigBool("tcp_keep_alive_enabled", false) { + if config.getTCPKeepAliveEnabled() { tcpKeepAlive = 1 } @@ -396,8 +402,8 @@ func (l *LunaSecurityProviderFramework) writeGroup(file *os.File, index int, gro } // writeEpilogue writes HA configuration and HASynchronize sections -func (l *LunaSecurityProviderFramework) writeEpilogue(file *os.File, groups []interface{}) error { - haLoggingEnabled := l.getConfigBool("ha_logging_enabled", true) +func (l *LunaSecurityProviderFramework) writeEpilogue(file *os.File, groups []interface{}, config *lunaSecurityProviderConfig) error { + haLoggingEnabled := config.getHALoggingEnabled() file.WriteString("}\n\n") file.WriteString("HAConfiguration = {\n") @@ -443,27 +449,47 @@ func (l *LunaSecurityProviderFramework) createSymlink(target, link string) error return os.Symlink(relTarget, link) } -// getConfigBool retrieves a boolean configuration value from JBP_CONFIG_LUNA_SECURITY_PROVIDER -func (l *LunaSecurityProviderFramework) getConfigBool(key string, defaultValue bool) bool { - config := os.Getenv("JBP_CONFIG_LUNA_SECURITY_PROVIDER") - if config == "" { - return defaultValue +func (l *LunaSecurityProviderFramework) loadConfig() (*lunaSecurityProviderConfig, error) { + // initialize default values + lspConfig := lunaSecurityProviderConfig{ + HALoggingEnabled: true, + LoggingEnabled: false, + TCPKeepAliveEnabled: false, } - - // Parse configuration for key - if contains(config, key) { - if contains(config, "true") { - return true + config := os.Getenv("JBP_CONFIG_LUNA_SECURITY_PROVIDER") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &lspConfig) + if err != nil { + l.context.Log.Warning("Unknown user config values: %s", err.Error()) } - if contains(config, "false") { - return false + // overlay JBP_CONFIG_LUNA_SECURITY_PROVIDER over default values + if err = yamlHandler.Unmarshal([]byte(config), &lspConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_LUNA_SECURITY_PROVIDER: %w", err) } } - - return defaultValue + return &lspConfig, nil } // paddedIndex returns a zero-padded two-digit index string func (l *LunaSecurityProviderFramework) paddedIndex(index int) string { return fmt.Sprintf("%02d", index) } + +func (l *lunaSecurityProviderConfig) getHALoggingEnabled() bool { + return l.HALoggingEnabled +} + +func (l *lunaSecurityProviderConfig) getLoggingEnabled() bool { + return l.LoggingEnabled +} + +func (l *lunaSecurityProviderConfig) getTCPKeepAliveEnabled() bool { + return l.TCPKeepAliveEnabled +} + +type lunaSecurityProviderConfig struct { + HALoggingEnabled bool `yaml:"ha_logging_enabled"` + LoggingEnabled bool `yaml:"logging_enabled"` + TCPKeepAliveEnabled bool `yaml:"tcp_keep_alive_enabled"` +} diff --git a/src/java/frameworks/metric_writer.go b/src/java/frameworks/metric_writer.go index f4590614e..627a7ea71 100644 --- a/src/java/frameworks/metric_writer.go +++ b/src/java/frameworks/metric_writer.go @@ -23,7 +23,12 @@ func NewMetricWriterFramework(ctx *common.Context) *MetricWriterFramework { // Detects Micrometer presence and checks if enabled func (m *MetricWriterFramework) Detect() (string, error) { // Check if explicitly enabled via configuration - if !m.isEnabled() { + config, err := m.loadConfig() + if err != nil { + m.context.Log.Warning("Failed to load debug config: %s", err.Error()) + return "", nil // Don't fail the build + } + if !config.isEnabled() { m.context.Log.Debug("Metric Writer is disabled (default)") return "", nil } @@ -167,24 +172,31 @@ func (m *MetricWriterFramework) buildCFTagEnvVars() string { return strings.Join(envVars, "\n") } -// isEnabled checks if Metric Writer is enabled -// Default is false (disabled) unless explicitly enabled via configuration -func (m *MetricWriterFramework) isEnabled() bool { - // Check JBP_CONFIG_METRIC_WRITER environment variable +func (m *MetricWriterFramework) loadConfig() (*metricWriterConfig, error) { + // initialize default values + mwConfig := metricWriterConfig{ + Enabled: true, + } config := os.Getenv("JBP_CONFIG_METRIC_WRITER") - - // Parse the config to check for enabled: true if config != "" { - // Simple check: if it contains "enabled: true" or "'enabled': true" - if contains(config, "enabled: true") || contains(config, "'enabled': true") || - contains(config, "enabled : true") || contains(config, "'enabled' : true") { - return true + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &mwConfig) + if err != nil { + m.context.Log.Warning("Unknown user config values: %s", err.Error()) } - if contains(config, "enabled: false") || contains(config, "'enabled': false") { - return false + // overlay JBP_CONFIG_METRIC_WRITER over default values + if err = yamlHandler.Unmarshal([]byte(config), &mwConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_METRIC_WRITER: %w", err) } } + return &mwConfig, nil +} - // Default to disabled - return false +type metricWriterConfig struct { + Enabled bool `yaml:"enabled"` +} + +// isEnabled checks if client certificate mapper is enabled +func (m *metricWriterConfig) isEnabled() bool { + return m.Enabled } diff --git a/src/java/frameworks/sealights_agent.go b/src/java/frameworks/sealights_agent.go index 313b9ab21..18b360f0f 100644 --- a/src/java/frameworks/sealights_agent.go +++ b/src/java/frameworks/sealights_agent.go @@ -173,6 +173,30 @@ func (f *SealightsAgentFramework) Finalize() error { // Build javaagent argument javaAgent := fmt.Sprintf("-javaagent:%s", runtimeAgentPath) + // Add if custom config is at place + config, err := f.loadConfig() + if err != nil { + f.context.Log.Warning("Failed to load sealight config: %s", err.Error()) + return nil // Don't fail the build + } + if config.BuildSessionId != "" { + systemProps += fmt.Sprintf(" -Dsl.buildSessionId=%s", config.BuildSessionId) + } + if slProxy, ok := service.Credentials["sl.proxy"].(string); ok && slProxy != "" { + systemProps += fmt.Sprintf(" -Dsl.proxy=%s", slProxy) + } else { + if config.Proxy != "" { + systemProps += fmt.Sprintf(" -Dsl.proxy=%s", config.Proxy) + } + } + if slLabId, ok := service.Credentials["sl.labId"].(string); ok && slLabId != "" { + systemProps += fmt.Sprintf(" -Dsl.labId=%s", slLabId) + } else { + if config.LabId != "" { + systemProps += fmt.Sprintf(" -Dsl.labId=%s", config.LabId) + } + } + // Combine javaagent and system properties javaOpts := fmt.Sprintf("%s %s", javaAgent, systemProps) @@ -190,3 +214,33 @@ func (f *SealightsAgentFramework) Finalize() error { f.context.Log.Info("Sealights Agent configured (priority 39)") return nil } + +func (f *SealightsAgentFramework) loadConfig() (*sealightConfig, error) { + // initialize default values + sConfig := sealightConfig{ + BuildSessionId: "", + LabId: "", + Proxy: "", + AutoUpgrade: false, + } + config := os.Getenv("JBP_CONFIG_SEALIGHTS") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &sConfig) + if err != nil { + f.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_SEALIGHTS over default values + if err = yamlHandler.Unmarshal([]byte(config), &sConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_SEALIGHTS: %w", err) + } + } + return &sConfig, nil +} + +type sealightConfig struct { + BuildSessionId string `yaml:"build_session_id"` + LabId string `yaml:"lab_id"` + Proxy string `yaml:"proxy"` + AutoUpgrade bool `yaml:"auto_upgrade"` +} diff --git a/src/java/frameworks/sky_walking_agent.go b/src/java/frameworks/sky_walking_agent.go index 9edb9b5e6..b70897fbf 100644 --- a/src/java/frameworks/sky_walking_agent.go +++ b/src/java/frameworks/sky_walking_agent.go @@ -120,7 +120,7 @@ func (s *SkyWalkingAgentFramework) Finalize() error { opts = append(opts, fmt.Sprintf("-javaagent:%s", runtimeJarPath)) // Configure application name (default to space:application_name) - appName := GetApplicationName(true) + appName := s.getAppName() if appName != "" { opts = append(opts, fmt.Sprintf("-Dskywalking.agent.service_name=%s", appName)) } @@ -200,6 +200,19 @@ func (s *SkyWalkingAgentFramework) getCredentials() SkyWalkingCredentials { return creds } +func (s *SkyWalkingAgentFramework) getAppName() string { + appName := GetApplicationName(true) + if appName != "" { + return appName + } + config, err := s.loadConfig() + if err != nil { + s.context.Log.Warning("Failed to load sky walking agent config: %s", err.Error()) + return "" + } + return config.DefaultApplicationName +} + func (s *SkyWalkingAgentFramework) constructJarPath(agentDir string) error { // Find the installed agent JAR (in skywalking-agent subdirectory) jarPattern := filepath.Join(agentDir, "skywalking-agent", "skywalking-agent.jar") @@ -209,3 +222,27 @@ func (s *SkyWalkingAgentFramework) constructJarPath(agentDir string) error { s.jarPath = jarPattern return nil } + +func (s *SkyWalkingAgentFramework) loadConfig() (*skyWalkingAgentConfig, error) { + // initialize default values + swaConfig := skyWalkingAgentConfig{ + DefaultApplicationName: "", + } + config := os.Getenv("JBP_CONFIG_SKY_WALKING_AGENT") + if config != "" { + yamlHandler := common.YamlHandler{} + err := yamlHandler.ValidateFields([]byte(config), &swaConfig) + if err != nil { + s.context.Log.Warning("Unknown user config values: %s", err.Error()) + } + // overlay JBP_CONFIG_SKY_WALKING_AGENT over default values + if err = yamlHandler.Unmarshal([]byte(config), &swaConfig); err != nil { + return nil, fmt.Errorf("failed to parse JBP_CONFIG_SKY_WALKING_AGENT: %w", err) + } + } + return &swaConfig, nil +} + +type skyWalkingAgentConfig struct { + DefaultApplicationName string `yaml:"default_application_name"` +}