Skip to content

Commit 389cd19

Browse files
committed
fix: remote python pack deployment
1 parent fa769ca commit 389cd19

File tree

5 files changed

+166
-17
lines changed

5 files changed

+166
-17
lines changed

cmd/func-util/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ func scaffold(ctx context.Context) error {
8787
}
8888

8989
if f.Runtime != "go" && f.Runtime != "python" {
90-
// Scaffolding is for now supported/needed only for Go.
90+
// Scaffolding is for now supported/needed only for Go/Python
91+
return nil
92+
}
93+
94+
// special case for python-pack
95+
if f.Build.Builder == "pack" && f.Runtime == "python" {
9196
return nil
9297
}
9398

@@ -147,7 +152,10 @@ func deploy(ctx context.Context) error {
147152
return fmt.Errorf("cannot determine working directory: %w", err)
148153
}
149154
}
150-
155+
// optional path definition to where the func.yaml is. Only relevant for
156+
// python+pack, otherwise empty
157+
root = filepath.Join(root, os.Args[3])
158+
fmt.Printf("root: %v\n", root)
151159
f, err := fn.NewFunction(root)
152160
if err != nil {
153161
return fmt.Errorf("cannot load function: %w", err)

pkg/pipelines/tekton/pipelines_provider.go

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
376439
func (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+
`

pkg/pipelines/tekton/tasks.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ spec:
6262
description: The registry associated with the function image.
6363
- name: BUILDER_IMAGE
6464
description: The image on which builds will run (must include lifecycle and compatible buildpacks).
65+
- name: contextDir
66+
description: context directory for the function project
67+
default: ""
6568
- name: SOURCE_SUBPATH
6669
description: A subpath within the "source" input where the source to build is located.
67-
default: ""
70+
default: "$(params.contextDir)"
6871
- name: ENV_VARS
6972
type: array
7073
description: Environment variables to set during _build-time_.
@@ -160,7 +163,7 @@ spec:
160163
if [ "$(params.SOURCE_SUBPATH)" != "" ]; then
161164
func_file="$(workspaces.source.path)/$(params.SOURCE_SUBPATH)/func.yaml"
162165
fi
163-
echo "--> Saving 'func.yaml'"
166+
echo "--> Saving 'func.yaml' from '$func_file'"
164167
cp $func_file /emptyDir/func.yaml
165168
166169
############################################
@@ -183,7 +186,7 @@ spec:
183186
- name: DOCKER_CONFIG
184187
value: $(workspaces.dockerconfig.path)
185188
args:
186-
- "-app=$(workspaces.source.path)/$(params.SOURCE_SUBPATH)"
189+
- "-app=$(workspaces.source.path)/$(params.contextDir)"
187190
- "-cache-dir=$(workspaces.cache.path)"
188191
- "-cache-image=$(params.CACHE_IMAGE)"
189192
- "-uid=$(params.USER_ID)"
@@ -413,13 +416,16 @@ spec:
413416
- name: image
414417
description: Container image to be deployed
415418
default: ""
419+
- name: subpath
420+
default: ""
421+
description: Optional Subpath to where func.yaml is
416422
workspaces:
417423
- name: source
418424
description: The workspace containing the function project
419425
steps:
420426
- name: func-deploy
421427
image: "%s"
422-
command: ["deploy", "$(params.path)", "$(params.image)"]
428+
command: ["deploy", "$(params.path)", "$(params.image)", "$(params.subpath)"]
423429
`, DeployerImage)
424430
}
425431

pkg/pipelines/tekton/templates.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,15 @@ const (
7777
)
7878

7979
type templateData struct {
80-
FunctionName string
81-
Annotations map[string]string
82-
Labels map[string]string
83-
ContextDir string
84-
FunctionImage string
85-
Registry string
86-
BuilderImage string
87-
BuildEnvs []string
80+
FunctionName string
81+
Annotations map[string]string
82+
Labels map[string]string
83+
ContextDir string
84+
FunctionImage string
85+
SubPathOverride string
86+
Registry string
87+
BuilderImage string
88+
BuildEnvs []string
8889

8990
PipelineName string
9091
PipelineRunName string
@@ -407,6 +408,11 @@ func createAndApplyPipelineRunTemplate(f fn.Function, namespace string, labels m
407408
Revision: pipelinesTargetBranch,
408409
}
409410

411+
// func project is gonna be in a subdirectory
412+
if f.Runtime == "python" && f.Build.Builder == "pack" {
413+
data.SubPathOverride = "fn"
414+
}
415+
410416
var template string
411417
switch f.Build.Builder {
412418
case builders.Pack:

pkg/pipelines/tekton/templates_pack.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ spec:
2828
description: Path where the function project is
2929
name: contextDir
3030
type: string
31+
- default: ''
32+
name: subPathOverride
33+
description: Override for subpath
3134
- description: Function image name
3235
name: imageName
3336
type: string
@@ -58,7 +61,7 @@ spec:
5861
- name: REGISTRY
5962
value: $(params.registry)
6063
- name: SOURCE_SUBPATH
61-
value: $(params.contextDir)
64+
value: $(params.subPathOverride)
6265
- name: BUILDER_IMAGE
6366
value: $(params.builderImage)
6467
- name: ENV_VARS
@@ -80,6 +83,8 @@ spec:
8083
value: $(workspaces.source.path)/$(params.contextDir)
8184
- name: image
8285
value: $(params.imageName)@$(tasks.build.results.IMAGE_DIGEST)
86+
- name: subpath
87+
value: $(params.subPathOverride)
8388
runAfter:
8489
- build
8590
{{.FuncDeployTaskRef}}
@@ -120,6 +125,8 @@ spec:
120125
value: {{.Revision}}
121126
- name: contextDir
122127
value: "{{.ContextDir}}"
128+
- name: subPathOverride
129+
value: "{{.SubPathOverride}}"
123130
- name: imageName
124131
value: {{.FunctionImage}}
125132
- name: registry

0 commit comments

Comments
 (0)