some changes
This commit is contained in:
3
Makefile
3
Makefile
@@ -7,6 +7,7 @@ BINARY = ${BIN_DIR}/$(NAME)
|
||||
CHECK_LINTER = command -v golangci-lint >/dev/null 2>&1
|
||||
CHECK_IMPORTS = command -v goimports >/dev/null 2>&1
|
||||
PATH := $(PATH):$(HOME)/go/bin
|
||||
VERSION = 0.0.1-dev
|
||||
|
||||
lint-tools:
|
||||
@if ! $(CHECK_LINTER); then \
|
||||
@@ -27,7 +28,7 @@ run: build
|
||||
@echo "-- running $(NAME)"
|
||||
@$(BINARY)
|
||||
|
||||
#BUILD_PARAMS = -trimpath
|
||||
BUILD_PARAMS = -trimpath -ldflags "-X git.oblat.lv/alex/triggerssmith/internal/vars.Version=$(VERSION)"
|
||||
|
||||
build:
|
||||
@echo "-- building $(NAME)"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package invoke
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
@@ -22,6 +25,46 @@ type Function struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
type TerminalLogger struct {
|
||||
fc *worker.FuncConfig
|
||||
}
|
||||
|
||||
func (l *TerminalLogger) Write(line string) {
|
||||
slog.Warn("function stderr", slog.String("line", line), slog.String("n:v", fmt.Sprintf("%s:%s", l.fc.Name, l.fc.Version)))
|
||||
}
|
||||
|
||||
type JSONFileLogger struct {
|
||||
fc *worker.FuncConfig
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewJSONFileLogger(fc *worker.FuncConfig, path string) (*JSONFileLogger, error) {
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler := slog.NewJSONHandler(file, &slog.HandlerOptions{})
|
||||
logger := slog.New(handler)
|
||||
|
||||
return &JSONFileLogger{
|
||||
fc: fc,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *JSONFileLogger) Write(line string) {
|
||||
l.logger.Warn("function stderr",
|
||||
slog.String("function", l.fc.Name),
|
||||
slog.String("version", l.fc.Version),
|
||||
slog.String("line", line),
|
||||
)
|
||||
}
|
||||
|
||||
func InvokeHandler(cfg *config.Config) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "function_id")
|
||||
@@ -49,8 +92,46 @@ func InvokeHandler(cfg *config.Config) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
var logger worker.Logger
|
||||
switch fc.Log.Output {
|
||||
case "stdout":
|
||||
logger = &TerminalLogger{
|
||||
fc: fc,
|
||||
}
|
||||
case "file":
|
||||
fileLogger, err := NewJSONFileLogger(fc, filepath.Join(treeCfg.Log.Path, fmt.Sprintf("%s:%s", fc.Name, f.Path), "event.log.json"))
|
||||
if err != nil {
|
||||
slog.Error("Failed to create file logger", slog.String("err", err.Error()))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
logger = fileLogger
|
||||
}
|
||||
|
||||
var frmt = func(s1 string, s2 string) string {
|
||||
return fmt.Sprintf("FAAS_%s=%s", s1, s2)
|
||||
}
|
||||
var env = []string{
|
||||
frmt("PROTOCOL", r.Proto),
|
||||
|
||||
frmt("METHOD", r.Method),
|
||||
frmt("PATH", r.URL.Path),
|
||||
frmt("QUERY", r.URL.RawQuery),
|
||||
}
|
||||
|
||||
for k, v := range r.Header {
|
||||
key := "FAAS_HEADER_" + strings.ReplaceAll(k, "-", "_")
|
||||
env = append(env, key+"="+v[0])
|
||||
}
|
||||
|
||||
input, _ := io.ReadAll(r.Body)
|
||||
output, err := worker.RunFunction(filepath.Join(root, f.FunctionName, f.Path, fc.Entry), fc, input)
|
||||
path := filepath.Join(root, f.FunctionName, f.Path, fc.Entry)
|
||||
output, err := worker.RunFunction(&worker.RunOps{
|
||||
Path: path,
|
||||
FuncConfig: fc,
|
||||
Log: logger,
|
||||
Env: env,
|
||||
}, input)
|
||||
if err != nil {
|
||||
slog.Error("Failed to run function", slog.String("err", err.Error()))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
@@ -42,6 +42,6 @@ func (r *Router) RouteHandler() chi.Router {
|
||||
})
|
||||
w.Write([]byte(b))
|
||||
})
|
||||
r.r.Handle("/i/invoke/function/{function_id}/{function_version}", invoke.InvokeHandler(r.cfg))
|
||||
r.r.Handle("/invoke/function/{function_id}/{function_version}", invoke.InvokeHandler(r.cfg))
|
||||
return r.r
|
||||
}
|
||||
|
||||
58
cmd/serve.go
58
cmd/serve.go
@@ -4,11 +4,10 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.oblat.lv/alex/triggerssmith/api"
|
||||
application "git.oblat.lv/alex/triggerssmith/internal/app"
|
||||
@@ -21,30 +20,36 @@ import (
|
||||
var optsServeCmd = struct {
|
||||
ConfigPath *string
|
||||
Debug *bool
|
||||
HideGreetings *bool
|
||||
}{}
|
||||
|
||||
// simple middleware for request logging
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
// // simple middleware for request logging
|
||||
// func loggingMiddleware(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// start := time.Now()
|
||||
|
||||
slog.Info("HTTP request",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("remote", r.RemoteAddr),
|
||||
)
|
||||
// slog.Info("HTTP request",
|
||||
// slog.String("method", r.Method),
|
||||
// slog.String("path", r.URL.Path),
|
||||
// slog.String("remote", r.RemoteAddr),
|
||||
// )
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
// next.ServeHTTP(w, r)
|
||||
|
||||
slog.Debug("HTTP request finished",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.Duration("latency", time.Since(start)),
|
||||
)
|
||||
})
|
||||
}
|
||||
// slog.Debug("HTTP request finished",
|
||||
// slog.String("method", r.Method),
|
||||
// slog.String("path", r.URL.Path),
|
||||
// slog.Duration("latency", time.Since(start)),
|
||||
// )
|
||||
// })
|
||||
// }
|
||||
|
||||
func writePID(path string) error {
|
||||
dir := filepath.Dir(path)
|
||||
err := os.MkdirAll(dir, 0644)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pid := os.Getpid()
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
@@ -61,12 +66,26 @@ var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
text := fmt.Sprintf(`
|
||||
_______ _____
|
||||
|__ __/ ____|
|
||||
| | | (___
|
||||
| | \___ \
|
||||
| | ____) |
|
||||
|_| |_____/
|
||||
|
||||
TriggerSmith - v%s
|
||||
`, vars.Version)
|
||||
if !*optsServeCmd.HideGreetings {
|
||||
fmt.Println(text)
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
slog.Error("Application panicked", slog.Any("error", r))
|
||||
os.Exit(-1)
|
||||
}
|
||||
}()
|
||||
|
||||
// configure logger
|
||||
if *optsServeCmd.Debug {
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})))
|
||||
@@ -170,5 +189,6 @@ var serveCmd = &cobra.Command{
|
||||
func init() {
|
||||
optsServeCmd.Debug = serveCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
|
||||
optsServeCmd.ConfigPath = serveCmd.Flags().StringP("config", "c", "config.yaml", "Path to configuration file")
|
||||
optsServeCmd.HideGreetings = serveCmd.Flags().BoolP("hide-greetings", "g", false, "Hide the welcome message and version when starting the server")
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
"data": {
|
||||
"driver": "sqlite",
|
||||
"path": "data.sqlite3"
|
||||
},
|
||||
"log": {
|
||||
"log_root_path": "log"
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "echo",
|
||||
"version": "0.0.1-00130112025",
|
||||
"entry": "echo.sh",
|
||||
"runtime": "exec"
|
||||
"runtime": "exec",
|
||||
"log": {
|
||||
"output": "stdout"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
read text
|
||||
echo "${text}"
|
||||
urldecode() {
|
||||
local data="${1//+/ }"
|
||||
printf '%b' "${data//%/\\x}"
|
||||
}
|
||||
|
||||
declare -A QUERY
|
||||
|
||||
IFS='&' read -ra pairs <<< "$FAAS_QUERY"
|
||||
|
||||
for pair in "${pairs[@]}"; do
|
||||
IFS='=' read -r raw_key raw_value <<< "$pair"
|
||||
key=$(urldecode "$raw_key")
|
||||
value=$(urldecode "$raw_value")
|
||||
QUERY["$key"]="$value"
|
||||
done
|
||||
|
||||
echo "a = ${QUERY[a]}"
|
||||
echo "b = ${QUERY[b]}"
|
||||
#echo $(ls)
|
||||
3
internal/vars/version.go
Normal file
3
internal/vars/version.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package vars
|
||||
|
||||
var Version = "0.0.0-none"
|
||||
@@ -10,11 +10,18 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Write(line string)
|
||||
}
|
||||
|
||||
type RootConfig struct {
|
||||
Data struct {
|
||||
Driver string `json:"driver"`
|
||||
Path string `json:"path"`
|
||||
} `json:"data"`
|
||||
Log struct {
|
||||
Path string `json:"log_root_path"`
|
||||
} `json:"log"`
|
||||
}
|
||||
|
||||
type Function struct {
|
||||
@@ -30,6 +37,9 @@ type FuncConfig struct {
|
||||
Version string `json:"version"`
|
||||
Entry string `json:"entry"`
|
||||
Runtime string `json:"runtime"`
|
||||
Log struct {
|
||||
Output string `json:"output"`
|
||||
} `json:"log"`
|
||||
}
|
||||
|
||||
func LoadTreeConfig(root string) (*RootConfig, error) {
|
||||
@@ -61,10 +71,19 @@ func OpenDB(cfg *RootConfig, root string) (*gorm.DB, error) {
|
||||
|
||||
func FindFunction(db *gorm.DB, name, version string) (*Function, error) {
|
||||
var f Function
|
||||
if version == "latest" {
|
||||
err := db.Where("function_name = ? AND deleted_at IS NULL", name).
|
||||
Order("created_at DESC").
|
||||
First(&f).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := db.Where("function_name = ? AND version = ? AND deleted_at IS NULL", name, version).
|
||||
First(&f).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,49 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func RunFunction(path string, fc *FuncConfig, input []byte) ([]byte, error) {
|
||||
if fc.Runtime != "exec" {
|
||||
return nil, fmt.Errorf("unsupported runtime: %s", fc.Runtime)
|
||||
type RunOps struct {
|
||||
Log Logger
|
||||
Path string
|
||||
FuncConfig *FuncConfig
|
||||
Env []string
|
||||
}
|
||||
|
||||
func RunFunction(opt *RunOps, input []byte) ([]byte, error) {
|
||||
if opt.FuncConfig.Runtime != "exec" {
|
||||
return nil, fmt.Errorf("unsupported runtime: %s", opt.FuncConfig.Runtime)
|
||||
}
|
||||
|
||||
cmd := exec.Command(path)
|
||||
cmd := exec.Command(opt.Path)
|
||||
cmd.Env = opt.Env
|
||||
cmd.Stdin = bytes.NewReader(input)
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stderrPipe)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
opt.Log.Write(line)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("failed: %w\noutput: %s", err, out.String())
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
2
log/echo:1d965976/event.log.json
Normal file
2
log/echo:1d965976/event.log.json
Normal file
@@ -0,0 +1,2 @@
|
||||
{"time":"2025-11-30T16:12:41.425709604+02:00","level":"WARN","msg":"function stderr","function":"echo","version":"0.0.1-00130112025","line":"bem bem"}
|
||||
{"time":"2025-11-30T16:12:42.487539993+02:00","level":"WARN","msg":"function stderr","function":"echo","version":"0.0.1-00130112025","line":"bem bem"}
|
||||
Reference in New Issue
Block a user