func/pkg/scaffolding/scaffold.go

130 lines
4.3 KiB
Go

package scaffolding
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"knative.dev/func/pkg/filesystem"
)
// Write scaffolding to a given path
//
// Scaffolding is a language-level operation which first detects the method
// signature used by the function's source code and then writes the
// appropriate scaffolding.
//
// NOTE: Scaffoding is not per-template, because a template is merely an
// example starting point for a Function implementation and should have no
// bearing on the shape that function can eventually take. The language,
// and optionally invocation hint (For cloudevents) are used for this. For
// example, there can be multiple templates which exemplify a given method
// signature, and the implementation can be switched at any time by the author.
// Language, by contrast, is fixed at time of initialization.
//
// out: the path to output scaffolding
// src: the path to the source code to scaffold
// runtime: the expected runtime of the target source code "go", "node" etc.
// invoke: the optional invocatin hint (default "http")
// fs: filesytem which contains scaffolding at '[runtime]/scaffolding'
// (exclusive with 'repo')
func Write(out, src, runtime, invoke string, fs filesystem.Filesystem) (err error) {
// detect the signature of the source code in the given location, presuming
// a runtime and invocation hint (default "http")
s, err := detectSignature(src, runtime, invoke)
if err != nil {
return err
}
// Filesystem Required
// This is a defensive check used to allow for simple tests which can
// omit providing a fileystem by just expecting this error.
if fs == nil {
return ErrFilesysetmRequired
}
// Path in the filesystem at which scaffolding is expected to exist
d := fmt.Sprintf("%v/scaffolding/%v", runtime, s.String()) // fs uses / on all OSs
if _, err := fs.Stat(d); err != nil {
return ErrScaffoldingNotFound
}
// Copy from d -> out from the filesystem
if err := filesystem.CopyFromFS(d, out, fs); err != nil {
return ScaffoldingError{"filesystem copy failed", err}
}
// Not my proudest moment
if runtime == "go" {
var data []byte
data, err = os.ReadFile(filepath.Join(src, "go.mod"))
if err != nil {
return fmt.Errorf("cannot read go.mod: %w", err)
}
r := regexp.MustCompile(`module\s+(\w+)`)
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return fmt.Errorf("cannot parse go.mod")
}
moduleName := string(matches[1])
for _, n := range []string{"go.mod", "main.go"} {
p := filepath.Join(out, n)
data, err = os.ReadFile(p)
if err != nil {
return fmt.Errorf("cannot read scaffolding file: %w", err)
}
data = bytes.ReplaceAll(data, []byte("function"), []byte(moduleName))
err = os.WriteFile(p, data, 0644)
if err != nil {
return fmt.Errorf("cannot patch scaffolding code: %w", err)
}
}
}
// Copy the certs from the filesystem to the build directory
if _, err := fs.Stat("certs"); err != nil {
return ScaffoldingError{"certs directory not found in filesystem", err}
}
if err := filesystem.CopyFromFS("certs", out, fs); err != nil {
return ScaffoldingError{"certs copy failed", err}
}
// Replace the 'f' link of the scaffolding (which is now incorrect) to
// link to the function's root.
rel, err := filepath.Rel(out, src)
if err != nil {
return ScaffoldingError{"error determining relative path to function source", err}
}
link := filepath.Join(out, "f")
_ = os.Remove(link)
if err = os.Symlink(rel, link); err != nil {
return fmt.Errorf("error linking scaffolding to source %w", err)
}
return
}
// detectSignature returns the Signature of the source code at the given
// location assuming a provided runtime and invocation hint.
func detectSignature(src, runtime, invoke string) (s Signature, err error) {
d, err := newDetector(runtime)
if err != nil {
return UnknownSignature, err
}
static, instanced, err := d.Detect(src)
if err != nil {
return
}
// Function must implement either a static handler or the instanced handler
// but not both.
if static && instanced {
return s, fmt.Errorf("function may not implement both the static and instanced method signatures simultaneously")
} else if !static && !instanced {
return s, fmt.Errorf("function does not implement any known method signatures or does not compile")
} else {
return toSignature(instanced, invoke), nil
}
}