diff --git a/src/java/common/context.go b/src/java/common/context.go index d9290d2e8e..aa19d4ce26 100644 --- a/src/java/common/context.go +++ b/src/java/common/context.go @@ -66,10 +66,6 @@ func DetermineJavaVersion(javaHome string) (int, error) { releaseFile := filepath.Join(javaHome, "release") content, err := os.ReadFile(releaseFile) if err != nil { - // Default to Java 17 if release file is missing - if os.IsNotExist(err) { - return 17, nil - } return 0, fmt.Errorf("failed to read release file: %w", err) } diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index fab4713ee0..c83213cfce 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -64,45 +64,46 @@ func (t *TomcatContainer) Supply() error { if javaHome != "" { javaMajorVersion, versionErr := common.DetermineJavaVersion(javaHome) - if versionErr == nil { - tomcatVersion := DetermineTomcatVersion(t.config.Tomcat.Version) - t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion) - - // Select Tomcat version pattern based on Java version - var versionPattern string - if tomcatVersion == "" { - t.context.Log.Info("Tomcat version not specified") - if javaMajorVersion >= 11 { - // Java 11+: Use Tomcat 10.x (Jakarta EE 9+) - versionPattern = "10.x" - t.context.Log.Info("Using Tomcat 10.x for Java %d", javaMajorVersion) - } else { - // Java 8-10: Use Tomcat 9.x (Java EE 8) - versionPattern = "9.x" - t.context.Log.Info("Using Tomcat 9.x for Java %d", javaMajorVersion) - } - } else { - versionPattern = tomcatVersion - t.context.Log.Info("Using Tomcat %s for Java %d", versionPattern, javaMajorVersion) - } + if versionErr != nil { + t.context.Log.Warning("Unable to determine Java version: %s (defaulting to 17)", versionErr.Error()) + javaMajorVersion = 17 + } - if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 { - return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) + tomcatVersion := DetermineTomcatVersion(t.config.Tomcat.Version) + t.context.Log.Debug("Detected Java major version: %d", javaMajorVersion) + + // Select Tomcat version pattern based on Java version + var versionPattern string + if tomcatVersion == "" { + t.context.Log.Info("Tomcat version not specified") + if javaMajorVersion >= 11 { + // Java 11+: Use Tomcat 10.x (Jakarta EE 9+) + versionPattern = "10.x" + t.context.Log.Info("Using Tomcat 10.x for Java %d", javaMajorVersion) + } else { + // Java 8-10: Use Tomcat 9.x (Java EE 8) + versionPattern = "9.x" + t.context.Log.Info("Using Tomcat 9.x for Java %d", javaMajorVersion) } + } else { + versionPattern = tomcatVersion + t.context.Log.Info("Using Tomcat %s for Java %d", versionPattern, javaMajorVersion) + } - // Resolve the version pattern to actual version using libbuildpack - allVersions := t.context.Manifest.AllDependencyVersions("tomcat") - resolvedVersion, err := libbuildpack.FindMatchingVersion(versionPattern, allVersions) - if err != nil { - return fmt.Errorf("tomcat version resolution error for pattern %q: %w", versionPattern, err) - } + if strings.HasPrefix(versionPattern, "10.") && javaMajorVersion < 11 { + return fmt.Errorf("Tomcat 10.x requires Java 11+, but Java %d detected", javaMajorVersion) + } - dep.Name = "tomcat" - dep.Version = resolvedVersion - t.context.Log.Debug("Resolved Tomcat version pattern '%s' to %s", versionPattern, resolvedVersion) - } else { - t.context.Log.Warning("Unable to determine Java version: %s", versionErr.Error()) + // Resolve the version pattern to actual version using libbuildpack + allVersions := t.context.Manifest.AllDependencyVersions("tomcat") + resolvedVersion, err := libbuildpack.FindMatchingVersion(versionPattern, allVersions) + if err != nil { + return fmt.Errorf("tomcat version resolution error for pattern %q: %w", versionPattern, err) } + + dep.Name = "tomcat" + dep.Version = resolvedVersion + t.context.Log.Debug("Resolved Tomcat version pattern '%s' to %s", versionPattern, resolvedVersion) } // Fallback to default version if we couldn't determine Java version diff --git a/src/java/jres/base_jre.go b/src/java/jres/base_jre.go new file mode 100644 index 0000000000..f1749b136b --- /dev/null +++ b/src/java/jres/base_jre.go @@ -0,0 +1,245 @@ +package jres + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/cloudfoundry/java-buildpack/src/java/common" +) + +// BaseJRE provides the shared implementation for all standard JRE providers. +// Variation between JREs is injected via fields — implementers never override +// Supply/Finalize, avoiding the "forgot to call base" footgun. +type BaseJRE struct { + ctx *common.Context + jreDir string + version string + javaHome string + memoryCalc *MemoryCalculator + jvmkill *JVMKillAgent + installedVersion string + + jreName string + jreKey string + dirPrefixes []string + dirExacts []string + installErrNote string + + extraFinalizeOpts func() string +} + +func newBaseJRE(ctx *common.Context, jreName, jreKey string, dirPrefixes, dirExacts []string, installErrNote string) BaseJRE { + return BaseJRE{ + ctx: ctx, + jreDir: filepath.Join(ctx.Stager.DepDir(), "jre"), + jreName: jreName, + jreKey: jreKey, + dirPrefixes: dirPrefixes, + dirExacts: dirExacts, + installErrNote: installErrNote, + } +} + +func (b *BaseJRE) Name() string { + return b.jreName +} + +func (b *BaseJRE) Detect() (bool, error) { + return DetectJREByEnv(b.jreKey), nil +} + +func (b *BaseJRE) Supply() error { + b.ctx.Log.BeginStep("Installing %s", b.jreName) + + dep, err := GetJREVersion(b.ctx, b.jreKey) + if err != nil { + return fmt.Errorf("failed to determine %s version from manifest: %w", b.jreName, err) + } + + b.version = dep.Version + b.ctx.Log.Info("Installing %s (%s)", b.jreName, b.version) + + if err := b.ctx.Installer.InstallDependency(dep, b.jreDir); err != nil { + if b.installErrNote != "" { + return fmt.Errorf("failed to install %s: %w %s", b.jreName, err, b.installErrNote) + } + return fmt.Errorf("failed to install %s: %w", b.jreName, err) + } + + javaHome, err := b.findJavaHome() + if err != nil { + return fmt.Errorf("failed to find JAVA_HOME: %w", err) + } + b.javaHome = javaHome + b.installedVersion = b.version + + if err := b.writeProfileDScript(); err != nil { + b.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) + } else { + b.ctx.Log.Debug("Created profile.d script: java.sh") + } + + if err := b.ctx.Stager.WriteEnvFile("JAVA_HOME", javaHome); err != nil { + b.ctx.Log.Warning("Could not write JAVA_HOME env file: %s", err.Error()) + } + + javaBin := filepath.Join(javaHome, "bin", "java") + if err := b.ctx.Stager.AddBinDependencyLink(javaBin, "java"); err != nil { + b.ctx.Log.Warning("Could not add java bin dependency link: %s", err.Error()) + } + + libDir := filepath.Join(javaHome, "lib") + if _, err := os.Stat(libDir); err == nil { + if err := b.ctx.Stager.LinkDirectoryInDepDir(libDir, "lib"); err != nil { + b.ctx.Log.Warning("Could not link JRE lib directory: %s", err.Error()) + } + } + + javaMajorVersion, err := common.DetermineJavaVersion(javaHome) + if err != nil { + b.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) + javaMajorVersion = 17 + } + b.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) + + b.jvmkill = NewJVMKillAgent(b.ctx, b.jreDir, b.version) + if err := b.jvmkill.Supply(); err != nil { + b.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) + } + + b.memoryCalc = NewMemoryCalculator(b.ctx, b.jreDir, b.version, javaMajorVersion, b.jreKey) + if err := b.memoryCalc.Supply(); err != nil { + b.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) + } + + b.ctx.Log.Info("%s installation complete", b.jreName) + return nil +} + +func (b *BaseJRE) Finalize() error { + b.ctx.Log.BeginStep("Finalizing %s configuration", b.jreName) + + if b.javaHome == "" { + javaHome, err := b.findJavaHome() + if err != nil { + b.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) + } else { + b.javaHome = javaHome + } + } + + if b.javaHome != "" { + if err := os.Setenv("JAVA_HOME", b.javaHome); err != nil { + b.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) + } else { + b.ctx.Log.Debug("Set JAVA_HOME=%s", b.javaHome) + } + } + + javaMajorVersion := 17 + if b.javaHome != "" { + if ver, err := common.DetermineJavaVersion(b.javaHome); err != nil { + b.ctx.Log.Warning("Could not determine Java version: %s (defaulting to %d)", err.Error(), javaMajorVersion) + } else { + javaMajorVersion = ver + } + } + + if b.jvmkill == nil { + b.jvmkill = NewJVMKillAgent(b.ctx, b.jreDir, b.version) + } + if err := b.jvmkill.Finalize(); err != nil { + b.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) + } + + baseOpts := []string{ + "-Djava.io.tmpdir=$TMPDIR", + "-XX:ActiveProcessorCount=$(nproc)", + } + if err := WriteJavaOpts(b.ctx, strings.Join(baseOpts, " ")); err != nil { + b.ctx.Log.Warning("Failed to write base JAVA_OPTS: %s", err.Error()) + } + + if b.memoryCalc == nil { + b.memoryCalc = NewMemoryCalculator(b.ctx, b.jreDir, b.version, javaMajorVersion, b.jreKey) + } + if err := b.memoryCalc.Finalize(); err != nil { + b.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) + } + + if b.extraFinalizeOpts != nil { + if extraOpts := b.extraFinalizeOpts(); extraOpts != "" { + if err := WriteJavaOpts(b.ctx, extraOpts); err != nil { + b.ctx.Log.Warning("Failed to write %s JAVA_OPTS: %s", b.jreName, err.Error()) + } + } + } + + b.ctx.Log.Info("%s finalization complete", b.jreName) + return nil +} + +func (b *BaseJRE) JavaHome() string { + return b.javaHome +} + +func (b *BaseJRE) Version() string { + return b.installedVersion +} + +func (b *BaseJRE) MemoryCalculatorCommand() string { + if b.memoryCalc == nil { + return "" + } + return b.memoryCalc.GetCalculatorCommand() +} + +func (b *BaseJRE) findJavaHome() (string, error) { + entries, err := os.ReadDir(b.jreDir) + if err != nil { + return "", fmt.Errorf("failed to read JRE directory: %w", err) + } + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + name := entry.Name() + matches := false + for _, prefix := range b.dirPrefixes { + if strings.HasPrefix(name, prefix) { + matches = true + break + } + } + if !matches { + for _, exact := range b.dirExacts { + if name == exact { + matches = true + break + } + } + } + if !matches { + continue + } + + path := filepath.Join(b.jreDir, name) + if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { + return path, nil + } + } + + if _, err := os.Stat(filepath.Join(b.jreDir, "bin", "java")); err == nil { + return b.jreDir, nil + } + + return "", fmt.Errorf("could not find valid JAVA_HOME in %s", b.jreDir) +} + +func (b *BaseJRE) writeProfileDScript() error { + return WriteJavaHomeProfileD(b.ctx, b.jreDir, b.javaHome) +} diff --git a/src/java/jres/graalvm.go b/src/java/jres/graalvm.go index 86a9a6662c..5fdce9ba20 100644 --- a/src/java/jres/graalvm.go +++ b/src/java/jres/graalvm.go @@ -1,219 +1,11 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// GraalVMJRE implements the JRE interface for GraalVM -type GraalVMJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// GraalVMJRE implements the JRE interface for GraalVM. +type GraalVMJRE struct{ BaseJRE } -// NewGraalVMJRE creates a new GraalVM JRE provider +// NewGraalVMJRE creates a new GraalVM JRE provider. func NewGraalVMJRE(ctx *common.Context) *GraalVMJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &GraalVMJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (g *GraalVMJRE) Name() string { - return "GraalVM" -} - -// Detect returns true if GraalVM JRE should be used -// GraalVM is selected via JBP_CONFIG_GRAAL_VM_JRE environment variable -func (g *GraalVMJRE) Detect() (bool, error) { - return DetectJREByEnv("graalvm"), nil -} - -// Supply installs the GraalVM JRE and its components -func (g *GraalVMJRE) Supply() error { - g.ctx.Log.BeginStep("Installing GraalVM JRE") - - // Determine version - dep, err := GetJREVersion(g.ctx, "graalvm") - if err != nil { - return fmt.Errorf("failed to determine GraalVM version from manifest: %w", err) - } - - g.version = dep.Version - g.ctx.Log.Info("Installing GraalVM (%s)", g.version) - - // Install JRE - if err := g.ctx.Installer.InstallDependency(dep, g.jreDir); err != nil { - return fmt.Errorf("failed to install GraalVM: %w (ensure repository_root is configured)", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := g.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - g.javaHome = javaHome - g.installedVersion = g.version - - // Write profile.d script for runtime environment - if err := g.writeProfileDScript(); err != nil { - g.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - g.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - g.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 17 // default for GraalVM - } - g.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent - g.jvmkill = NewJVMKillAgent(g.ctx, g.jreDir, g.version) - if err := g.jvmkill.Supply(); err != nil { - g.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - g.memoryCalc = NewMemoryCalculator(g.ctx, g.jreDir, g.version, javaMajorVersion, "graalvm") - if err := g.memoryCalc.Supply(); err != nil { - g.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - g.ctx.Log.Info("GraalVM JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -func (g *GraalVMJRE) Finalize() error { - g.ctx.Log.BeginStep("Finalizing GraalVM JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if g.javaHome == "" { - javaHome, err := g.findJavaHome() - if err != nil { - g.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - g.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if g.javaHome != "" { - if err := os.Setenv("JAVA_HOME", g.javaHome); err != nil { - g.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - g.ctx.Log.Debug("Set JAVA_HOME=%s", g.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 17 // default - if g.javaHome != "" { - if ver, err := common.DetermineJavaVersion(g.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - if g.jvmkill == nil { - g.jvmkill = NewJVMKillAgent(g.ctx, g.jreDir, g.version) - } - - // Finalize JVMKill agent - if err := g.jvmkill.Finalize(); err != nil { - g.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Reconstruct Memory Calculator component if not already set - if g.memoryCalc == nil { - g.memoryCalc = NewMemoryCalculator(g.ctx, g.jreDir, g.version, javaMajorVersion, "graalvm") - } - - // Finalize Memory Calculator - if err := g.memoryCalc.Finalize(); err != nil { - g.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - g.ctx.Log.Info("GraalVM JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (g *GraalVMJRE) JavaHome() string { - return g.javaHome -} - -// Version returns the installed JRE version -func (g *GraalVMJRE) Version() string { - return g.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -func (g *GraalVMJRE) MemoryCalculatorCommand() string { - if g.memoryCalc == nil { - return "" - } - return g.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// GraalVM tarballs usually extract to graalvm-* or jdk-* subdirectories -func (g *GraalVMJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(g.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for graalvm-*, jdk-*, or jre-* subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for GraalVM directory patterns - if len(name) > 7 && name[:7] == "graalvm" { - path := filepath.Join(g.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - // Also check for standard jdk/jre patterns - if len(name) > 3 && (name[:3] == "jdk" || name[:3] == "jre") { - path := filepath.Join(g.jreDir, name) - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(g.jreDir, "bin", "java")); err == nil { - return g.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", g.jreDir) -} - -// writeProfileDScript creates a profile.d script that exports JAVA_HOME, JRE_HOME, and PATH at runtime -// Delegates to the shared helper function in jre.go -func (g *GraalVMJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(g.ctx, g.jreDir, g.javaHome) + return &GraalVMJRE{newBaseJRE(ctx, "GraalVM", "graalvm", []string{"graalvm"}, nil, "(ensure repository_root is configured)")} } diff --git a/src/java/jres/ibm.go b/src/java/jres/ibm.go index a6fa1662e2..33f87a1065 100644 --- a/src/java/jres/ibm.go +++ b/src/java/jres/ibm.go @@ -1,223 +1,13 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// IBMJRE implements the JRE interface for IBM JRE -// IBM JRE requires a user-provided repository via JBP_CONFIG_IBM_JRE environment variable -// IBM JRE adds specific JVM options: -Xtune:virtualized -Xshareclasses:none -type IBMJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// IBMJRE implements the JRE interface for IBM JRE. +type IBMJRE struct{ BaseJRE } -// NewIBMJRE creates a new IBM JRE provider +// NewIBMJRE creates a new IBM JRE provider. func NewIBMJRE(ctx *common.Context) *IBMJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &IBMJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (i *IBMJRE) Name() string { - return "IBM JRE" -} - -// Detect returns true if IBM JRE should be used -// IBM JRE requires explicit configuration via JBP_CONFIG_IBM_JRE environment variable -func (i *IBMJRE) Detect() (bool, error) { - return DetectJREByEnv("ibm"), nil -} - -// Supply installs the IBM JRE and its components -func (i *IBMJRE) Supply() error { - i.ctx.Log.BeginStep("Installing IBM JRE") - - // Determine version - dep, err := GetJREVersion(i.ctx, "ibm") - if err != nil { - return fmt.Errorf("failed to determine IBM JRE version from manifest: %w", err) - } - - i.version = dep.Version - i.ctx.Log.Info("Installing IBM JRE (%s)", i.version) - - // Install JRE - if err := i.ctx.Installer.InstallDependency(dep, i.jreDir); err != nil { - return fmt.Errorf("failed to install IBM JRE: %w", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := i.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - i.javaHome = javaHome - i.installedVersion = i.version - - // Write profile.d script for runtime environment - if err := i.writeProfileDScript(); err != nil { - i.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - i.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - i.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 8 // IBM JRE default - } - i.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent (using IBM-specific repository if configured) - i.jvmkill = NewJVMKillAgent(i.ctx, i.jreDir, i.version) - if err := i.jvmkill.Supply(); err != nil { - i.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - i.memoryCalc = NewMemoryCalculator(i.ctx, i.jreDir, i.version, javaMajorVersion, "ibm") - if err := i.memoryCalc.Supply(); err != nil { - i.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - i.ctx.Log.Info("IBM JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -// Adds IBM-specific JVM options: -Xtune:virtualized -Xshareclasses:none -func (i *IBMJRE) Finalize() error { - i.ctx.Log.BeginStep("Finalizing IBM JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if i.javaHome == "" { - javaHome, err := i.findJavaHome() - if err != nil { - i.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - i.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if i.javaHome != "" { - if err := os.Setenv("JAVA_HOME", i.javaHome); err != nil { - i.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - i.ctx.Log.Debug("Set JAVA_HOME=%s", i.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 8 // IBM JRE default - if i.javaHome != "" { - if ver, err := common.DetermineJavaVersion(i.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - if i.jvmkill == nil { - i.jvmkill = NewJVMKillAgent(i.ctx, i.jreDir, i.version) - } - - // Finalize JVMKill agent - if err := i.jvmkill.Finalize(); err != nil { - i.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Reconstruct Memory Calculator component if not already set - if i.memoryCalc == nil { - i.memoryCalc = NewMemoryCalculator(i.ctx, i.jreDir, i.version, javaMajorVersion, "ibm") - } - - // Finalize Memory Calculator - if err := i.memoryCalc.Finalize(); err != nil { - i.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - // Add IBM-specific JVM options - // -Xtune:virtualized - Optimizes for virtualized environments - // -Xshareclasses:none - Disables class data sharing (not supported in containers) - ibmOpts := "-Xtune:virtualized -Xshareclasses:none" - if err := WriteJavaOpts(i.ctx, ibmOpts); err != nil { - i.ctx.Log.Warning("Failed to write IBM JVM options: %s", err.Error()) - // Non-fatal - } - - i.ctx.Log.Info("IBM JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (i *IBMJRE) JavaHome() string { - return i.javaHome -} - -// Version returns the installed JRE version -func (i *IBMJRE) Version() string { - return i.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -func (i *IBMJRE) MemoryCalculatorCommand() string { - if i.memoryCalc == nil { - return "" - } - return i.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// IBM JRE tarballs usually extract to ibm-java-* or jre subdirectories -func (i *IBMJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(i.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for ibm-java-* or jre subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for common IBM JRE directory patterns - if (len(name) > 8 && name[:8] == "ibm-java") || name == "jre" { - path := filepath.Join(i.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(i.jreDir, "bin", "java")); err == nil { - return i.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", i.jreDir) -} - -// writeProfileDScript creates the profile.d script for setting JAVA_HOME, JRE_HOME, and PATH at runtime -func (i *IBMJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(i.ctx, i.jreDir, i.javaHome) + b := newBaseJRE(ctx, "IBM JRE", "ibm", []string{"ibm-java"}, []string{"jre"}, "") + b.extraFinalizeOpts = func() string { return "-Xtune:virtualized -Xshareclasses:none" } + return &IBMJRE{b} } diff --git a/src/java/jres/jre_test.go b/src/java/jres/jre_test.go index 784469bfe6..01148477f0 100644 --- a/src/java/jres/jre_test.go +++ b/src/java/jres/jre_test.go @@ -571,10 +571,10 @@ IMPLEMENTOR="Eclipse Adoptium"` Expect(version).To(Equal(21)) }) - It("defaults to 17 when release file is missing", func() { - version, err := common.DetermineJavaVersion(javaHome) - Expect(err).NotTo(HaveOccurred()) - Expect(version).To(Equal(17)) + It("returns an error when release file is missing", func() { + _, err := common.DetermineJavaVersion(javaHome) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("release file")) }) }) diff --git a/src/java/jres/openjdk.go b/src/java/jres/openjdk.go index 5299d7261f..b610876d5d 100644 --- a/src/java/jres/openjdk.go +++ b/src/java/jres/openjdk.go @@ -1,254 +1,11 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" - "strings" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// OpenJDKJRE implements the JRE interface for OpenJDK -type OpenJDKJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// OpenJDKJRE implements the JRE interface for OpenJDK. +type OpenJDKJRE struct{ BaseJRE } -// NewOpenJDKJRE creates a new OpenJDK JRE provider +// NewOpenJDKJRE creates a new OpenJDK JRE provider. func NewOpenJDKJRE(ctx *common.Context) *OpenJDKJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &OpenJDKJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (o *OpenJDKJRE) Name() string { - return "OpenJDK" -} - -// Detect returns true if OpenJDK should be used -// OpenJDK is selected via JBP_CONFIG_OPEN_JDK_JRE environment variable -func (o *OpenJDKJRE) Detect() (bool, error) { - return DetectJREByEnv("openjdk"), nil -} - -// Supply installs the OpenJDK JRE and its components -func (o *OpenJDKJRE) Supply() error { - o.ctx.Log.BeginStep("Installing OpenJDK JRE") - - // Determine version - dep, err := GetJREVersion(o.ctx, "openjdk") - if err != nil { - return fmt.Errorf("failed to determine OpenJDK version from manifest: %w", err) - } - - o.version = dep.Version - o.ctx.Log.Info("Installing OpenJDK (%s)", o.version) - - // Install JRE - if err := o.ctx.Installer.InstallDependency(dep, o.jreDir); err != nil { - return fmt.Errorf("failed to install OpenJDK: %w", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := o.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - o.javaHome = javaHome - o.installedVersion = o.version - - // Create profile.d script to export JAVA_HOME at runtime - // This is needed for containers like DistZip that use startup scripts expecting $JAVA_HOME - if err := o.writeProfileDScript(); err != nil { - o.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - o.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Write JAVA_HOME to the deps env dir so subsequent buildpacks can use it. - // This is the multi-buildpack pattern used by go-buildpack (WriteEnvFile for GOROOT) - // and dotnet-core-buildpack. - if err := o.ctx.Stager.WriteEnvFile("JAVA_HOME", javaHome); err != nil { - o.ctx.Log.Warning("Could not write JAVA_HOME env file: %s", err.Error()) - } - - // Symlink the java binary into the shared bin directory so it is on PATH - // for subsequent buildpacks — mirrors go-buildpack's AddBinDependencyLink for "go". - javaBin := filepath.Join(javaHome, "bin", "java") - if err := o.ctx.Stager.AddBinDependencyLink(javaBin, "java"); err != nil { - o.ctx.Log.Warning("Could not add java bin dependency link: %s", err.Error()) - } - - // Link the JRE lib directory into the deps dir so native libraries (.so files) - // are included on LD_LIBRARY_PATH — mirrors dotnet-core-buildpack's LinkDirectoryInDepDir. - libDir := filepath.Join(javaHome, "lib") - if _, err := os.Stat(libDir); err == nil { - if err := o.ctx.Stager.LinkDirectoryInDepDir(libDir, "lib"); err != nil { - o.ctx.Log.Warning("Could not link JRE lib directory: %s", err.Error()) - } - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - o.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 17 // default - } - o.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent - o.jvmkill = NewJVMKillAgent(o.ctx, o.jreDir, o.version) - if err := o.jvmkill.Supply(); err != nil { - o.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - o.memoryCalc = NewMemoryCalculator(o.ctx, o.jreDir, o.version, javaMajorVersion, "openjdk") - if err := o.memoryCalc.Supply(); err != nil { - o.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - o.ctx.Log.Debug("OpenJDK JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -func (o *OpenJDKJRE) Finalize() error { - o.ctx.Log.Debug("Finalizing OpenJDK JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if o.javaHome == "" { - javaHome, err := o.findJavaHome() - if err != nil { - o.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - o.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if o.javaHome != "" { - if err := os.Setenv("JAVA_HOME", o.javaHome); err != nil { - o.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - o.ctx.Log.Debug("Set JAVA_HOME=%s", o.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 17 // default - if o.javaHome != "" { - if ver, err := common.DetermineJavaVersion(o.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - // This handles the case where finalize is called on a fresh instance - if o.jvmkill == nil { - o.jvmkill = NewJVMKillAgent(o.ctx, o.jreDir, o.version) - } - - // Finalize JVMKill agent - if err := o.jvmkill.Finalize(); err != nil { - o.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Add base JAVA_OPTS for compatibility with Ruby buildpack - // These are standard JVM options that should be set for all OpenJDK-like JREs - baseOpts := []string{ - "-Djava.io.tmpdir=$TMPDIR", // Temp directory - "-XX:ActiveProcessorCount=$(nproc)", // CPU count - } - - // Note: We do NOT set -Djava.ext.dirs= here because frameworks like Container Security Provider - // need to set their own java.ext.dirs values. The Ruby buildpack delegates this to the - // JavaSecurity framework which collects all extension directories from contributing frameworks. - - if err := WriteJavaOpts(o.ctx, strings.Join(baseOpts, " ")); err != nil { - o.ctx.Log.Warning("Failed to write base JAVA_OPTS: %s", err.Error()) - } - - // Reconstruct Memory Calculator component if not already set - if o.memoryCalc == nil { - o.memoryCalc = NewMemoryCalculator(o.ctx, o.jreDir, o.version, javaMajorVersion, "openjdk") - } - - // Finalize Memory Calculator - if err := o.memoryCalc.Finalize(); err != nil { - o.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - o.ctx.Log.Debug("OpenJDK JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (o *OpenJDKJRE) JavaHome() string { - return o.javaHome -} - -// Version returns the installed JRE version -func (o *OpenJDKJRE) Version() string { - return o.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -// This is prepended to the container startup command to calculate optimal JVM memory settings -func (o *OpenJDKJRE) MemoryCalculatorCommand() string { - if o.memoryCalc == nil { - return "" - } - return o.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// OpenJDK tarballs usually extract to jdk-* or jre-* subdirectories -func (o *OpenJDKJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(o.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for jdk-* or jre-* subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for common OpenJDK directory patterns - if len(name) > 3 && (name[:3] == "jdk" || name[:3] == "jre") { - path := filepath.Join(o.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(o.jreDir, "bin", "java")); err == nil { - return o.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", o.jreDir) -} - -// writeProfileDScript creates a profile.d script that exports JAVA_HOME, JRE_HOME, and PATH at runtime -// Delegates to the shared helper function in jre.go -func (o *OpenJDKJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(o.ctx, o.jreDir, o.javaHome) + return &OpenJDKJRE{newBaseJRE(ctx, "OpenJDK", "openjdk", []string{"jdk", "jre"}, nil, "")} } diff --git a/src/java/jres/oracle.go b/src/java/jres/oracle.go index f578f3b1b5..9cc8d5f0e9 100644 --- a/src/java/jres/oracle.go +++ b/src/java/jres/oracle.go @@ -1,212 +1,11 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// OracleJRE implements the JRE interface for Oracle JRE -// Oracle JRE requires a user-provided repository via JBP_CONFIG_ORACLE_JRE environment variable -type OracleJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// OracleJRE implements the JRE interface for Oracle JRE. +type OracleJRE struct{ BaseJRE } -// NewOracleJRE creates a new Oracle JRE provider +// NewOracleJRE creates a new Oracle JRE provider. func NewOracleJRE(ctx *common.Context) *OracleJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &OracleJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (o *OracleJRE) Name() string { - return "Oracle JRE" -} - -// Detect returns true if Oracle JRE should be used -// Oracle JRE requires explicit configuration via JBP_CONFIG_ORACLE_JRE environment variable -func (o *OracleJRE) Detect() (bool, error) { - return DetectJREByEnv("oracle"), nil -} - -// Supply installs the Oracle JRE and its components -func (o *OracleJRE) Supply() error { - o.ctx.Log.BeginStep("Installing Oracle JRE") - - // Determine version - dep, err := GetJREVersion(o.ctx, "oracle") - if err != nil { - return fmt.Errorf("failed to determine Oracle JRE version from manifest: %w", err) - } - - o.version = dep.Version - o.ctx.Log.Info("Installing Oracle JRE (%s)", o.version) - - // Install JRE - if err := o.ctx.Installer.InstallDependency(dep, o.jreDir); err != nil { - return fmt.Errorf("failed to install Oracle JRE: %w", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := o.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - o.javaHome = javaHome - o.installedVersion = o.version - - // Write profile.d script for runtime environment - if err := o.writeProfileDScript(); err != nil { - o.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - o.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - o.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 17 // default - } - o.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent - o.jvmkill = NewJVMKillAgent(o.ctx, o.jreDir, o.version) - if err := o.jvmkill.Supply(); err != nil { - o.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - o.memoryCalc = NewMemoryCalculator(o.ctx, o.jreDir, o.version, javaMajorVersion, "oracle") - if err := o.memoryCalc.Supply(); err != nil { - o.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - o.ctx.Log.Info("Oracle JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -func (o *OracleJRE) Finalize() error { - o.ctx.Log.BeginStep("Finalizing Oracle JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if o.javaHome == "" { - javaHome, err := o.findJavaHome() - if err != nil { - o.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - o.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if o.javaHome != "" { - if err := os.Setenv("JAVA_HOME", o.javaHome); err != nil { - o.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - o.ctx.Log.Debug("Set JAVA_HOME=%s", o.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 17 // default - if o.javaHome != "" { - if ver, err := common.DetermineJavaVersion(o.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - if o.jvmkill == nil { - o.jvmkill = NewJVMKillAgent(o.ctx, o.jreDir, o.version) - } - - // Finalize JVMKill agent - if err := o.jvmkill.Finalize(); err != nil { - o.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Reconstruct Memory Calculator component if not already set - if o.memoryCalc == nil { - o.memoryCalc = NewMemoryCalculator(o.ctx, o.jreDir, o.version, javaMajorVersion, "oracle") - } - - // Finalize Memory Calculator - if err := o.memoryCalc.Finalize(); err != nil { - o.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - o.ctx.Log.Info("Oracle JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (o *OracleJRE) JavaHome() string { - return o.javaHome -} - -// Version returns the installed JRE version -func (o *OracleJRE) Version() string { - return o.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -func (o *OracleJRE) MemoryCalculatorCommand() string { - if o.memoryCalc == nil { - return "" - } - return o.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// Oracle JRE tarballs usually extract to jdk-* or jre-* subdirectories -func (o *OracleJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(o.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for jdk-* or jre-* subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for common Oracle JRE directory patterns - if len(name) > 3 && (name[:3] == "jdk" || name[:3] == "jre") { - path := filepath.Join(o.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(o.jreDir, "bin", "java")); err == nil { - return o.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", o.jreDir) -} - -// writeProfileDScript creates the profile.d script for setting JAVA_HOME, JRE_HOME, and PATH at runtime -func (o *OracleJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(o.ctx, o.jreDir, o.javaHome) + return &OracleJRE{newBaseJRE(ctx, "Oracle JRE", "oracle", []string{"jdk", "jre"}, nil, "")} } diff --git a/src/java/jres/sapmachine.go b/src/java/jres/sapmachine.go index 1391d150e0..2f5a10df33 100644 --- a/src/java/jres/sapmachine.go +++ b/src/java/jres/sapmachine.go @@ -1,219 +1,11 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// SapMachineJRE implements the JRE interface for SAP Machine OpenJDK -type SapMachineJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// SapMachineJRE implements the JRE interface for SAP Machine OpenJDK. +type SapMachineJRE struct{ BaseJRE } -// NewSapMachineJRE creates a new SAP Machine JRE provider +// NewSapMachineJRE creates a new SAP Machine JRE provider. func NewSapMachineJRE(ctx *common.Context) *SapMachineJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &SapMachineJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (s *SapMachineJRE) Name() string { - return "SapMachine" -} - -// Detect returns true if SAP Machine JRE should be used -// SAP Machine is selected via JBP_CONFIG_SAP_MACHINE_JRE environment variable -func (s *SapMachineJRE) Detect() (bool, error) { - return DetectJREByEnv("sapmachine"), nil -} - -// Supply installs the SAP Machine JRE and its components -func (s *SapMachineJRE) Supply() error { - s.ctx.Log.BeginStep("Installing SAP Machine JRE") - - // Determine version - dep, err := GetJREVersion(s.ctx, "sapmachine") - if err != nil { - return fmt.Errorf("failed to determine SAP Machine version from manifest: %w", err) - } - - s.version = dep.Version - s.ctx.Log.Info("Installing SAP Machine (%s)", s.version) - - // Install JRE - if err := s.ctx.Installer.InstallDependency(dep, s.jreDir); err != nil { - return fmt.Errorf("failed to install SAP Machine: %w", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := s.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - s.javaHome = javaHome - s.installedVersion = s.version - - // Write profile.d script for runtime environment - if err := s.writeProfileDScript(); err != nil { - s.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - s.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - s.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 17 // default for SAP Machine - } - s.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent - s.jvmkill = NewJVMKillAgent(s.ctx, s.jreDir, s.version) - if err := s.jvmkill.Supply(); err != nil { - s.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - s.memoryCalc = NewMemoryCalculator(s.ctx, s.jreDir, s.version, javaMajorVersion, "sapmachine") - if err := s.memoryCalc.Supply(); err != nil { - s.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - s.ctx.Log.Info("SAP Machine JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -func (s *SapMachineJRE) Finalize() error { - s.ctx.Log.BeginStep("Finalizing SAP Machine JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if s.javaHome == "" { - javaHome, err := s.findJavaHome() - if err != nil { - s.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - s.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if s.javaHome != "" { - if err := os.Setenv("JAVA_HOME", s.javaHome); err != nil { - s.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - s.ctx.Log.Debug("Set JAVA_HOME=%s", s.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 17 // default - if s.javaHome != "" { - if ver, err := common.DetermineJavaVersion(s.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - if s.jvmkill == nil { - s.jvmkill = NewJVMKillAgent(s.ctx, s.jreDir, s.version) - } - - // Finalize JVMKill agent - if err := s.jvmkill.Finalize(); err != nil { - s.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Reconstruct Memory Calculator component if not already set - if s.memoryCalc == nil { - s.memoryCalc = NewMemoryCalculator(s.ctx, s.jreDir, s.version, javaMajorVersion, "sapmachine") - } - - // Finalize Memory Calculator - if err := s.memoryCalc.Finalize(); err != nil { - s.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - s.ctx.Log.Info("SAP Machine JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (s *SapMachineJRE) JavaHome() string { - return s.javaHome -} - -// Version returns the installed JRE version -func (s *SapMachineJRE) Version() string { - return s.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -func (s *SapMachineJRE) MemoryCalculatorCommand() string { - if s.memoryCalc == nil { - return "" - } - return s.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// SAP Machine tarballs usually extract to sapmachine-* or jdk-* subdirectories -func (s *SapMachineJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(s.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for sapmachine-*, jdk-*, or jre-* subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for SAP Machine directory patterns - if len(name) > 10 && name[:10] == "sapmachine" { - path := filepath.Join(s.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - // Also check for standard jdk/jre patterns - if len(name) > 3 && (name[:3] == "jdk" || name[:3] == "jre") { - path := filepath.Join(s.jreDir, name) - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(s.jreDir, "bin", "java")); err == nil { - return s.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", s.jreDir) -} - -// writeProfileDScript creates a profile.d script that exports JAVA_HOME, JRE_HOME, and PATH at runtime -// Delegates to the shared helper function in jre.go -func (s *SapMachineJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(s.ctx, s.jreDir, s.javaHome) + return &SapMachineJRE{newBaseJRE(ctx, "SapMachine", "sapmachine", []string{"sapmachine"}, nil, "")} } diff --git a/src/java/jres/standard_jres_test.go b/src/java/jres/standard_jres_test.go new file mode 100644 index 0000000000..b5e8ddfb5c --- /dev/null +++ b/src/java/jres/standard_jres_test.go @@ -0,0 +1,274 @@ +package jres_test + +import ( + "bytes" + "os" + "path/filepath" + + "github.com/cloudfoundry/java-buildpack/src/java/common" + "github.com/cloudfoundry/java-buildpack/src/java/jres" + "github.com/cloudfoundry/libbuildpack" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Standard JREs", func() { + testCases := []struct { + description string + displayName string + envVar string + otherEnvVar string + subdir string + version string + newJRE func(*common.Context) jres.JRE + }{ + { + description: "Zulu", + displayName: "Zulu", + envVar: "JBP_CONFIG_ZULU_JRE", + otherEnvVar: "JBP_CONFIG_OPEN_JDK_JRE", + subdir: "zulu-17.0.13", + version: "17.0.13", + newJRE: func(ctx *common.Context) jres.JRE { + return jres.NewZuluJRE(ctx) + }, + }, + { + description: "SapMachine", + displayName: "SapMachine", + envVar: "JBP_CONFIG_SAP_MACHINE_JRE", + otherEnvVar: "JBP_CONFIG_ZULU_JRE", + subdir: "sapmachine-17.0.13", + version: "17.0.13", + newJRE: func(ctx *common.Context) jres.JRE { + return jres.NewSapMachineJRE(ctx) + }, + }, + { + description: "IBM", + displayName: "IBM JRE", + envVar: "JBP_CONFIG_IBM_JRE", + otherEnvVar: "JBP_CONFIG_ORACLE_JRE", + subdir: "ibm-java-8.0", + version: "1.8.0_422", + newJRE: func(ctx *common.Context) jres.JRE { + return jres.NewIBMJRE(ctx) + }, + }, + { + description: "Oracle", + displayName: "Oracle JRE", + envVar: "JBP_CONFIG_ORACLE_JRE", + otherEnvVar: "JBP_CONFIG_GRAAL_VM_JRE", + subdir: "jdk-17.0.13", + version: "17.0.13", + newJRE: func(ctx *common.Context) jres.JRE { + return jres.NewOracleJRE(ctx) + }, + }, + { + description: "GraalVM", + displayName: "GraalVM", + envVar: "JBP_CONFIG_GRAAL_VM_JRE", + otherEnvVar: "JBP_CONFIG_SAP_MACHINE_JRE", + subdir: "graalvm-21.0.1", + version: "21.0.1", + newJRE: func(ctx *common.Context) jres.JRE { + return jres.NewGraalVMJRE(ctx) + }, + }, + } + + for _, tc := range testCases { + tc := tc + + Describe(tc.description, func() { + var ( + ctx *common.Context + jre jres.JRE + cleanup func() + ) + + BeforeEach(func() { + ctx, cleanup = makeTestContext() + jre = tc.newJRE(ctx) + clearJREEnvVars() + DeferCleanup(cleanup) + DeferCleanup(clearJREEnvVars) + DeferCleanup(os.Unsetenv, "JAVA_HOME") + }) + + Describe("Name", func() { + It("returns the expected display name", func() { + Expect(jre.Name()).To(Equal(tc.displayName)) + }) + }) + + Describe("Detect", func() { + It("returns true when the correct environment variable is set", func() { + Expect(os.Setenv(tc.envVar, "{jre: {version: 17.+}}")).To(Succeed()) + + detected, err := jre.Detect() + Expect(err).NotTo(HaveOccurred()) + Expect(detected).To(BeTrue()) + }) + + It("returns false when no environment variable is set", func() { + detected, err := jre.Detect() + Expect(err).NotTo(HaveOccurred()) + Expect(detected).To(BeFalse()) + }) + + It("returns false when a different JRE environment variable is set", func() { + Expect(os.Setenv(tc.otherEnvVar, "{jre: {version: 17.+}}")).To(Succeed()) + + detected, err := jre.Detect() + Expect(err).NotTo(HaveOccurred()) + Expect(detected).To(BeFalse()) + }) + }) + + Describe("Version", func() { + It("returns empty before installation", func() { + Expect(jre.Version()).To(BeEmpty()) + }) + }) + + Describe("JavaHome", func() { + It("returns empty before installation", func() { + Expect(jre.JavaHome()).To(BeEmpty()) + }) + }) + + Describe("Finalize", func() { + It("sets JAVA_HOME using the JRE-specific directory prefix", func() { + expectedJavaHome := createFakeJRE(ctx, tc.subdir, tc.version) + + Expect(jre.Finalize()).To(Succeed()) + Expect(os.Getenv("JAVA_HOME")).To(Equal(expectedJavaHome)) + Expect(jre.JavaHome()).To(Equal(expectedJavaHome)) + }) + }) + }) + } + + Describe("IBM", func() { + var ( + ctx *common.Context + ibmJRE jres.JRE + cleanup func() + ) + + BeforeEach(func() { + ctx, cleanup = makeTestContext() + ibmJRE = jres.NewIBMJRE(ctx) + clearJREEnvVars() + DeferCleanup(cleanup) + DeferCleanup(clearJREEnvVars) + DeferCleanup(os.Unsetenv, "JAVA_HOME") + }) + + Describe("Finalize", func() { + It("sets JAVA_HOME when the extracted directory is exactly jre", func() { + expectedJavaHome := createFakeJRE(ctx, "jre", "1.8.0_422") + + Expect(ibmJRE.Finalize()).To(Succeed()) + Expect(os.Getenv("JAVA_HOME")).To(Equal(expectedJavaHome)) + Expect(ibmJRE.JavaHome()).To(Equal(expectedJavaHome)) + }) + }) + }) +}) + +var _ = Describe("BaseJRE Java version detection fallback", func() { + It("logs a warning when DetermineJavaVersion fails in Finalize and falls back to 17", func() { + var buf bytes.Buffer + ctx, cleanup := makeTestContextWithBuffer(&buf) + defer cleanup() + + // Create a jre dir with a valid java binary but no release file, + // so DetermineJavaVersion returns an error and Finalize falls back to Java 17. + jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") + Expect(os.MkdirAll(filepath.Join(jreDir, "bin"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(jreDir, "bin", "java"), []byte("#!/bin/sh\n"), 0755)).To(Succeed()) + + jre := jres.NewOpenJDKJRE(ctx) + err := jre.Finalize() + Expect(err).NotTo(HaveOccurred()) + + Expect(buf.String()).To(ContainSubstring("WARNING")) + Expect(buf.String()).To(ContainSubstring("defaulting to 17")) + }) +}) + +func makeTestContext() (*common.Context, func()) { + return makeTestContextWithBuffer(os.Stdout) +} + +func makeTestContextWithBuffer(w interface{ Write([]byte) (int, error) }) (*common.Context, func()) { + buildDir, err := os.MkdirTemp("", "build") + Expect(err).NotTo(HaveOccurred()) + + depsDir, err := os.MkdirTemp("", "deps") + Expect(err).NotTo(HaveOccurred()) + + cacheDir, err := os.MkdirTemp("", "cache") + Expect(err).NotTo(HaveOccurred()) + + Expect(os.MkdirAll(filepath.Join(depsDir, "0"), 0755)).To(Succeed()) + + logger := libbuildpack.NewLogger(w) + manifest := &libbuildpack.Manifest{} + installer := &libbuildpack.Installer{} + stager := libbuildpack.NewStager([]string{buildDir, cacheDir, depsDir, "0"}, logger, manifest) + command := &libbuildpack.Command{} + + ctx := &common.Context{ + Stager: stager, + Manifest: manifest, + Installer: installer, + Log: logger, + Command: command, + } + + cleanup := func() { + os.RemoveAll(buildDir) + os.RemoveAll(depsDir) + os.RemoveAll(cacheDir) + } + + return ctx, cleanup +} + +func createFakeJRE(ctx *common.Context, subdir, version string) string { + jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") + javaHome := jreDir + if subdir != "" { + javaHome = filepath.Join(jreDir, subdir) + } + + Expect(os.MkdirAll(filepath.Join(javaHome, "bin"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(javaHome, "bin", "java"), []byte("#!/bin/sh\necho 'java version \""+version+"\"'\n"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(javaHome, "release"), []byte("JAVA_VERSION=\""+version+"\"\n"), 0644)).To(Succeed()) + + return javaHome +} + +func clearJREEnvVars() { + for _, envVar := range []string{ + "JBP_CONFIG_OPENJDK", + "JBP_CONFIG_OPEN_JDK_JRE", + "JBP_CONFIG_ZULU", + "JBP_CONFIG_ZULU_JRE", + "JBP_CONFIG_SAPMACHINE", + "JBP_CONFIG_SAP_MACHINE_JRE", + "JBP_CONFIG_IBM", + "JBP_CONFIG_IBM_JRE", + "JBP_CONFIG_ORACLE", + "JBP_CONFIG_ORACLE_JRE", + "JBP_CONFIG_GRAALVM", + "JBP_CONFIG_GRAAL_VM_JRE", + } { + os.Unsetenv(envVar) + } +} diff --git a/src/java/jres/zulu.go b/src/java/jres/zulu.go index 60ced44c66..144c9d1e19 100644 --- a/src/java/jres/zulu.go +++ b/src/java/jres/zulu.go @@ -1,219 +1,11 @@ package jres -import ( - "fmt" - "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" - "path/filepath" -) +import "github.com/cloudfoundry/java-buildpack/src/java/common" -// ZuluJRE implements the JRE interface for Azul Zulu OpenJDK -type ZuluJRE struct { - ctx *common.Context - jreDir string - version string - javaHome string - memoryCalc *MemoryCalculator - jvmkill *JVMKillAgent - installedVersion string -} +// ZuluJRE implements the JRE interface for Azul Zulu OpenJDK. +type ZuluJRE struct{ BaseJRE } -// NewZuluJRE creates a new Zulu JRE provider +// NewZuluJRE creates a new Zulu JRE provider. func NewZuluJRE(ctx *common.Context) *ZuluJRE { - jreDir := filepath.Join(ctx.Stager.DepDir(), "jre") - - return &ZuluJRE{ - ctx: ctx, - jreDir: jreDir, - } -} - -// Name returns the name of this JRE provider -func (z *ZuluJRE) Name() string { - return "Zulu" -} - -// Detect returns true if Zulu JRE should be used -// Zulu is selected via JBP_CONFIG_ZULU_JRE environment variable -func (z *ZuluJRE) Detect() (bool, error) { - return DetectJREByEnv("zulu"), nil -} - -// Supply installs the Zulu JRE and its components -func (z *ZuluJRE) Supply() error { - z.ctx.Log.BeginStep("Installing Zulu JRE") - - // Determine version - dep, err := GetJREVersion(z.ctx, "zulu") - if err != nil { - return fmt.Errorf("failed to determine Zulu version from manifest: %w", err) - } - - z.version = dep.Version - z.ctx.Log.Info("Installing Zulu (%s)", z.version) - - // Install JRE - if err := z.ctx.Installer.InstallDependency(dep, z.jreDir); err != nil { - return fmt.Errorf("failed to install Zulu: %w", err) - } - - // Find the actual JAVA_HOME (handle nested directories from tar extraction) - javaHome, err := z.findJavaHome() - if err != nil { - return fmt.Errorf("failed to find JAVA_HOME: %w", err) - } - z.javaHome = javaHome - z.installedVersion = z.version - - // Set up JAVA_HOME environment - if err := z.writeProfileDScript(); err != nil { - z.ctx.Log.Warning("Could not write java.sh profile.d script: %s", err.Error()) - } else { - z.ctx.Log.Debug("Created profile.d script: java.sh") - } - - // Determine Java major version - javaMajorVersion, err := common.DetermineJavaVersion(javaHome) - if err != nil { - z.ctx.Log.Warning("Could not determine Java version: %s", err.Error()) - javaMajorVersion = 11 // default for Zulu - } - z.ctx.Log.Info("Detected Java major version: %d", javaMajorVersion) - - // Install JVMKill agent - z.jvmkill = NewJVMKillAgent(z.ctx, z.jreDir, z.version) - if err := z.jvmkill.Supply(); err != nil { - z.ctx.Log.Warning("Failed to install JVMKill agent: %s (continuing)", err.Error()) - // Non-fatal - continue without jvmkill - } - - // Install Memory Calculator - z.memoryCalc = NewMemoryCalculator(z.ctx, z.jreDir, z.version, javaMajorVersion, "zulu") - if err := z.memoryCalc.Supply(); err != nil { - z.ctx.Log.Warning("Failed to install Memory Calculator: %s (continuing)", err.Error()) - // Non-fatal - continue without memory calculator - } - - z.ctx.Log.Info("Zulu JRE installation complete") - return nil -} - -// Finalize performs final JRE configuration -func (z *ZuluJRE) Finalize() error { - z.ctx.Log.BeginStep("Finalizing Zulu JRE configuration") - - // Find the actual JAVA_HOME (needed if finalize is called on a fresh instance) - if z.javaHome == "" { - javaHome, err := z.findJavaHome() - if err != nil { - z.ctx.Log.Warning("Failed to find JAVA_HOME: %s", err.Error()) - } else { - z.javaHome = javaHome - } - } - - // Set JAVA_HOME in environment for frameworks that need it during finalize phase - // (e.g., Luna Security Provider, Container Security Provider) - if z.javaHome != "" { - if err := os.Setenv("JAVA_HOME", z.javaHome); err != nil { - z.ctx.Log.Warning("Failed to set JAVA_HOME environment variable: %s", err.Error()) - } else { - z.ctx.Log.Debug("Set JAVA_HOME=%s", z.javaHome) - } - } - - // Determine Java major version for memory calculator - javaMajorVersion := 11 // default - if z.javaHome != "" { - if ver, err := common.DetermineJavaVersion(z.javaHome); err == nil { - javaMajorVersion = ver - } - } - - // Reconstruct JVMKill agent component if not already set - if z.jvmkill == nil { - z.jvmkill = NewJVMKillAgent(z.ctx, z.jreDir, z.version) - } - - // Finalize JVMKill agent - if err := z.jvmkill.Finalize(); err != nil { - z.ctx.Log.Warning("Failed to finalize JVMKill agent: %s", err.Error()) - // Non-fatal - } - - // Reconstruct Memory Calculator component if not already set - if z.memoryCalc == nil { - z.memoryCalc = NewMemoryCalculator(z.ctx, z.jreDir, z.version, javaMajorVersion, "zulu") - } - - // Finalize Memory Calculator - if err := z.memoryCalc.Finalize(); err != nil { - z.ctx.Log.Warning("Failed to finalize Memory Calculator: %s", err.Error()) - // Non-fatal - } - - z.ctx.Log.Info("Zulu JRE finalization complete") - return nil -} - -// JavaHome returns the path to JAVA_HOME -func (z *ZuluJRE) JavaHome() string { - return z.javaHome -} - -// Version returns the installed JRE version -func (z *ZuluJRE) Version() string { - return z.installedVersion -} - -// MemoryCalculatorCommand returns the shell command snippet to run memory calculator at runtime -func (z *ZuluJRE) MemoryCalculatorCommand() string { - if z.memoryCalc == nil { - return "" - } - return z.memoryCalc.GetCalculatorCommand() -} - -// findJavaHome locates the actual JAVA_HOME directory after extraction -// Zulu tarballs usually extract to zulu-* subdirectories -func (z *ZuluJRE) findJavaHome() (string, error) { - entries, err := os.ReadDir(z.jreDir) - if err != nil { - return "", fmt.Errorf("failed to read JRE directory: %w", err) - } - - // Look for zulu-*, jdk-*, or jre-* subdirectory - for _, entry := range entries { - if entry.IsDir() { - name := entry.Name() - // Check for common Zulu directory patterns - if len(name) > 4 && name[:4] == "zulu" { - path := filepath.Join(z.jreDir, name) - // Verify it has a bin directory with java - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - // Also check for standard jdk/jre patterns - if len(name) > 3 && (name[:3] == "jdk" || name[:3] == "jre") { - path := filepath.Join(z.jreDir, name) - if _, err := os.Stat(filepath.Join(path, "bin", "java")); err == nil { - return path, nil - } - } - } - } - - // If no subdirectory found, check if jreDir itself is valid - if _, err := os.Stat(filepath.Join(z.jreDir, "bin", "java")); err == nil { - return z.jreDir, nil - } - - return "", fmt.Errorf("could not find valid JAVA_HOME in %s", z.jreDir) -} - -// writeProfileDScript creates a profile.d script that exports JAVA_HOME, JRE_HOME, and PATH at runtime -// Delegates to the shared helper function in jre.go -func (z *ZuluJRE) writeProfileDScript() error { - return WriteJavaHomeProfileD(z.ctx, z.jreDir, z.javaHome) + return &ZuluJRE{newBaseJRE(ctx, "Zulu", "zulu", []string{"zulu"}, nil, "")} }