Skip to content

Commit

Permalink
Merge pull request #178 from paketo-buildpacks/build-logic
Browse files Browse the repository at this point in the history
Support for jlink to create custom JRE & reorg of build logic
  • Loading branch information
pivotal-david-osullivan authored Jun 24, 2022
2 parents 1010cea + fde497a commit 0a9ff7a
Show file tree
Hide file tree
Showing 6 changed files with 550 additions and 94 deletions.
243 changes: 158 additions & 85 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package libjvm

import (
"fmt"
"github.com/mattn/go-shellwords"
"github.com/paketo-buildpacks/libpak/effect"
"strings"

"github.com/buildpacks/libcnb"
Expand All @@ -27,149 +29,220 @@ import (
)

type Build struct {
Logger bard.Logger
Logger bard.Logger
Result libcnb.BuildResult
CertLoader CertificateLoader
DependencyCache libpak.DependencyCache
}

func NewBuild(logger bard.Logger) Build {
cl := NewCertificateLoader()
cl.Logger = logger.BodyWriter()

return Build{
Logger: logger,
Result: libcnb.NewBuildResult(),
CertLoader: cl,
}
}

func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
var jdkRequired, jreRequired, jreMissing, jreSkipped, jLinkEnabled bool

pr := libpak.PlanEntryResolver{Plan: context.Plan}

_, jdkRequired, err := pr.Resolve("jdk")
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve jdk plan entry\n%w", err)
}

jrePlanEntry, jreRequired, err := pr.Resolve("jre")
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve jre plan entry\n%w", err)
}

if !jdkRequired && !jreRequired {
return b.Result, nil
}
b.Logger.Title(context.Buildpack)
result := libcnb.NewBuildResult()

cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err)
}

pr := libpak.PlanEntryResolver{Plan: context.Plan}
jvmVersion := NewJVMVersion(b.Logger)
v, err := jvmVersion.GetJVMVersion(context.Application.Path, cr)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to determine jvm version\n%w", err)
}

dr, err := libpak.NewDependencyResolver(context)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency resolver\n%w", err)
}

dc, err := libpak.NewDependencyCache(context)
b.DependencyCache, err = libpak.NewDependencyCache(context)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to create dependency cache\n%w", err)
}
dc.Logger = b.Logger
b.DependencyCache.Logger = b.Logger

cl := NewCertificateLoader()
cl.Logger = b.Logger.BodyWriter()
depJDK, err := dr.Resolve("jdk", v)
if jdkRequired && err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
}

jvmVersion := NewJVMVersion(b.Logger)
v, err := jvmVersion.GetJVMVersion(context.Application.Path, cr)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to determine jvm version\n%w", err)
jreMissing = false
depJRE, err := dr.Resolve("jre", v)
if libpak.IsNoValidDependencies(err) {
jreMissing = true
}

jreSkipped := false
if t, _ := cr.Resolve("BP_JVM_TYPE"); strings.ToLower(t) == "jdk" {
jreSkipped = true
}

_, jdkRequired, err := pr.Resolve("jdk")
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve jdk plan entry\n%w", err)
if jl := cr.ResolveBool("BP_JVM_JLINK_ENABLED"); jl {
jLinkEnabled = true
}

jrePlanEntry, jreRequired, err := pr.Resolve("jre")
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to resolve jre plan entry\n%w", err)
// jLink
if jLinkEnabled {
if IsBeforeJava9(v) {
return libcnb.BuildResult{}, fmt.Errorf("unable to build, jlink is compatible with Java 9+ only\n")
}
if err = b.contributeJDK(depJDK); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to contribute JDK for Jlink\n%w", err)
}
if err = b.contributeJLink(cr, jrePlanEntry.Metadata, context.Application.Path, depJDK); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to contribute Jlink\n%w", err)
}
b.contributeHelpers(context, depJDK)
return b.Result, nil
}

