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

Commit

Permalink
feat: Add configuration for Python buildpack
Browse files Browse the repository at this point in the history
This adds a new configuration `BP_FUNCTION` to the Python
buildpack. The check for `func.yaml` is now optional as long as
the configuration is defined.

Signed-off-by: Andrew Su <[email protected]>
  • Loading branch information
andrew-su committed May 31, 2022
1 parent 0fccc84 commit 0a3dde0
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 39 deletions.
36 changes: 22 additions & 14 deletions buildpacks/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,8 @@ The buildpack will do the following if detection passed:
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 Python function:
* <a name="func.yaml"></a>`func.yaml`: This is the configuration used to give the buildpack some configurations.
* The python module and function name can be modified here by defining some environment variables.
```
envs:
- name: MODULE_NAME
value: my_module
- name: FUNCTION_NAME
value: my_func
```
By defining the above instead of a `func.py` file like below, the file should now be `my_module.py` containing a function with the name `my_func`
* `func.py`: This python module will be where we search for a function by default.
* If you want to use a different name for the file. See description for [`func.yaml`](#func.yaml).
* If you want to use a different name for the file. See [configuration](#configuration) or [`func.yaml`](#func.yaml).
* This file should contain the function to invoke when we receive an event.
* The function can handle http requests:
```
Expand All @@ -47,8 +36,20 @@ From within this directory we require a few files to properly detect this as a P
```
* You can find more details about the different accepted parameters [below](#fp).
* `requirements.txt`: This file is used for defining your dependencies. However if you have no dependencies, we're still expecting an empty file.
* TODO: Remove the expectation of file `requirements.txt`
* <a name="func.yaml"></a>`func.yaml` (optional): This is the configuration used to configure your function.
* The python module and function name can be modified here by defining some environment variables in the `envs` section.
```
envs:
- name: MODULE_NAME
value: my_module
- name: FUNCTION_NAME
value: my_func
```
By defining the above, we will look for a `my_module.py` instead of `func.py` which contains a function with the name `my_func`.
**NOTE**: The environment variables here (namely `MODULE_NAME` and `FUNCTION_NAME` will be overriden by the values specified by `BP_FUNCTION`)
* `requirements.txt`: This file is required by the Python dependency. It is used to define your function's dependencies. If you do not have any, you still need to provide an empty file.
## <a name="fp"></a> Accepted Function Parameters
The function handles either HTTP or CloudEvents based on the parameter's name and type. Only the following arguments are accepted:
Expand All @@ -74,6 +75,13 @@ ghcr.io/vmware-tanzu/function-buildpacks-for-knative/functions-builder
* [Buildpack CLI](https://buildpacks.io/docs/tools/pack/)
### <a name="usage"></a> Usage
## <a name="configuration"></a> Configuration
| Environment Variable | Description |
| -------------------- | ----------- |
| `$BP_FUNCTION` | Configure the function handler. Defaults to `func.main`. |
Build the function container with the Buildpack CLI
```
pack build <your_image_name_and_tag> --builder ghcr.io/vmware-tanzu/function-buildpacks-for-knative/functions-builder:<version>
Expand Down
6 changes: 6 additions & 0 deletions buildpacks/python/buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ 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 = "func.main"
description = "The function to run, specify in the form of `module.function_name`"
name = "BP_FUNCTION"

[[metadata.dependencies]]
id = "invoker"
name = "Python Invoker"
Expand Down
66 changes: 41 additions & 25 deletions buildpacks/python/python/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,68 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
knfn "knative.dev/kn-plugin-func"
)

const (
EnvModuleName = "MODULE_NAME"
EnvFunctionName = "FUNCTION_NAME"

ModuleNameDefault = "func"
FunctionNameDefault = "main"
)

type Detect struct {
Logger bard.Logger
}

func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
result := libcnb.DetectResult{}
configFile := filepath.Join(context.Application.Path, knfn.ConfigFile)
func (d Detect) getFuncYamlEnvs(appPath string) (map[string]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("'%s' not detected", knfn.ConfigFile)
return make(map[string]string), false
}

f, err := knfn.NewFunction(context.Application.Path)
f, err := knfn.NewFunction(appPath)
if err != nil {
return result, fmt.Errorf("parsing function config: %v", err)
d.Logger.Bodyf("unable to parse '%s': %v", knfn.ConfigFile, err)
return make(map[string]string), false
}

envs := envsToMap(f.Envs)
setDefaults(envs)
return envsToMap(f.Envs), true
}

func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
result := libcnb.DetectResult{}

envs, hasValidFuncYaml := d.getFuncYamlEnvs(context.Application.Path)

cr, err := libpak.NewConfigurationResolver(context.Buildpack, &d.Logger)
if err != nil {
return libcnb.DetectResult{}, err
}

functionHandler, funcFound := cr.Resolve("BP_FUNCTION")
funcParts := strings.Split(functionHandler, ".")
if funcFound {
if len(funcParts) != 2 || len(funcParts[0]) == 0 || len(funcParts[1]) == 0 { // We're expecting the format of module.func_name
return libcnb.DetectResult{}, fmt.Errorf("BP_FUNCTION detected but is invalid, it should be in the form of `module.function_name`")
}

envs[EnvModuleName] = funcParts[0]
envs[EnvFunctionName] = funcParts[1]
} else {
if _, found := envs[EnvModuleName]; !found {
envs[EnvModuleName] = funcParts[0]
}

if _, found := envs[EnvFunctionName]; !found {
envs[EnvFunctionName] = funcParts[1]
}
}

result.Plans = append(result.Plans, libcnb.BuildPlan{
Provides: []libcnb.BuildPlanProvide{
Expand Down Expand Up @@ -72,7 +100,7 @@ func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error
},
})

result.Pass = true
result.Pass = hasValidFuncYaml || funcFound

return result, nil
}
Expand All @@ -95,15 +123,3 @@ func envsToMap(envs knfn.Envs) map[string]string {

return result
}

func setDefaults(envs map[string]string) {
setDefault(envs, EnvModuleName, ModuleNameDefault)
setDefault(envs, EnvFunctionName, FunctionNameDefault)
}

func setDefault(m map[string]string, key string, def string) {
_, ok := m[key]
if !ok {
m[key] = def
}
}

0 comments on commit 0a3dde0

Please sign in to comment.