mirror of https://github.com/knative/func.git
fix: Python local buildpack build (#2907)
* fix: Python local buildpack build Signed-off-by: Matej Vašek <mvasek@redhat.com> * fix: sane default for LISTEN_ADDRESS in pack build Signed-off-by: Matej Vašek <mvasek@redhat.com> --------- Signed-off-by: Matej Vašek <mvasek@redhat.com>
This commit is contained in:
parent
18a119abff
commit
879233d668
|
@ -120,10 +120,6 @@ var DefaultLifecycleImage = "docker.io/buildpacksio/lifecycle:553c041"
|
|||
|
||||
// Build the Function at path.
|
||||
func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platform) (err error) {
|
||||
if f.Runtime == "python" {
|
||||
return fmt.Errorf("python is not currently supported with pack builder (use host or s2i builder instead")
|
||||
}
|
||||
|
||||
if len(platforms) != 0 {
|
||||
return errors.New("the pack builder does not support specifying target platforms directly")
|
||||
}
|
||||
|
@ -186,6 +182,10 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
|
|||
opts.ContainerConfig.Network = "host"
|
||||
}
|
||||
|
||||
if _, ok := opts.Env["BPE_DEFAULT_LISTEN_ADDRESS"]; !ok {
|
||||
opts.Env["BPE_DEFAULT_LISTEN_ADDRESS"] = "[::]:8080"
|
||||
}
|
||||
|
||||
var bindings = make([]string, 0, len(f.Build.Mounts))
|
||||
for _, m := range f.Build.Mounts {
|
||||
bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination))
|
||||
|
@ -215,6 +215,10 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
|
|||
return fmt.Errorf("podman 4.3 is not supported, use podman 4.2 or 4.4")
|
||||
}
|
||||
|
||||
if f.Runtime == "python" {
|
||||
cli = pyScaffoldInjector{cli}
|
||||
}
|
||||
|
||||
// Client with a logger which is enabled if in Verbose mode and a dockerClient that supports SSH docker daemon connection.
|
||||
if impl, err = pack.NewClient(pack.WithLogger(b.logger), pack.WithDockerClient(cli)); err != nil {
|
||||
return fmt.Errorf("cannot create pack client: %w", err)
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
package buildpacks
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
// Hack implementation of DockerClient that overrides CopyToContainer method.
|
||||
// The CopyToContainer method hijacks the uploaded project stream and injects function scaffolding to it.
|
||||
// It basically moves content of /workspace to /workspace/fn and then setup scaffolding code directly in /workspace.
|
||||
type pyScaffoldInjector struct {
|
||||
client.CommonAPIClient
|
||||
}
|
||||
|
||||
func (s pyScaffoldInjector) CopyToContainer(ctx context.Context, ctr, p string, r io.Reader, opts container.CopyToContainerOptions) error {
|
||||
|
||||
if pc, _, _, ok := runtime.Caller(1); ok &&
|
||||
!strings.Contains(runtime.FuncForPC(pc).Name(), "build.copyDir") {
|
||||
// We are not called by "project dir copy" so we do simple direct forward call.
|
||||
return s.CommonAPIClient.CopyToContainer(ctx, ctr, p, r, opts)
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
var err error
|
||||
defer func() {
|
||||
_ = pw.CloseWithError(err)
|
||||
}()
|
||||
tr := tar.NewReader(r)
|
||||
tw := tar.NewWriter(pw)
|
||||
|
||||
for {
|
||||
var hdr *tar.Header
|
||||
hdr, err = tr.Next()
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(hdr.Name, "/workspace/") {
|
||||
hdr.Name = strings.Replace(hdr.Name, "/workspace/", "/workspace/fn/", 1)
|
||||
}
|
||||
|
||||
err = tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(tw, tr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = writePythonScaffolding(tw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = tw.Close()
|
||||
}()
|
||||
|
||||
return s.CommonAPIClient.CopyToContainer(ctx, ctr, p, pr, opts)
|
||||
}
|
||||
|
||||
func writePythonScaffolding(tw *tar.Writer) error {
|
||||
for _, f := range []struct {
|
||||
path string
|
||||
content string
|
||||
}{
|
||||
{
|
||||
path: "pyproject.toml",
|
||||
content: pyprojectToml,
|
||||
},
|
||||
{
|
||||
path: "service/main.py",
|
||||
content: serviceMain,
|
||||
},
|
||||
{
|
||||
path: "service/__init__.py",
|
||||
content: "",
|
||||
},
|
||||
} {
|
||||
err := tw.WriteHeader(&tar.Header{
|
||||
Name: "/workspace/" + f.path,
|
||||
Size: int64(len(f.content)),
|
||||
Mode: 0644,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tw.Write([]byte(f.content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const pyprojectToml = `[project]
|
||||
name = "service"
|
||||
description = "an autogenerated service which runs a Function"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
license = "MIT"
|
||||
dependencies = [
|
||||
"func-python",
|
||||
"function @ ./fn"
|
||||
]
|
||||
authors = [
|
||||
{ name="The Knative Authors", email="knative-dev@googlegroups.com"},
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<4.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
script = "service.main:main"
|
||||
`
|
||||
|
||||
const serviceMain = `"""
|
||||
This code is glue between a user's Function and the middleware which will
|
||||
expose it as a network service. This code is written on-demand when a
|
||||
Function is being built, deployed or run. This will be included in the
|
||||
final container.
|
||||
"""
|
||||
import logging
|
||||
from func_python.cloudevent import serve
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
try:
|
||||
from function import new as handler # type: ignore[import]
|
||||
except ImportError:
|
||||
try:
|
||||
from function import handle as handler # type: ignore[import]
|
||||
except ImportError:
|
||||
logging.error("Function must export either 'new' or 'handle'")
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
logging.info("Functions middleware invoking user function")
|
||||
serve(handler)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
`
|
Loading…
Reference in New Issue