some changes

This commit is contained in:
2025-12-14 16:34:42 +02:00
parent 6aae5f9fb0
commit 18a31be0b1
11 changed files with 207 additions and 34 deletions

View File

@@ -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)"

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -2,5 +2,8 @@
"data": {
"driver": "sqlite",
"path": "data.sqlite3"
},
"log": {
"log_root_path": "log"
}
}

View File

@@ -2,5 +2,8 @@
"name": "echo",
"version": "0.0.1-00130112025",
"entry": "echo.sh",
"runtime": "exec"
"runtime": "exec",
"log": {
"output": "stdout"
}
}

View File

@@ -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
View File

@@ -0,0 +1,3 @@
package vars
var Version = "0.0.0-none"

View File

@@ -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
}

View File

@@ -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
}

View 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"}