jreAvailable := jreRequired
if jreRequired {
_, err := dr.Resolve("jre", v)
if libpak.IsNoValidDependencies(err) {
jreAvailable = false
// use JDK as JRE
if jreRequired && (jreSkipped || jreMissing) {
b.warnIfJreNotUsed(jreMissing, jreSkipped)
if err = b.contributeJDKAsJRE(depJDK, jrePlanEntry, context); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to contribute JDK as JRE\n%w", err)
}
b.contributeHelpers(context, depJDK)
return b.Result, nil
}

// we need a JDK, we're not using the JDK as a JRE and the JRE has not been skipped
if jdkRequired && !(jreRequired && !jreAvailable) && !jreSkipped {
dep, err := dr.Resolve("jdk", v)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
// contribute a JDK
if jdkRequired {
if err = b.contributeJDK(depJDK); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to contribute JDK \n%w", err)
}
}

jdk, be, err := NewJDK(dep, dc, cl)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to create jdk\n%w", err)
// contribute a JRE
if jreRequired {
dt := JREType
if err = b.contributeJRE(depJRE, context.Application.Path, dt, jrePlanEntry.Metadata); err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to contribute JDK \n%w", err)
}
if IsLaunchContribution(jrePlanEntry.Metadata) {
b.contributeHelpers(context, depJRE)
}
}

return b.Result, nil
}

jdk.Logger = b.Logger
result.Layers = append(result.Layers, jdk)
result.BOM.Entries = append(result.BOM.Entries, be)
func (b *Build) contributeJDK(jdkDep libpak.BuildpackDependency) error {
jdk, be, err := NewJDK(jdkDep, b.DependencyCache, b.CertLoader)
if err != nil {
return fmt.Errorf("unable to create jdk\n%w", err)
}

if jreRequired {
dt := JREType
depJRE, err := dr.Resolve("jre", v)
jdk.Logger = b.Logger
b.Result.Layers = append(b.Result.Layers, jdk)
b.Result.BOM.Entries = append(b.Result.BOM.Entries, be)
return nil
}

if !jreAvailable || jreSkipped {
b.warnIfJreNotUsed(jreAvailable, jreSkipped)
func (b *Build) contributeJDKAsJRE(jdkDep libpak.BuildpackDependency, jrePlanEntry libcnb.BuildpackPlanEntry, context libcnb.BuildContext) error {
// This forces the contributed layer to be build + cache + launch so it's available everywhere
jrePlanEntry.Metadata["build"] = true
jrePlanEntry.Metadata["cache"] = true

// This forces the contributed layer to be build + cache + launch so it's available everywhere
jrePlanEntry.Metadata["build"] = true
jrePlanEntry.Metadata["cache"] = true
dt := JDKType
if err := b.contributeJRE(jdkDep, context.Application.Path, dt, jrePlanEntry.Metadata); err != nil {
return fmt.Errorf("unable to contribute JRE\n%w", err)
}
return nil
}

dt = JDKType
depJRE, err = dr.Resolve("jdk", v)
}
func (b *Build) contributeJRE(jreDep libpak.BuildpackDependency, appPath string, distributionType DistributionType, metadata map[string]interface{}) error {
jre, be, err := NewJRE(appPath, jreDep, b.DependencyCache, distributionType, b.CertLoader, metadata)
if err != nil {
return fmt.Errorf("unable to create jdk\n%w", err)
}

if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
}
jre.Logger = b.Logger
b.Result.Layers = append(b.Result.Layers, jre)
b.Result.BOM.Entries = append(b.Result.BOM.Entries, be)
return nil
}

