From 8b7067d8149d84ba3f810d4f1f9a8760cda37417 Mon Sep 17 00:00:00 2001 From: Andrew Su Date: Wed, 4 May 2022 17:38:07 -0400 Subject: [PATCH] feat: Add configuration for Java buildpack The detect now checks for func.yaml OR they explicitly defined an environment variable for the function name or package scan path. Signed-off-by: Andrew Su --- buildpacks/java/README.md | 14 ++++- buildpacks/java/buildpack.toml | 12 ++++ buildpacks/java/java/build.go | 15 +++-- buildpacks/java/java/detect.go | 65 +++++++++++---------- buildpacks/java/java/func_yaml_envs.go | 81 ++++++++++++++++++++++++++ buildpacks/java/java/function.go | 43 +++++++------- 6 files changed, 172 insertions(+), 58 deletions(-) create mode 100644 buildpacks/java/java/func_yaml_envs.go diff --git a/buildpacks/java/README.md b/buildpacks/java/README.md index 01bc5e28..3ea6da4d 100644 --- a/buildpacks/java/README.md +++ b/buildpacks/java/README.md @@ -4,18 +4,28 @@ The Java Function Buildpack is a Cloud Native Buildpack that provides a Spring B ## Behaviour This buildpack will participate if any of the following conditions are met: -* A file with the name `func.yaml` is detected +* One of the configuration environment variables are explicitly set. +* A file with the name `func.yaml` is detected. The buildpack will do the following if detection passed: * Request for a JRE to be installed * Contributes the Spring Boot application to a layer marked `launch` with the layer's path prepended to `$CLASSPATH` * Contributes environment variables defined in `func.yaml` to the `launch` layer +* Contributes environment variables to configure Spring Boot if any configuration variables are defined. (Overrides anything from `func.yaml`) + +## Configuration + +| Environment Variable | Description | +|----------------------|-------------| +| `$BP_FUNCTION` | Configure the composition of functions. Defaults to empty (load all functions). | +| `$BP_FUNCTION_PACKAGES` | Configure the package to search for functions. Defaults to `functions`. | + ## Getting started To get started you'll need to create a directory where your function will be defined. From within this directory we require a few files to properly detect this as a Java function: -* `func.yaml`: We use this to configure the runtime environment variables. See the [Knative Func CLI docs](https://github.com/knative-sandbox/kn-plugin-func/blob/main/docs/guides/func_yaml.md) for more details. +* `func.yaml` (optional): We use this to configure the runtime environment variables. See the [Knative Func CLI docs](https://github.com/knative-sandbox/kn-plugin-func/blob/main/docs/guides/func_yaml.md) for more details. * `pom.xml` or `build.gradle`: These are used by the other Java buildpacks to compile your function. * Java package in folder `src/main/java/functions`: This is the default location your function will be detected. If you do choose to use another package to store your functions, you will need to [set a new search location](#TODO). diff --git a/buildpacks/java/buildpack.toml b/buildpacks/java/buildpack.toml index 6fc343e7..1270f7f9 100644 --- a/buildpacks/java/buildpack.toml +++ b/buildpacks/java/buildpack.toml @@ -13,6 +13,18 @@ api = "0.6" include-files = ["README.md", "bin/build", "bin/detect", "bin/main", "buildpack.toml", "VERSION"] pre-package = "./build.sh" + [[metadata.configurations]] + build = true + default = "" + description = "The function to run" + name = "BP_FUNCTION" + + [[metadata.configurations]] + build = true + default = "functions" + description = "The packages to scan for functions, the default is 'functions'" + name = "BP_FUNCTION_PACKAGES" + [[metadata.dependencies]] id = "invoker" name = "Java Invoker" diff --git a/buildpacks/java/java/build.go b/buildpacks/java/java/build.go index a3fe5036..0197fd84 100644 --- a/buildpacks/java/java/build.go +++ b/buildpacks/java/java/build.go @@ -22,7 +22,7 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { b.Logger.Title(context.Buildpack) result := libcnb.NewBuildResult() - _, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) + cr, err := libpak.NewConfigurationResolver(context.Buildpack, &b.Logger) if err != nil { return libcnb.BuildResult{}, fmt.Errorf("unable to create configuration resolver\n%w", err) } @@ -47,6 +47,11 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { if !ok { return result, nil } + if e.Metadata["has_func_yaml"] == true { + envs := NewFuncYamlEnvs(context.Application.Path) + envs.Logger = b.Logger + result.Layers = append(result.Layers, envs) + } dep, err := dr.Resolve("invoker", "") if err != nil { @@ -71,10 +76,10 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) { result.BOM.Entries = append(result.BOM.Entries, be) } - f := NewFunction(e, context.Application.Path) - if err != nil { - return libcnb.BuildResult{}, fmt.Errorf("unable to create function\n%w", err) - } + funcDef, _ := cr.Resolve("BP_FUNCTION") + funcPackages, _ := cr.Resolve("BP_FUNCTION_PACKAGES") + + f := NewFunction(funcPackages, funcDef, context.Application.Path) f.Logger = b.Logger result.Layers = append(result.Layers, f) diff --git a/buildpacks/java/java/detect.go b/buildpacks/java/java/detect.go index 49e84576..ae477883 100644 --- a/buildpacks/java/java/detect.go +++ b/buildpacks/java/java/detect.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" knfn "knative.dev/kn-plugin-func" ) @@ -17,22 +18,45 @@ type Detect struct { Logger bard.Logger } -func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { - result := libcnb.DetectResult{} +func (d Detect) checkConfigs(cr libpak.ConfigurationResolver) bool { + if _, defined := cr.Resolve("BP_FUNCTION"); defined { + return true + } + + if _, defined := cr.Resolve("BP_FUNCTION_PACKAGES"); defined { + return true + } + + return false +} - configFile := filepath.Join(context.Application.Path, knfn.ConfigFile) +func (d Detect) checkFuncYaml(appPath string) bool { + configFile := filepath.Join(appPath, knfn.ConfigFile) _, err := os.Stat(configFile) if err != nil { - d.logf(fmt.Sprintf("unable to find file '%s'", configFile)) - return result, nil + d.Logger.Bodyf("unable to find file '%s'", configFile) + return false } - f, err := knfn.NewFunction(context.Application.Path) + return true +} + +func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) { + result := libcnb.DetectResult{} + + appPath := context.Application.Path + funcYamlPass := d.checkFuncYaml(appPath) + + cr, err := libpak.NewConfigurationResolver(context.Buildpack, &d.Logger) if err != nil { - return result, fmt.Errorf("parsing function config: %v", err) + return result, fmt.Errorf("unable to create configuration resolver: %v", err) } - envs := envsToMap(f.Envs) + configPass := d.checkConfigs(cr) + if err != nil { + d.Logger.Bodyf("unable to check buildpack configurations: %v", err) + return result, nil + } result.Plans = append(result.Plans, libcnb.BuildPlan{ Provides: []libcnb.BuildPlanProvide{ @@ -44,8 +68,8 @@ func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error { Name: "java-function", Metadata: map[string]interface{}{ - "launch": true, - "envs": envs, + "launch": true, + "has_func_yaml": funcYamlPass, }, }, { @@ -60,25 +84,6 @@ func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error }, }) - result.Pass = true + result.Pass = funcYamlPass || configPass return result, nil } - -func (d Detect) logf(format string, args ...interface{}) { - d.Logger.Infof(format, args...) -} - -func envsToMap(envs knfn.Envs) map[string]string { - result := map[string]string{} - - for _, e := range envs { - key := *e.Name - val := "" - if e.Value != nil { - val = *e.Value - } - result[key] = val - } - - return result -} diff --git a/buildpacks/java/java/func_yaml_envs.go b/buildpacks/java/java/func_yaml_envs.go new file mode 100644 index 00000000..552e1f59 --- /dev/null +++ b/buildpacks/java/java/func_yaml_envs.go @@ -0,0 +1,81 @@ +// Copyright 2021-2022 VMware, Inc. +// SPDX-License-Identifier: BSD-2-Clause + +package java + +import ( + "os" + "path/filepath" + + "github.com/buildpacks/libcnb" + "github.com/paketo-buildpacks/libpak" + "github.com/paketo-buildpacks/libpak/bard" + knfn "knative.dev/kn-plugin-func" +) + +type FuncYamlEnvs struct { + LayerContributor libpak.LayerContributor + Logger bard.Logger + + Envs map[string]string +} + +func NewFuncYamlEnvs(applicationPath string) FuncYamlEnvs { + envs := getFuncYamlEnvs(applicationPath) + return FuncYamlEnvs{ + LayerContributor: libpak.NewLayerContributor( + "func-yaml-envs", + envs, + libcnb.LayerTypes{ + Launch: true, + }, + ), + Envs: envs, + } +} + +func (f FuncYamlEnvs) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { + f.LayerContributor.Logger = f.Logger + return f.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) { + for k, v := range f.Envs { + layer.LaunchEnvironment.Default(k, v) + } + return layer, nil + }) +} + +func (f FuncYamlEnvs) Name() string { + return f.LayerContributor.Name +} + +func getFuncYamlEnvs(applicationPath string) map[string]string { + envs := map[string]string{} + + configFile := filepath.Join(applicationPath, knfn.ConfigFile) + _, err := os.Stat(configFile) + if err != nil { + return envs + } + + f, err := knfn.NewFunction(applicationPath) + if err != nil { + return envs + } + + return envsToMap(f.Envs) +} + +func envsToMap(envs knfn.Envs) map[string]string { + result := map[string]string{} + + for _, e := range envs { + key := *e.Name + val := "" + if e.Value != nil { + val = *e.Value + } + result[key] = val + } + + return result +} diff --git a/buildpacks/java/java/function.go b/buildpacks/java/java/function.go index 6df7f713..e828de66 100644 --- a/buildpacks/java/java/function.go +++ b/buildpacks/java/java/function.go @@ -4,8 +4,6 @@ package java import ( - "strings" - "github.com/buildpacks/libcnb" "github.com/paketo-buildpacks/libpak" "github.com/paketo-buildpacks/libpak/bard" @@ -15,24 +13,26 @@ type Function struct { LayerContributor libpak.LayerContributor Logger bard.Logger - ApplicationPath string - Handler string - Envs map[string]interface{} + ApplicationPath string + FunctionDefinition string + FunctionPackages string } -func NewFunction(plan libcnb.BuildpackPlanEntry, applicationPath string) Function { - envs := plan.Metadata["envs"].(map[string]interface{}) - +func NewFunction(packages string, definition string, applicationPath string) Function { return Function{ ApplicationPath: applicationPath, LayerContributor: libpak.NewLayerContributor( - plan.Name, - envs, + "java-function", + map[string]string{ + "packages": packages, + "definition": definition, + }, libcnb.LayerTypes{ Launch: true, }, ), - Envs: envs, + FunctionPackages: packages, + FunctionDefinition: definition, } } @@ -40,20 +40,21 @@ func (f Function) Contribute(layer libcnb.Layer) (libcnb.Layer, error) { f.LayerContributor.Logger = f.Logger return f.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) { - if len(f.Handler) > 0 { - if strings.ContainsAny(f.Handler, ".") { - layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_FUNCTION_CLASS", f.Handler) - } else { - layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_DEFINITION", f.Handler) - } + if len(f.FunctionPackages) > 0 { + layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_SCAN_PACKAGES", f.FunctionPackages) } - layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_LOCATION", f.ApplicationPath) - - for k, v := range f.Envs { - layer.LaunchEnvironment.Default(k, v) + if len(f.FunctionDefinition) > 0 { + // if strings.ContainsAny(f.Handler, ".") { + // layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_FUNCTION_CLASS", f.Handler) + // } else { + // layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_DEFINITION", f.FunctionDefinition) + // } + layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_DEFINITION", f.FunctionDefinition) } + layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_LOCATION", f.ApplicationPath) + return layer, nil }) }