Skip to content

Commit ce4e875

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

File tree

5 files changed

+181
-31
lines changed

5 files changed

+181
-31
lines changed

cmd/func-util/main.go

Lines changed: 7 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,7 @@ func deploy(ctx context.Context) error {
147152
return fmt.Errorf("cannot determine working directory: %w", err)
148153
}
149154
}
150-
155+
fmt.Printf("root: %v\n", root)
151156
f, err := fn.NewFunction(root)
152157
if err != nil {
153158
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: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ 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: SOURCE_SUBPATH
66-
description: A subpath within the "source" input where the source to build is located.
65+
- name: contextDir
66+
description: context directory for the function project
67+
default: ""
68+
- name: BUILD_TARGET
69+
description: A directory to where to build from. (passed to pack builder)
6770
default: ""
6871
- name: ENV_VARS
6972
type: array
@@ -157,10 +160,10 @@ spec:
157160
############################################
158161
159162
func_file="$(workspaces.source.path)/func.yaml"
160-
if [ "$(params.SOURCE_SUBPATH)" != "" ]; then
161-
func_file="$(workspaces.source.path)/$(params.SOURCE_SUBPATH)/func.yaml"
163+
if [ "$(params.contextDir)" != "" ]; then
164+
func_file="$(workspaces.source.path)/$(params.contextDir)/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.BUILD_TARGET)"
187190
- "-cache-dir=$(workspaces.cache.path)"
188191
- "-cache-image=$(params.CACHE_IMAGE)"
189192
- "-uid=$(params.USER_ID)"
@@ -220,13 +223,13 @@ spec:
220223
digest=$(cat $(results.IMAGE_DIGEST.path))
221224
222225
func_file="$(workspaces.source.path)/func.yaml"
223-
if [ "$(params.SOURCE_SUBPATH)" != "" ]; then
224-
func_file="$(workspaces.source.path)/$(params.SOURCE_SUBPATH)/func.yaml"
226+
if [ "$(params.contextDir)" != "" ]; then
227+
func_file="$(workspaces.source.path)/$(params.contextDir)/func.yaml"
225228
fi
226229
227230
if [[ ! -f "$func_file" ]]; then
228231
echo "--> Restoring 'func.yaml'"
229-
mkdir -p "$(workspaces.source.path)/$(params.SOURCE_SUBPATH)"
232+
mkdir -p "$(workspaces.source.path)/$(params.contextDir)"
230233
cp /emptyDir/func.yaml $func_file
231234
fi
232235
@@ -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: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path"
8+
"path/filepath"
89
"strings"
910
"text/template"
1011

@@ -77,14 +78,15 @@ const (
7778
)
7879

7980
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
81+
FunctionName string
82+
Annotations map[string]string
83+
Labels map[string]string
84+
ContextDir string
85+
FunctionImage string
86+
SubPathOverride string
87+
Registry string
88+
BuilderImage string
89+
BuildEnvs []string
8890

8991
PipelineName string
9092
PipelineRunName string
@@ -387,14 +389,15 @@ func createAndApplyPipelineRunTemplate(f fn.Function, namespace string, labels m
387389
}
388390

389391
data := templateData{
390-
FunctionName: f.Name,
391-
Annotations: f.Deploy.Annotations,
392-
Labels: labels,
393-
ContextDir: contextDir,
394-
FunctionImage: f.Deploy.Image,
395-
Registry: f.Registry,
396-
BuilderImage: getBuilderImage(f),
397-
BuildEnvs: buildEnvs,
392+
FunctionName: f.Name,
393+
Annotations: f.Deploy.Annotations,
394+
Labels: labels,
395+
ContextDir: contextDir,
396+
SubPathOverride: contextDir,
397+
FunctionImage: f.Deploy.Image,
398+
Registry: f.Registry,
399+
BuilderImage: getBuilderImage(f),
400+
BuildEnvs: buildEnvs,
398401

399402
PipelineName: getPipelineName(f),
400403
PipelineRunName: getPipelineRunGenerateName(f),
@@ -407,6 +410,13 @@ func createAndApplyPipelineRunTemplate(f fn.Function, namespace string, labels m
407410
Revision: pipelinesTargetBranch,
408411
}
409412

413+
// this is for current impl. of python+pack hack
414+
// its for pack builder which needs the path of where to deploy and its
415+
// different from where func.yaml is
416+
if f.Runtime == "python" && f.Build.Builder == "pack" {
417+
data.ContextDir = filepath.Join(data.ContextDir, "fn")
418+
}
419+
410420
var template string
411421
switch f.Build.Builder {
412422
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: Dir to build from (might not be where func.yaml is)
3134
- description: Function image name
3235
name: imageName
3336
type: string
@@ -57,8 +60,10 @@ spec:
5760
value: $(params.imageName)
5861
- name: REGISTRY
5962
value: $(params.registry)
60-
- name: SOURCE_SUBPATH
63+
- name: contextDir
6164
value: $(params.contextDir)
65+
- name: SOURCE_SUBPATH
66+
value: $(params.subPathOverride)
6267
- name: BUILDER_IMAGE
6368
value: $(params.builderImage)
6469
- name: ENV_VARS
@@ -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)