add function handling (basic)

This commit is contained in:
2025-11-30 15:28:30 +02:00
parent c1e5fc90ee
commit 6aae5f9fb0
10 changed files with 215 additions and 2 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
bin/ bin/
config.yaml config.yaml
*.sqlite3

62
api/invoke/worker.go Normal file
View File

@@ -0,0 +1,62 @@
package invoke
import (
"io"
"log/slog"
"net/http"
"path/filepath"
"time"
"git.oblat.lv/alex/triggerssmith/internal/config"
"git.oblat.lv/alex/triggerssmith/internal/worker"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
type Function struct {
ID uint `gorm:"primaryKey;autoIncrement"`
FunctionName string `gorm:"not null"`
Version string `gorm:"not null"`
Path string `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func InvokeHandler(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "function_id")
version := chi.URLParam(r, "function_version")
slog.Debug("executing a function", slog.String("id", id), slog.String("version", version))
root := cfg.Functions.FunctionDir
treeCfg, _ := worker.LoadTreeConfig(root)
db, err := worker.OpenDB(treeCfg, root)
if err != nil {
slog.Error("Failed to open db", slog.String("err", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
//f, _ := worker.FindFunction(db, "echo", "0.0.1-00130112025")
f, err := worker.FindFunction(db, id, version)
if err != nil {
slog.Error("Failed to find function", slog.String("err", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
fc, err := worker.LoadFunctionConfig(root, f.FunctionName, f.Path)
if err != nil {
slog.Error("Failed to load function config", slog.String("err", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
input, _ := io.ReadAll(r.Body)
output, err := worker.RunFunction(filepath.Join(root, f.FunctionName, f.Path, fc.Entry), fc, input)
if err != nil {
slog.Error("Failed to run function", slog.String("err", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
return
}
slog.Debug("executing done", slog.Any("in", input), slog.Any("out", output))
w.Write(output)
}
}

View File

@@ -1,10 +1,14 @@
package api package api
import ( import (
"encoding/json"
"net/http" "net/http"
"path/filepath" "path/filepath"
"time"
"git.oblat.lv/alex/triggerssmith/api/invoke"
"git.oblat.lv/alex/triggerssmith/internal/config" "git.oblat.lv/alex/triggerssmith/internal/config"
"git.oblat.lv/alex/triggerssmith/internal/vars"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
@@ -28,5 +32,16 @@ func (r *Router) RouteHandler() chi.Router {
}) })
fs := http.FileServer(http.Dir("static")) fs := http.FileServer(http.Dir("static"))
r.r.Handle("/static/*", http.StripPrefix("/static/", fs)) r.r.Handle("/static/*", http.StripPrefix("/static/", fs))
r.r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
b, _ := json.Marshal(struct {
Status string `json:"status"`
Uptime string `json:"uptime"`
}{
Status: "ok",
Uptime: time.Since(vars.START_TIME).String(),
})
w.Write([]byte(b))
})
r.r.Handle("/i/invoke/function/{function_id}/{function_version}", invoke.InvokeHandler(r.cfg))
return r.r return r.r
} }

6
functions/config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"data": {
"driver": "sqlite",
"path": "data.sqlite3"
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "echo",
"version": "0.0.1-00130112025",
"entry": "echo.sh",
"runtime": "exec"
}

View File

@@ -0,0 +1,4 @@
#!/bin/bash
read text
echo "${text}"

View File

@@ -13,8 +13,13 @@ type ServerConfig struct {
LogPath string `mapstructure:"log_path"` LogPath string `mapstructure:"log_path"`
} }
type FuncConfig struct {
FunctionDir string `mapstructure:"func_dir"`
}
type Config struct { type Config struct {
Server ServerConfig `mapstructure:"server"` Server ServerConfig `mapstructure:"server"`
Functions FuncConfig `mapstructure:"functions"`
} }
var configPath atomic.Value // string var configPath atomic.Value // string
@@ -22,6 +27,8 @@ var defaults = map[string]any{
"server.port": 8080, "server.port": 8080,
"server.address": "127.0.0.0", "server.address": "127.0.0.0",
"server.static_dir": "./static", "server.static_dir": "./static",
"functions.func_dir": "./functions",
} }
func read(cfg *Config) error { func read(cfg *Config) error {

View File

@@ -0,0 +1,5 @@
package vars
import "time"
var START_TIME = time.Now()

82
internal/worker/handle.go Normal file
View File

@@ -0,0 +1,82 @@
package worker
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type RootConfig struct {
Data struct {
Driver string `json:"driver"`
Path string `json:"path"`
} `json:"data"`
}
type Function struct {
ID uint `gorm:"primaryKey"`
FunctionName string
Version string
Path string
DeletedAt gorm.DeletedAt `gorm:"index"`
}
type FuncConfig struct {
Name string `json:"name"`
Version string `json:"version"`
Entry string `json:"entry"`
Runtime string `json:"runtime"`
}
func LoadTreeConfig(root string) (*RootConfig, error) {
cfgPath := filepath.Join(root, "config.json")
b, err := os.ReadFile(cfgPath)
if err != nil {
return nil, err
}
var cfg RootConfig
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func OpenDB(cfg *RootConfig, root string) (*gorm.DB, error) {
switch cfg.Data.Driver {
case "sqlite":
dbPath := filepath.Join(root, cfg.Data.Path)
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
return nil, err
}
return db, nil
default:
return nil, fmt.Errorf("unsupported db driver: %s", cfg.Data.Driver)
}
}
func FindFunction(db *gorm.DB, name, version string) (*Function, error) {
var f Function
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
}
func LoadFunctionConfig(root, funcName, funcPath string) (*FuncConfig, error) {
cfgFile := filepath.Join(root, funcName, funcPath, "config.json")
b, err := os.ReadFile(cfgFile)
if err != nil {
return nil, err
}
var cfg FuncConfig
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

25
internal/worker/run.go Normal file
View File

@@ -0,0 +1,25 @@
package worker
import (
"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)
}
cmd := exec.Command(path)
cmd.Stdin = bytes.NewReader(input)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("failed: %w\noutput: %s", err, out.String())
}
return out.Bytes(), nil
}