jre, be, err := NewJRE(context.Application.Path, depJRE, dc, dt, cl, jrePlanEntry.Metadata)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to create jre\n%w", err)
}
func (b *Build) contributeJLink(configurationResolver libpak.ConfigurationResolver, planEntryMetadata map[string]interface{}, appPath string, jdkDep libpak.BuildpackDependency) error {
args, explicit := configurationResolver.Resolve("BP_JVM_JLINK_ARGS")
argList, err := shellwords.Parse(args)
if err != nil {
return fmt.Errorf("unable to parse jlink arguments %s %w\n", args, err)
}

jre.Logger = b.Logger
result.Layers = append(result.Layers, jre)
result.BOM.Entries = append(result.BOM.Entries, be)
jlink, err := NewJLink(appPath, effect.NewExecutor(), argList, b.CertLoader, planEntryMetadata, explicit)
if err != nil {
return fmt.Errorf("unable to create jlink jre\n%w", err)
}
jlink.JavaVersion = jdkDep.Version
jlink.Logger = b.Logger
b.Result.Layers = append(b.Result.Layers, jlink)
return nil
}

if IsLaunchContribution(jrePlanEntry.Metadata) {
helpers := []string{"active-processor-count", "java-opts", "jvm-heap", "link-local-dns", "memory-calculator",
"security-providers-configurer", "jmx", "jfr"}

if IsBeforeJava9(depJRE.Version) {
helpers = append(helpers, "security-providers-classpath-8")
helpers = append(helpers, "debug-8")
} else {
helpers = append(helpers, "security-providers-classpath-9")
helpers = append(helpers, "debug-9")
helpers = append(helpers, "nmt")
}
// Java 18 bug - cacerts keystore type not readable
if IsBeforeJava18(depJRE.Version) {
helpers = append(helpers, "openssl-certificate-loader")
}

h, be := libpak.NewHelperLayer(context.Buildpack, helpers...)
h.Logger = b.Logger
result.Layers = append(result.Layers, h)
result.BOM.Entries = append(result.BOM.Entries, be)

jsp := NewJavaSecurityProperties(context.Buildpack.Info)
jsp.Logger = b.Logger
result.Layers = append(result.Layers, jsp)
}
func (b *Build) contributeHelpers(context libcnb.BuildContext, depJRE libpak.BuildpackDependency) {
helpers := []string{"active-processor-count", "java-opts", "jvm-heap", "link-local-dns", "memory-calculator",
"security-providers-configurer", "jmx", "jfr"}

if IsBeforeJava9(depJRE.Version) {
helpers = append(helpers, "security-providers-classpath-8")
helpers = append(helpers, "debug-8")
} else {
helpers = append(helpers, "security-providers-classpath-9")
helpers = append(helpers, "debug-9")
helpers = append(helpers, "nmt")
}
// Java 18 bug - cacerts keystore type not readable
if IsBeforeJava18(depJRE.Version) {
helpers = append(helpers, "openssl-certificate-loader")
}

h, be := libpak.NewHelperLayer(context.Buildpack, helpers...)
h.Logger = b.Logger
b.Result.Layers = append(b.Result.Layers, h)
b.Result.BOM.Entries = append(b.Result.BOM.Entries, be)

return result, nil
jsp := NewJavaSecurityProperties(context.Buildpack.Info)
jsp.Logger = b.Logger
b.Result.Layers = append(b.Result.Layers, jsp)
}

func (b Build) warnIfJreNotUsed(jreAvailable, jreSkipped bool) {
func (b Build) warnIfJreNotUsed(jreMissing, jreSkipped bool) {
msg := "Using a JDK at runtime has security implications."

if !jreAvailable && !jreSkipped {
if jreMissing && !jreSkipped {
msg = fmt.Sprintf("No valid JRE available, providing matching JDK instead. %s", msg)
}

if jreSkipped {
subMsg := "A JDK was specifically requested by the user"
if jreAvailable {
if !jreMissing {
subMsg = fmt.Sprintf("%s, however a JRE is available", subMsg)
} else {
subMsg = fmt.Sprintf("%s and a JDK is the only option", subMsg)
Expand Down
Loading

0 comments on commit 0a9ff7a

Please sign in to comment.