mirror of https://github.com/knative/func.git
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
package oci
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"os/exec"
|
|
slashpath "path"
|
|
"path/filepath"
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
|
)
|
|
|
|
var defaultPythonBase = "python:3.13-slim" // Moving from docker.io. See issue #2720
|
|
|
|
type pythonBuilder struct{}
|
|
|
|
func (b pythonBuilder) Base() string {
|
|
return defaultPythonBase
|
|
}
|
|
|
|
// Configure gives the python builder a chance to mutate the final
|
|
// ConfigFile that will be used when building the template.
|
|
func (b pythonBuilder) Configure(job buildJob, _ v1.Platform, cf v1.ConfigFile) (v1.ConfigFile, error) {
|
|
var (
|
|
svcRelPath, _ = filepath.Rel(job.function.Root, job.buildDir()) // eg .func/builds/by-hash/$HASH
|
|
svcPath = filepath.Join("/func", svcRelPath) // eg /func/.func/builds/by-hash/$HASH
|
|
pythonPathEnv = fmt.Sprintf("PYTHONPATH=%v/lib", svcPath)
|
|
mainPath = fmt.Sprintf("%v/service/main.py", svcPath)
|
|
listenAddrEnv = "LISTEN_ADDRESS=0.0.0.0:8080"
|
|
)
|
|
|
|
cf.Config.Env = append(cf.Config.Env, pythonPathEnv, listenAddrEnv)
|
|
cf.Config.Cmd = []string{"python", mainPath}
|
|
return cf, nil
|
|
}
|
|
|
|
func (b pythonBuilder) WriteShared(job buildJob) (layers []imageLayer, err error) {
|
|
var desc v1.Descriptor
|
|
var layer v1.Layer
|
|
|
|
// Create venv
|
|
if job.verbose {
|
|
fmt.Printf("python -m venv .venv\n")
|
|
}
|
|
cmd := exec.CommandContext(job.ctx, "python", "-m", "venv", ".venv")
|
|
cmd.Dir = job.buildDir()
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdout = os.Stdout
|
|
if err = cmd.Run(); err != nil {
|
|
return
|
|
}
|
|
|
|
pipPath := filepath.Join(".venv", "bin", "pip")
|
|
|
|
// Upgrade pip
|
|
if job.verbose {
|
|
fmt.Printf(".venv/bin/pip install --upgrade pip\n")
|
|
}
|
|
cmd = exec.CommandContext(job.ctx, pipPath, "install", "--upgrade", "pip")
|
|
cmd.Dir = job.buildDir()
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdout = os.Stdout
|
|
if err = cmd.Run(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Install Dependencies of the current project into ./lib
|
|
// In the scaffolding direcotory.
|
|
if job.verbose {
|
|
fmt.Printf(".venv/bin/pip install . --target lib\n")
|
|
}
|
|
cmd = exec.CommandContext(job.ctx, pipPath, "install", ".", "--target", "lib")
|
|
cmd.Dir = job.buildDir()
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdout = os.Stdout
|
|
if err = cmd.Run(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Tar up the now-final build directory
|
|
source := job.buildDir()
|
|
target := filepath.Join(job.buildDir(), "lib.tar.gz")
|
|
if err = newPythonLibTarball(job, source, target); err != nil {
|
|
return
|
|
}
|
|
|
|
// Layer
|
|
if layer, err = tarball.LayerFromFile(target); err != nil {
|
|
return
|
|
}
|
|
|
|
// Descriptor
|
|
if desc, err = newDescriptor(layer); err != nil {
|
|
return
|
|
}
|
|
|
|
// Blob
|
|
blob := filepath.Join(job.blobsDir(), desc.Digest.Hex)
|
|
if job.verbose {
|
|
fmt.Printf("mv %v %v\n", rel(job.buildDir(), target), rel(job.buildDir(), blob))
|
|
}
|
|
if err = os.Rename(target, blob); err != nil {
|
|
return
|
|
}
|
|
|
|
return []imageLayer{{Descriptor: desc, Layer: layer}}, nil
|
|
}
|
|
|
|
func newPythonLibTarball(job buildJob, root, target string) error {
|
|
// Create a tarball of the "build directory"
|
|
// when extracted, it's root will be /func
|
|
// all files within should have path prefix .func/builds/by-hash/$hash
|
|
|
|
targetFile, err := os.Create(target) // final .tar.gz
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer targetFile.Close()
|
|
|
|
gw := gzip.NewWriter(targetFile)
|
|
defer gw.Close()
|
|
|
|
tw := tar.NewWriter(gw)
|
|
defer tw.Close()
|
|
|
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO:
|
|
// This is not ideal because we have to explicitly ignore the
|
|
// oci and venv directories from being tarred and the tar from itself.
|
|
// In hindsight, it would have been better to have the "build"
|
|
// directory contain two sub-directories:
|
|
// ./dist - the scaffolding, libraries and link to the source code.
|
|
// ./container - the final OCI container.
|
|
if path == job.ociDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
if path == filepath.Join(root, ".venv") {
|
|
return filepath.SkipDir
|
|
}
|
|
if path == target {
|
|
return nil
|
|
}
|
|
|
|
lnk := "" // if link, this will be used as the target
|
|
if info.Mode()&fs.ModeSymlink != 0 {
|
|
if lnk, err = validatedLinkTarget(job.function.Root, path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
header, err := tar.FileInfoHeader(info, lnk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The relative path from the function's root to the file
|
|
relPath, err := filepath.Rel(job.function.Root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header.Name = slashpath.Join("/func/", filepath.ToSlash(relPath))
|
|
header.Uid = DefaultUid
|
|
header.Gid = DefaultGid
|
|
if err := tw.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
if !info.Mode().IsRegular() { //nothing more to do for non-regular
|
|
return nil
|
|
}
|
|
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.Copy(tw, file)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (b pythonBuilder) WritePlatform(ctx buildJob, p v1.Platform) (layers []imageLayer, err error) {
|
|
return []imageLayer{}, nil
|
|
}
|