@@ -272,6 +272,20 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
272
272
}
273
273
}
274
274
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
+ }
275
289
pr , pw := io .Pipe ()
276
290
277
291
const nobodyID = 65534
@@ -282,7 +296,7 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
282
296
283
297
err := tw .WriteHeader (& tar.Header {
284
298
Typeflag : tar .TypeDir ,
285
- Name : "source/" ,
299
+ Name : hName ,
286
300
Mode : 0777 ,
287
301
Uid : nobodyID ,
288
302
Gid : nobodyID ,
@@ -341,7 +355,8 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
341
355
return fmt .Errorf ("cannot create a tar header: %w" , err )
342
356
}
343
357
// "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 ))
345
360
346
361
err = tw .WriteHeader (hdr )
347
362
if err != nil {
@@ -365,13 +380,61 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
365
380
if err != nil {
366
381
_ = pw .CloseWithError (fmt .Errorf ("error while creating tar stream from sources: %w" , err ))
367
382
} 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
+ }
368
390
_ = tw .Close ()
369
391
_ = pw .Close ()
370
392
}
371
393
}()
372
394
return pr
373
395
}
374
396
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
+
375
438
// Remove tries to remove all resources that are present on the cluster and belongs to the input function and it's pipelines
376
439
func (pp * PipelinesProvider ) Remove (ctx context.Context , f fn.Function ) error {
377
440
return pp .removeClusterResources (ctx , f )
@@ -574,3 +637,62 @@ func createPipelinePersistentVolumeClaim(ctx context.Context, f fn.Function, nam
574
637
}
575
638
return nil
576
639
}
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