@@ -272,6 +272,20 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
272272 }
273273 }
274274
275+ // Hack for python+pack where the function needs to be in a subdirectory
276+ // and the actual main (scaffolding) needs to be in the source.
277+ // So main in "root" and function in "root/subdir" otherwise pack builder
278+ // determines that current function is JAVA or something because we currently
279+ // build in the .s2i directory (needs fixing) and it contains a 'bin/' dir.
280+ // buildpacks determines its somehow java.
281+ //
282+ // You can see this in the python scaffolding injector made for local builds
283+ // for buildpacks builder & python runtime.
284+ hName := "source"
285+ usePyInjector := f .Build .Builder == "pack" && f .Runtime == "python"
286+ if usePyInjector {
287+ hName = "source/fn"
288+ }
275289 pr , pw := io .Pipe ()
276290
277291 const nobodyID = 65534
@@ -282,7 +296,7 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
282296
283297 err := tw .WriteHeader (& tar.Header {
284298 Typeflag : tar .TypeDir ,
285- Name : "source/" ,
299+ Name : hName ,
286300 Mode : 0777 ,
287301 Uid : nobodyID ,
288302 Gid : nobodyID ,
@@ -341,7 +355,8 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
341355 return fmt .Errorf ("cannot create a tar header: %w" , err )
342356 }
343357 // "source" is expected path in workspace pvc
344- hdr .Name = path .Join ("source" , filepath .ToSlash (relp ))
358+ // current Hack: python + pack builder needs subdir ('source/fn')
359+ hdr .Name = path .Join (hName , filepath .ToSlash (relp ))
345360
346361 err = tw .WriteHeader (hdr )
347362 if err != nil {
@@ -365,13 +380,61 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
365380 if err != nil {
366381 _ = pw .CloseWithError (fmt .Errorf ("error while creating tar stream from sources: %w" , err ))
367382 } else {
383+ // python injector hack for python+pack builder
384+ if usePyInjector {
385+ err = pythonInjector (tw , f .Invoke )
386+ if err != nil {
387+ _ = pw .CloseWithError (fmt .Errorf ("cannot inject python main into tar stream: %w" , err ))
388+ }
389+ }
368390 _ = tw .Close ()
369391 _ = pw .Close ()
370392 }
371393 }()
372394 return pr
373395}
374396
397+ // inject python main etc. into the tar file at the root dir instead of the usual
398+ // subdir for python + pack builder
399+ func pythonInjector (tw * tar.Writer , invoke string ) (err error ) {
400+ if invoke == "" {
401+ invoke = "http"
402+ }
403+ // the function files were all written in "source/fn" therefore new header
404+ // for the actual "source" is neeeded
405+ for _ , f := range []struct {
406+ path string
407+ content string
408+ }{
409+ {
410+ path : "service/main.py" ,
411+ content : mainContent (invoke ),
412+ },
413+ {
414+ path : "pyproject.toml" ,
415+ content : tomlContent ,
416+ },
417+ {
418+ path : "service/__init__.py" ,
419+ content : "" ,
420+ },
421+ } {
422+ err := tw .WriteHeader (& tar.Header {
423+ Name : "/source/" + f .path ,
424+ Size : int64 (len (f .content )),
425+ Mode : 0644 ,
426+ })
427+ if err != nil {
428+ return err
429+ }
430+ _ , err = tw .Write ([]byte (f .content ))
431+ if err != nil {
432+ return err
433+ }
434+ }
435+ return nil
436+ }
437+
375438// Remove tries to remove all resources that are present on the cluster and belongs to the input function and it's pipelines
376439func (pp * PipelinesProvider ) Remove (ctx context.Context , f fn.Function ) error {
377440 return pp .removeClusterResources (ctx , f )
@@ -574,3 +637,62 @@ func createPipelinePersistentVolumeClaim(ctx context.Context, f fn.Function, nam
574637 }
575638 return nil
576639}
640+
641+ func mainContent (invoke string ) string {
642+ template := `"""
643+ This code is glue between a user's Function and the middleware which will
644+ expose it as a network service. This code is written on-demand when a
645+ Function is being built, deployed or run. This will be included in the
646+ final container.
647+ """
648+ import logging
649+ from func_python.%s import serve
650+
651+ logging.basicConfig(level=logging.INFO)
652+
653+ try:
654+ from function import new as handler # type: ignore[import]
655+ except ImportError:
656+ try:
657+ from function import handle as handler # type: ignore[import]
658+ except ImportError:
659+ logging.error("Function must export either 'new' or 'handle'")
660+ raise
661+
662+ def main():
663+ logging.info("Functions middleware invoking user function")
664+ serve(handler)
665+
666+ if __name__ == "__main__":
667+ main()
668+ `
669+ return fmt .Sprintf (template , invoke )
670+ }
671+
672+ const tomlContent = `[project]
673+ name = "service"
674+ description = "an autogenerated service which runs a Function"
675+ version = "0.1.0"
676+ requires-python = ">=3.9"
677+ license = "MIT"
678+ dependencies = [
679+ "func-python",
680+ "function @ ./fn"
681+ ]
682+ authors = [
683+ { name="The Knative Authors", email="[email protected] "}, 684+ ]
685+
686+ [build-system]
687+ requires = ["hatchling"]
688+ build-backend = "hatchling.build"
689+
690+ [tool.hatch.metadata]
691+ allow-direct-references = true
692+
693+ [tool.poetry.dependencies]
694+ python = ">=3.9,<4.0"
695+
696+ [tool.poetry.scripts]
697+ script = "service.main:main"
698+ `
0 commit comments