-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathjava_opts_writer.go
More file actions
142 lines (121 loc) · 5.74 KB
/
Copy pathjava_opts_writer.go
File metadata and controls
142 lines (121 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package frameworks
import (
"fmt"
"github.com/cloudfoundry/java-buildpack/src/java/common"
"os"
"path/filepath"
)
// writeJavaOptsFile writes JAVA_OPTS to a numbered .opts file for centralized assembly
//
// Priority determines execution order (lower numbers run first):
// - 05: JRE base options
// - 11: AppDynamics Agent
// - 12: AspectJ Weaver Agent
// - 13: Azure Application Insights Agent
// - 14: Checkmarx IAST Agent
// - 17: Container Security Provider
// - 18: Contrast Security Agent
// - 19: Datadog Java Agent (changed from 18 to avoid collision)
// - 20: Debug Framework, Elastic APM Agent
// - 21: Google Stackdriver Debugger
// - 22: Google Stackdriver Profiler
// - 26: JaCoCo Agent
// - 27: Introscope Agent
// - 29: JMX Framework
// - 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)
//
// At runtime, profile.d/00_java_opts.sh reads all .opts files in order and assembles JAVA_OPTS
func writeJavaOptsFile(ctx *common.Context, priority int, name string, javaOpts string) error {
// Create java_opts directory in deps
optsDir := filepath.Join(ctx.Stager.DepDir(), "java_opts")
if err := os.MkdirAll(optsDir, 0755); err != nil {
return fmt.Errorf("failed to create java_opts directory: %w", err)
}
// Write .opts file with priority prefix (e.g., 17_container_security.opts)
filename := fmt.Sprintf("%02d_%s.opts", priority, name)
optsFile := filepath.Join(optsDir, filename)
if err := os.WriteFile(optsFile, []byte(javaOpts), 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", filename, err)
}
ctx.Log.Debug("Wrote JAVA_OPTS to %s (priority %d)", filename, priority)
return nil
}
// CreateJavaOptsAssemblyScript creates the centralized profile.d script that assembles all JAVA_OPTS
// This should be called ONCE during finalization (by the finalize coordinator)
func CreateJavaOptsAssemblyScript(ctx *common.Context) error {
// Get the actual buildpack index to support multi-buildpack scenarios
depsIdx := ctx.Stager.DepsIdx()
// Build the assembly script with the correct buildpack index
assemblyScript := fmt.Sprintf(`#!/bin/bash
# Centralized JAVA_OPTS Assembly
# Reads all .opts files from $DEPS_DIR/%s/java_opts/ in numerical order
# and assembles them into a single JAVA_OPTS environment variable
# Expands runtime variables like $DEPS_DIR, $HOME, $JAVA_OPTS, and all other environment variables
# Save original JAVA_OPTS from environment (user-provided)
# Normalize to single line: YAML block scalars (>) may introduce newlines
# Only convert newlines to spaces — do not use xargs which strips quotes and backslashes
USER_JAVA_OPTS=$(printf '%%s' "$JAVA_OPTS" | tr '\n' ' ')
# Start building new JAVA_OPTS
JAVA_OPTS=""
# Escape replacement-special chars once; these values are loop-invariant.
_escaped_deps_dir="${DEPS_DIR//\\/\\\\}"
_escaped_deps_dir="${_escaped_deps_dir//&/\\&}"
_escaped_home="${HOME//\\/\\\\}"
_escaped_home="${_escaped_home//&/\\&}"
_user_java_opts_placeholder='__JAVA_OPTS_BUILDPACK_PLACEHOLDER__'
_escaped_user_java_opts="${USER_JAVA_OPTS//\\/\\\\}"
_escaped_user_java_opts="${_escaped_user_java_opts//&/\\&}"
if [ -d "$DEPS_DIR/%s/java_opts" ]; then
for opts_file in "$DEPS_DIR/%s/java_opts"/*.opts; do
if [ -f "$opts_file" ]; then
# Read content and expand runtime variables
opts_content=$(cat "$opts_file")
# Expand $DEPS_DIR and $HOME using bash parameter expansion.
# In ${var//pattern/repl}, '&' and '\' are special in replacement strings,
# so escape them first to preserve literal path contents.
opts_content="${opts_content//\$DEPS_DIR/$_escaped_deps_dir}"
opts_content="${opts_content//\$HOME/$_escaped_home}"
# Shield $JAVA_OPTS from eval: replace with a placeholder first,
# then substitute the actual value AFTER eval so that quotes and
# backslashes in the user-provided JAVA_OPTS are never exposed to eval.
opts_content="${opts_content//\$JAVA_OPTS/$_user_java_opts_placeholder}"
# Escape \ and " in opts_content so they do not break the
# eval "..." wrapper below. \ must be escaped first.
_eval_safe="${opts_content//\\/\\\\}"
_eval_safe="${_eval_safe//\"/\\\"}"
# Expand any remaining environment variables in opts content via eval.
# Note: eval executes commands, but .opts files are written by the buildpack
# at staging time and run within the container context.
# This matches how the Ruby buildpack naturally expanded variables via shell.
opts_content=$(eval "printf '%%s' \"$_eval_safe\"")
# Now safely substitute JAVA_OPTS after eval (preserves quotes, backslashes, and ampersands)
opts_content="${opts_content//$_user_java_opts_placeholder/$_escaped_user_java_opts}"
if [ -n "$opts_content" ]; then
JAVA_OPTS="$JAVA_OPTS $opts_content"
fi
fi
done
fi
# Trim leading/trailing whitespace
JAVA_OPTS=$(echo "$JAVA_OPTS" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
export JAVA_OPTS
`, depsIdx, depsIdx, depsIdx)
if err := ctx.Stager.WriteProfileD("00_java_opts.sh", assemblyScript); err != nil {
return fmt.Errorf("failed to write 00_java_opts.sh: %w", err)
}
ctx.Log.Debug("Created centralized JAVA_OPTS assembly script: profile.d/00_java_opts.sh")
return nil
}