Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

Commit

Permalink
feat: Add configuration for Java buildpack
Browse files Browse the repository at this point in the history
The detect now checks for func.yaml OR they explicitly
defined an environment variable for the function class or
the default function name.

Signed-off-by: Andrew Su <[email protected]>
  • Loading branch information
andrew-su committed Jun 21, 2022
1 parent 5dd685c commit 39bad39
Show file tree
Hide file tree
Showing 22 changed files with 193 additions and 75 deletions.
17 changes: 13 additions & 4 deletions buildpacks/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@ 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
* A buildpack configuration variable `BP_FUNCTION` is 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 the function invoker 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 the invoker if any configuration variables are defined. (Overrides anything from `func.yaml`)

## Configuration

| Environment Variable | Description |
|----------------------|-------------|
| `$BP_FUNCTION` | Configure the function to load. If the function lives in the default package: `<class>`. If the function lives in their own package: `<package>.<class>`. Defaults to `functions.Handler` |
| `$BP_DEFAULT_FUNCTION` | Configure the default function. By specifying this property, a function with the name can be accessed via the `/` path. Defaults to empty. |

## 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).
* 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 define where your function is located with the `BP_FUNCTION` configuration for the buildpack.

## Compiling Your Function
To compile your function with the buildpack, we've provided a builder which has all the pre-requisites ready to go.
Expand Down
12 changes: 12 additions & 0 deletions buildpacks/java/buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "functions.Handler"
description = "The function to run. It must be in the format of <package>.<class>, if it is in the default package then just <class>"
name = "BP_FUNCTION"

[[metadata.configurations]]
build = true
default = ""
description = "The default function name. In the event there are multiple functions. Specifying the name of the function will make it accessible through the root path '/'"
name = "BP_DEFAULT_FUNCTION"

[[metadata.dependencies]]
id = "invoker"
name = "Java Invoker"
Expand Down
15 changes: 10 additions & 5 deletions buildpacks/java/java/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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")
defaultDef, _ := cr.Resolve("BP_DEFAULT_FUNCTION")

f := NewFunction(funcDef, defaultDef, context.Application.Path)
f.Logger = b.Logger
result.Layers = append(result.Layers, f)

Expand Down
65 changes: 35 additions & 30 deletions buildpacks/java/java/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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_DEFAULT_FUNCTION"); 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{
Expand All @@ -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,
},
},
{
Expand All @@ -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
}
81 changes: 81 additions & 0 deletions buildpacks/java/java/func_yaml_envs.go
Original file line number Diff line number Diff line change
@@ -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
}
36 changes: 14 additions & 22 deletions buildpacks/java/java/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
package java

import (
"strings"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
Expand All @@ -15,45 +13,39 @@ type Function struct {
LayerContributor libpak.LayerContributor
Logger bard.Logger

ApplicationPath string
Handler string
Envs map[string]interface{}
ApplicationPath string
Function string
DefaultFunction string
}

func NewFunction(plan libcnb.BuildpackPlanEntry, applicationPath string) Function {
envs := plan.Metadata["envs"].(map[string]interface{})

func NewFunction(function string, defaultFunc string, applicationPath string) Function {
return Function{
ApplicationPath: applicationPath,
LayerContributor: libpak.NewLayerContributor(
plan.Name,
envs,
"java-function",
map[string]string{
"function": function,
},
libcnb.LayerTypes{
Launch: true,
},
),
Envs: envs,
Function: function,
DefaultFunction: defaultFunc,
}
}

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)
}
}

layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_LOCATION", f.ApplicationPath)
layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_FUNCTION_CLASS", f.Function) // Function lives here

for k, v := range f.Envs {
layer.LaunchEnvironment.Default(k, v)
if len(f.DefaultFunction) > 0 {
layer.LaunchEnvironment.Default("SPRING_CLOUD_FUNCTION_DEFINITION", f.DefaultFunction)
}

return layer, nil
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.function.Function;

@SpringBootApplication
public class JavaFunctionInvokerApplication {
public static void main(String[] args) {
Expand Down
3 changes: 2 additions & 1 deletion invokers/java/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
spring.cloud.function.location:/workspace
spring.cloud.function.location:/workspace
spring.cloud.function.scan.enabled:false
2 changes: 1 addition & 1 deletion samples/java/cloudevents/txt-to-pdf-maven/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FUNCTION_IMAGE ?= us.gcr.io/daisy-284300/functions/demo:txttopdfjava
FUNCTION := out/image
$(FUNCTION):
@mkdir -p $(@D)
pack build $(FUNCTION_IMAGE) --path ./spring-java-fn/ --builder ghcr.io/vmware-tanzu/function-buildpacks-for-knative/functions-builder:0.0.10
pack build $(FUNCTION_IMAGE) --path ./spring-java-fn/ --builder ghcr.io/vmware-tanzu/function-buildpacks-for-knative/functions-builder:0.0.10 --env BP_FUNCTION=com.example.demo.DemoApplication --env BP_DEFAULT_FUNCTION=func
printf $(FUNCTION_IMAGE) > $@

build: $(FUNCTION)
Expand Down
2 changes: 1 addition & 1 deletion samples/java/cloudevents/txt-to-pdf-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ If you encounter any Knative errors while re-deploying the app, be sure to delet
```
## Legacy Deployment YAML
```
apiVersion: v1
kind: Secret
metadata:
Expand Down
6 changes: 5 additions & 1 deletion samples/java/http/request-response-gradle/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# request-response

This is a sample function for how to create and send HTTP requests via our Java HTTP function.
This is a sample function for how to create and send HTTP requests via our Java HTTP function.

# Building

To build this sample, use the latest buildpack and build with `--env BP_FUNCTION=com.vmware.functions.Handler` set on the `pack` cli command.
Binary file not shown.
6 changes: 5 additions & 1 deletion samples/java/http/request-response-maven/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# request-response

This is a sample function for how to create and send HTTP requests via our Java HTTP function.
This is a sample function for how to create and send HTTP requests via our Java HTTP function.

# Building

To build this sample, use the latest buildpack and build with `--env BP_FUNCTION=com.vmware.functions.Handler` set on the `pack` cli command.
Loading

0 comments on commit 39bad39

Please sign in to comment.