add function handling (basic)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
bin/
|
||||
config.yaml
|
||||
config.yaml
|
||||
*.sqlite3
|
||||
62
api/invoke/worker.go
Normal file
62
api/invoke/worker.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"git.oblat.lv/alex/triggerssmith/api/invoke"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/vars"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@@ -28,5 +32,16 @@ func (r *Router) RouteHandler() chi.Router {
|
||||
})
|
||||
fs := http.FileServer(http.Dir("static"))
|
||||
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
|
||||
}
|
||||
|
||||
6
functions/config.json
Normal file
6
functions/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"driver": "sqlite",
|
||||
"path": "data.sqlite3"
|
||||
}
|
||||
}
|
||||
6
functions/echo/1d965976/config.json
Normal file
6
functions/echo/1d965976/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "echo",
|
||||
"version": "0.0.1-00130112025",
|
||||
"entry": "echo.sh",
|
||||
"runtime": "exec"
|
||||
}
|
||||
4
functions/echo/1d965976/echo.sh
Executable file
4
functions/echo/1d965976/echo.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
read text
|
||||
echo "${text}"
|
||||
@@ -13,8 +13,13 @@ type ServerConfig struct {
|
||||
LogPath string `mapstructure:"log_path"`
|
||||
}
|
||||
|
||||
type FuncConfig struct {
|
||||
FunctionDir string `mapstructure:"func_dir"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Functions FuncConfig `mapstructure:"functions"`
|
||||
}
|
||||
|
||||
var configPath atomic.Value // string
|
||||
@@ -22,6 +27,8 @@ var defaults = map[string]any{
|
||||
"server.port": 8080,
|
||||
"server.address": "127.0.0.0",
|
||||
"server.static_dir": "./static",
|
||||
|
||||
"functions.func_dir": "./functions",
|
||||
}
|
||||
|
||||
func read(cfg *Config) error {
|
||||
|
||||
5
internal/vars/variables.go
Normal file
5
internal/vars/variables.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package vars
|
||||
|
||||
import "time"
|
||||
|
||||
var START_TIME = time.Now()
|
||||
82
internal/worker/handle.go
Normal file
82
internal/worker/handle.go
Normal 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
25
internal/worker/run.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user