Uses v2 configration schema

This commit is contained in:
2024-01-25 00:09:39 +08:00
parent 7ad17d9417
commit b906edc022
37 changed files with 542 additions and 437 deletions

View File

@ -17,7 +17,7 @@ type CliConnection struct {
}
func (v CliConnection) CheckConnectivity() error {
client := fiber.Get(v.Url + "/cgi/connectivity")
client := fiber.Get(v.Url + "/cgi/metadata")
client.BasicAuth("RoadSign CLI", v.Credential)
if status, data, err := client.Bytes(); len(err) > 0 {

View File

@ -11,9 +11,9 @@ import (
"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds/conn"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"github.com/gofiber/fiber/v2"
"github.com/pelletier/go-toml/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v2"
)
var DeployCommands = []*cli.Command{
@ -78,7 +78,7 @@ var DeployCommands = []*cli.Command{
return err
} else {
raw, _ := io.ReadAll(file)
yaml.Unmarshal(raw, &site)
toml.Unmarshal(raw, &site)
}
url := fmt.Sprintf("/webhooks/sync/%s", ctx.Args().Get(1))

View File

@ -21,7 +21,7 @@ func main() {
// Configure settings
viper.AddConfigPath("$HOME")
viper.SetConfigName(".roadsignrc")
viper.SetConfigType("yaml")
viper.SetConfigType("toml")
// Load settings
if err := viper.ReadInConfig(); err != nil {

View File

@ -10,7 +10,6 @@ import (
"code.smartsheep.studio/goatworks/roadsign/pkg/hypertext"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/sideload"
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -27,7 +26,7 @@ func main() {
viper.AddConfigPath(".")
viper.AddConfigPath("..")
viper.SetConfigName("settings")
viper.SetConfigType("yaml")
viper.SetConfigType("toml")
// Load settings
if err := viper.ReadInConfig(); err != nil {
@ -44,20 +43,15 @@ func main() {
log.Warn().Msgf("RoadSign auto generated api credential is %s", credential)
}
// Load & init sign
// Load & init navi
if err := navi.ReadInConfig(viper.GetString("paths.configs")); err != nil {
log.Panic().Err(err).Msg("An error occurred when loading configurations.")
} else {
log.Info().Int("count", len(sign.App.Sites)).Msg("All configuration has been loaded.")
log.Info().Int("count", len(navi.R.Regions)).Msg("All configuration has been loaded.")
}
// Preheat processes
go func() {
log.Info().Msg("Preheating processes...")
sign.App.PreheatProcesses(func(total int, success int) {
log.Info().Int("requested", total).Int("succeed", success).Msgf("Preheat processes completed!")
})
}()
// Init warden
navi.InitializeWarden(navi.R.Regions)
// Init hypertext server
hypertext.RunServer(

View File

@ -1,9 +1,10 @@
package hypertext
import (
"math/rand"
"regexp"
"code.smartsheep.studio/goatworks/roadnavi/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
@ -16,16 +17,16 @@ func UseProxies(app *fiber.App) {
headers := ctx.GetReqHeaders()
// Filtering sites
for _, site := range navi.App.Sites {
for _, region := range navi.R.Regions {
// Matching rules
for _, rule := range site.Rules {
if !lo.Contains(rule.Host, host) {
for _, location := range region.Locations {
if !lo.Contains(location.Host, host) {
continue
}
if !func() bool {
flag := false
for _, pattern := range rule.Path {
for _, pattern := range location.Path {
if ok, _ := regexp.MatchString(pattern, path); ok {
flag = true
break
@ -38,7 +39,7 @@ func UseProxies(app *fiber.App) {
// Filter query strings
flag := true
for rk, rv := range rule.Queries {
for rk, rv := range location.Queries {
for ik, iv := range queries {
if rk != ik && rv != iv {
flag = false
@ -54,7 +55,7 @@ func UseProxies(app *fiber.App) {
}
// Filter headers
for rk, rv := range rule.Headers {
for rk, rv := range location.Headers {
for ik, iv := range headers {
if rk == ik {
for _, ov := range iv {
@ -76,9 +77,12 @@ func UseProxies(app *fiber.App) {
continue
}
idx := rand.Intn(len(location.Destinations))
dest := location.Destinations[idx]
// Passing all the rules means the site is what we are looking for.
// Let us respond to our client!
return makeResponse(ctx, site)
return makeResponse(ctx, &dest)
}
}
@ -89,19 +93,19 @@ func UseProxies(app *fiber.App) {
})
}
func makeResponse(ctx *fiber.Ctx, site *navi.SiteConfig) error {
func makeResponse(ctx *fiber.Ctx, dest *navi.Destination) error {
// Modify request
for _, transformer := range site.Transformers {
for _, transformer := range dest.Transformers {
if err := transformer.TransformRequest(ctx); err != nil {
return err
}
}
// Forward
err := navi.App.Forward(ctx, site)
err := navi.R.Forward(ctx, dest)
// Modify response
for _, transformer := range site.Transformers {
for _, transformer := range dest.Transformers {
if err := transformer.TransformResponse(ctx); err != nil {
return err
}

View File

@ -4,34 +4,35 @@ import (
"io"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v2"
"github.com/pelletier/go-toml/v2"
)
var App *RoadApp
var R *RoadApp
func ReadInConfig(root string) error {
instance := &RoadApp{
Sites: []*SiteConfig{},
Regions: make([]*Region, 0),
}
if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error {
var site SiteConfig
if err := filepath.Walk(root, func(fp string, info os.FileInfo, _ error) error {
var region Region
if info.IsDir() {
return nil
} else if file, err := os.OpenFile(fp, os.O_RDONLY, 0755); err != nil {
return err
} else if data, err := io.ReadAll(file); err != nil {
return err
} else if err := yaml.Unmarshal(data, &site); err != nil {
} else if err := toml.Unmarshal(data, &region); err != nil {
return err
} else {
defer file.Close()
// Extract file name as site id
site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0]
instance.Sites = append(instance.Sites, &site)
if region.Disabled {
return nil
}
instance.Regions = append(instance.Regions, &region)
}
return nil
@ -39,7 +40,7 @@ func ReadInConfig(root string) error {
return err
}
App = instance
R = instance
return nil
}

View File

@ -18,16 +18,16 @@ import (
"github.com/valyala/fasthttp"
)
func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamInstance) error {
func makeHypertextResponse(c *fiber.Ctx, dest *Destination) error {
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond
return proxy.Do(c, upstream.MakeURI(c), &fasthttp.Client{
return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{
ReadTimeout: timeout,
WriteTimeout: timeout,
})
}
func makeFileResponse(c *fiber.Ctx, upstream *UpstreamInstance) error {
uri, queries := upstream.GetRawURI()
func makeFileResponse(c *fiber.Ctx, dest *Destination) error {
uri, queries := dest.GetRawUri()
root := http.Dir(uri)
method := c.Method()

View File

@ -1,49 +1,24 @@
package navi
import (
"errors"
"math/rand"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
"github.com/gofiber/fiber/v2"
)
type RoadApp struct {
Sites []*SiteConfig `json:"sites"`
Regions []*Region `json:"regions"`
}
func (v *RoadApp) Forward(ctx *fiber.Ctx, site *SiteConfig) error {
if len(site.Upstreams) == 0 {
return errors.New("invalid configuration")
}
// Do forward
idx := rand.Intn(len(site.Upstreams))
upstream := site.Upstreams[idx]
switch upstream.GetType() {
case UpstreamTypeHypertext:
return makeHypertextResponse(ctx, upstream)
case UpstreamTypeFile:
return makeFileResponse(ctx, upstream)
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error {
switch dest.GetType() {
case DestinationHypertext:
return makeHypertextResponse(ctx, dest)
case DestinationStaticFile:
return makeFileResponse(ctx, dest)
default:
return fiber.ErrBadGateway
}
}
type RequestTransformerConfig = transformers.RequestTransformerConfig
type SiteConfig struct {
ID string `json:"id"`
Rules []*RouterRule `json:"rules" yaml:"rules"`
Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"`
Upstreams []*UpstreamInstance `json:"upstreams" yaml:"upstreams"`
}
type RouterRule struct {
Host []string `json:"host" yaml:"host"`
Path []string `json:"path" yaml:"path"`
Queries map[string]string `json:"queries" yaml:"queries"`
Headers map[string][]string `json:"headers" yaml:"headers"`
}
type RequestTransformerConfig = transformers.TransformerConfig

78
pkg/navi/struct.go Normal file
View File

@ -0,0 +1,78 @@
package navi
import (
"fmt"
"net/url"
"strings"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
type Region struct {
ID string `json:"id" toml:"id"`
Disabled bool `json:"disabled" toml:"disabled"`
Locations []Location `json:"locations" toml:"locations"`
Applications []warden.Application `json:"applications" toml:"applications"`
}
type Location struct {
ID string `json:"id" toml:"id"`
Host []string `json:"host" toml:"host"`
Path []string `json:"path" toml:"path"`
Queries map[string]string `json:"queries" toml:"queries"`
Headers map[string][]string `json:"headers" toml:"headers"`
Destinations []Destination `json:"destinations" toml:"destinations"`
}
type DestinationType = int8
const (
DestinationHypertext = DestinationType(iota)
DestinationStaticFile
DestinationUnknown
)
type Destination struct {
ID string `json:"id" toml:"id"`
Uri string `json:"uri" toml:"uri"`
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
}
func (v *Destination) GetType() DestinationType {
protocol := strings.SplitN(v.Uri, "://", 2)[0]
switch protocol {
case "http", "https":
return DestinationHypertext
case "file", "files":
return DestinationStaticFile
}
return DestinationUnknown
}
func (v *Destination) GetRawUri() (string, url.Values) {
uri := strings.SplitN(v.Uri, "://", 2)[1]
data := strings.SplitN(uri, "?", 2)
data = append(data, " ") // Make data array least have two element
qs, _ := url.ParseQuery(data[0])
return data[0], qs
}
func (v *Destination) MakeUri(ctx *fiber.Ctx) string {
var queries []string
for k, v := range ctx.Queries() {
parsed, _ := url.QueryUnescape(v)
value := url.QueryEscape(parsed)
queries = append(queries, fmt.Sprintf("%s=%s", k, value))
}
path := string(ctx.Request().URI().Path())
hash := string(ctx.Request().URI().Hash())
return v.Uri + path +
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
lo.Ternary(len(hash) > 0, "#"+hash, "")
}

View File

@ -5,13 +5,13 @@ import (
"github.com/valyala/fasthttp"
)
var CompressResponse = RequestTransformer{
var CompressResponse = Transformer{
ModifyResponse: func(options any, ctx *fiber.Ctx) error {
opts := DeserializeOptions[struct {
Level int `json:"level" yaml:"level"`
Level int `json:"level" toml:"level"`
}](options)
var fctx = func(c *fasthttp.RequestCtx) {}
fctx := func(c *fasthttp.RequestCtx) {}
var compressor fasthttp.RequestHandler
switch opts.Level {
// Best Speed Mode

View File

@ -9,17 +9,17 @@ import (
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type RequestTransformer struct {
type Transformer struct {
ModifyRequest func(options any, ctx *fiber.Ctx) error
ModifyResponse func(options any, ctx *fiber.Ctx) error
}
type RequestTransformerConfig struct {
Type string `json:"type" yaml:"type"`
Options any `json:"options" yaml:"options"`
type TransformerConfig struct {
Type string `json:"type" toml:"type"`
Options any `json:"options" toml:"options"`
}
func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
func (v *TransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
for k, f := range Transformers {
if k == v.Type {
if f.ModifyRequest != nil {
@ -31,7 +31,7 @@ func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
return nil
}
func (v *RequestTransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
func (v *TransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
for k, f := range Transformers {
if k == v.Type {
if f.ModifyResponse != nil {
@ -55,7 +55,7 @@ func DeserializeOptions[T any](data any) T {
// Map of Transformers
// Every transformer need to be mapped here so that they can get work.
var Transformers = map[string]RequestTransformer{
var Transformers = map[string]Transformer{
"replacePath": ReplacePath,
"compressResponse": CompressResponse,
}

View File

@ -1,18 +1,19 @@
package transformers
import (
"github.com/gofiber/fiber/v2"
"regexp"
"strings"
"github.com/gofiber/fiber/v2"
)
var ReplacePath = RequestTransformer{
var ReplacePath = Transformer{
ModifyRequest: func(options any, ctx *fiber.Ctx) error {
opts := DeserializeOptions[struct {
Pattern string `json:"pattern" yaml:"pattern"`
Value string `json:"value" yaml:"value"`
Repl string `json:"repl" yaml:"repl"` // Use when complex mode(regexp) enabled
Complex bool `json:"complex" yaml:"complex"`
Pattern string `json:"pattern" toml:"pattern"`
Value string `json:"value" toml:"value"`
Repl string `json:"repl" toml:"repl"` // Use when complex mode(regexp) enabled
Complex bool `json:"complex" toml:"complex"`
}](options)
path := string(ctx.Request().URI().Path())
if !opts.Complex {

View File

@ -1,58 +0,0 @@
package navi
import (
"fmt"
"net/url"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
const (
UpstreamTypeFile = "file"
UpstreamTypeHypertext = "hypertext"
UpstreamTypeUnknown = "unknown"
)
type UpstreamInstance struct {
ID string `json:"id" yaml:"id"`
URI string `json:"uri" yaml:"uri"`
}
func (v *UpstreamInstance) GetType() string {
protocol := strings.SplitN(v.URI, "://", 2)[0]
switch protocol {
case "file", "files":
return UpstreamTypeFile
case "http", "https":
return UpstreamTypeHypertext
}
return UpstreamTypeUnknown
}
func (v *UpstreamInstance) GetRawURI() (string, url.Values) {
uri := strings.SplitN(v.URI, "://", 2)[1]
data := strings.SplitN(uri, "?", 2)
data = append(data, " ") // Make data array least have two element
qs, _ := url.ParseQuery(data[0])
return data[0], qs
}
func (v *UpstreamInstance) MakeURI(ctx *fiber.Ctx) string {
var queries []string
for k, v := range ctx.Queries() {
parsed, _ := url.QueryUnescape(v)
value := url.QueryEscape(parsed)
queries = append(queries, fmt.Sprintf("%s=%s", k, value))
}
path := string(ctx.Request().URI().Path())
hash := string(ctx.Request().URI().Hash())
return v.URI + path +
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
lo.Ternary(len(hash) > 0, "#"+hash, "")
}

17
pkg/navi/warden.go Normal file
View File

@ -0,0 +1,17 @@
package navi
import "code.smartsheep.studio/goatworks/roadsign/pkg/warden"
func InitializeWarden(regions []*Region) {
for _, region := range regions {
for _, application := range region.Applications {
warden.InstancePool = append(warden.InstancePool, &warden.AppInstance{
Manifest: application,
})
}
}
for _, instance := range warden.InstancePool {
instance.Wake()
}
}

View File

@ -0,0 +1,26 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func getApplications(c *fiber.Ctx) error {
applications := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []warden.Application {
return item.Applications
})
return c.JSON(applications)
}
func getApplicationLogs(c *fiber.Ctx) error {
if instance, ok := lo.Find(warden.InstancePool, func(item *warden.AppInstance) bool {
return item.Manifest.ID == c.Params("id")
}); !ok {
return fiber.NewError(fiber.StatusNotFound)
} else {
return c.SendString(instance.Logs())
}
}

View File

@ -5,7 +5,7 @@ import (
"github.com/gofiber/fiber/v2"
)
func responseConnectivity(c *fiber.Ctx) error {
func getMetadata(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"server": "RoadSign",
"version": roadsign.AppVersion,

View File

@ -1,29 +0,0 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func getProcesses(c *fiber.Ctx) error {
processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance {
return item.Processes
})
return c.JSON(processes)
}
func getProcessLog(c *fiber.Ctx) error {
processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance {
return item.Processes
})
if target, ok := lo.Find(processes, func(item *sign.ProcessInstance) bool {
return item.ID == c.Params("id")
}); !ok {
return fiber.NewError(fiber.StatusNotFound)
} else {
return c.SendString(target.GetLogs())
}
}

View File

@ -1,6 +1,7 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"context"
"os"
"path/filepath"
@ -14,23 +15,23 @@ import (
func doPublish(c *fiber.Ctx) error {
var workdir string
var site *sign.SiteConfig
var upstream *sign.UpstreamInstance
var process *sign.ProcessInstance
for _, item := range navi.App.Sites {
var destination *navi.Destination
var application *warden.Application
for _, item := range navi.R.Regions {
if item.ID == c.Params("site") {
site = item
for _, stream := range item.Upstreams {
if stream.ID == c.Params("slug") {
upstream = stream
workdir, _ = stream.GetRawURI()
break
for _, location := range item.Locations {
for _, dest := range location.Destinations {
if dest.ID == c.Params("slug") {
destination = &dest
workdir, _ = dest.GetRawUri()
break
}
}
}
for _, proc := range item.Processes {
if proc.ID == c.Params("slug") {
process = proc
workdir = proc.Workdir
for _, app := range item.Applications {
if app.ID == c.Params("slug") {
application = &app
workdir = app.Workdir
break
}
}
@ -38,14 +39,15 @@ func doPublish(c *fiber.Ctx) error {
}
}
if upstream == nil && process == nil {
return fiber.ErrNotFound
} else if upstream != nil && upstream.GetType() != navi.UpstreamTypeFile {
var instance *warden.AppInstance
if application != nil {
if instance = warden.GetFromPool(application.ID); instance != nil {
instance.Stop()
}
} else if destination != nil && destination.GetType() != navi.DestinationStaticFile {
return fiber.ErrUnprocessableEntity
}
for _, process := range site.Processes {
process.StopProcess()
} else {
return fiber.ErrNotFound
}
if c.Query("overwrite", "yes") == "yes" {
@ -81,5 +83,9 @@ func doPublish(c *fiber.Ctx) error {
}
}
if instance != nil {
instance.Wake()
}
return c.SendStatus(fiber.StatusOK)
}

67
pkg/sideload/regions.go Normal file
View File

@ -0,0 +1,67 @@
package sideload
import (
"fmt"
"os"
"path/filepath"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/pelletier/go-toml/v2"
"github.com/samber/lo"
"github.com/spf13/viper"
)
func getRegions(c *fiber.Ctx) error {
return c.JSON(navi.R.Regions)
}
func getRegionConfig(c *fiber.Ctx) error {
fp := filepath.Join(viper.GetString("paths.configs"), c.Params("id"))
var err error
var data []byte
if data, err = os.ReadFile(fp + ".toml"); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
return c.Type("toml").SendString(string(data))
}
func doSync(c *fiber.Ctx) error {
req := string(c.Body())
id := c.Params("slug")
path := filepath.Join(viper.GetString("paths.configs"), fmt.Sprintf("%s.toml", id))
if file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil {
return fiber.NewError(fiber.ErrInternalServerError.Code, err.Error())
} else {
raw, _ := toml.Marshal(req)
file.Write(raw)
defer file.Close()
}
var rebootQueue []*warden.AppInstance
if region, ok := lo.Find(navi.R.Regions, func(item *navi.Region) bool {
return item.ID == id
}); ok {
for _, application := range region.Applications {
if instance := warden.GetFromPool(application.ID); instance != nil {
instance.Stop()
rebootQueue = append(rebootQueue, instance)
}
}
}
// Reload
navi.ReadInConfig(viper.GetString("paths.configs"))
// Reboot
for _, instance := range rebootQueue {
instance.Wake()
}
return c.SendStatus(fiber.StatusOK)
}

View File

@ -1,11 +1,12 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view"
"fmt"
"net/http"
"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view"
"github.com/gofiber/fiber/v2/middleware/filesystem"
jsoniter "github.com/json-iterator/go"
"net/http"
roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
"github.com/gofiber/fiber/v2"
@ -51,18 +52,18 @@ func InitSideload() *fiber.App {
cgi := app.Group("/cgi").Name("CGI")
{
cgi.All("/connectivity", responseConnectivity)
cgi.Get("/metadata", getMetadata)
cgi.Get("/statistics", getStatistics)
cgi.Get("/sites", getSites)
cgi.Get("/sites/cfg/:id", getSiteConfig)
cgi.Get("/processes", getProcesses)
cgi.Get("/processes/logs/:id", getProcessLog)
cgi.Get("/sites", getRegions)
cgi.Get("/sites/cfg/:id", getRegionConfig)
cgi.Get("/processes", getApplications)
cgi.Get("/processes/logs/:id", getApplicationLogs)
}
webhooks := app.Group("/webhooks").Name("WebHooks")
{
webhooks.Put("/publish/:site/:slug", doPublish)
webhooks.Put("/sync/:slug", doSyncSite)
webhooks.Put("/sync/:slug", doSync)
}
return app

View File

@ -1,64 +0,0 @@
package sideload
import (
"fmt"
"os"
"path/filepath"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)
func getSites(c *fiber.Ctx) error {
return c.JSON(sign.App.Sites)
}
func getSiteConfig(c *fiber.Ctx) error {
fp := filepath.Join(viper.GetString("paths.configs"), c.Params("id"))
var err error
var data []byte
if data, err = os.ReadFile(fp + ".yml"); err != nil {
if data, err = os.ReadFile(fp + ".yaml"); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
}
return c.Type("yaml").SendString(string(data))
}
func doSyncSite(c *fiber.Ctx) error {
var req navi.SiteConfig
if err := c.BodyParser(&req); err != nil {
return err
}
id := c.Params("slug")
path := filepath.Join(viper.GetString("paths.configs"), fmt.Sprintf("%s.yaml", id))
if file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil {
return fiber.NewError(fiber.ErrInternalServerError.Code, err.Error())
} else {
raw, _ := yaml.Marshal(req)
file.Write(raw)
defer file.Close()
}
if site, ok := lo.Find(sign.App.Sites, func(item *sign.SiteConfig) bool {
return item.ID == id
}); ok {
for _, process := range site.Processes {
process.StopProcess()
}
}
// Reload
sign.ReadInConfig(viper.GetString("paths.configs"))
sign.App.PreheatProcesses()
return c.SendStatus(fiber.StatusOK)
}

View File

@ -1,27 +1,27 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func getStatistics(c *fiber.Ctx) error {
upstreams := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.UpstreamInstance {
return item.Upstreams
locations := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []navi.Location {
return item.Locations
})
processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance {
return item.Processes
destinations := lo.FlatMap(locations, func(item navi.Location, idx int) []navi.Destination {
return item.Destinations
})
unhealthy := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance {
return lo.Filter(item.Processes, func(item *sign.ProcessInstance, idx int) bool {
return item.Status != navi.ProcessStarted
})
applications := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []warden.Application {
return item.Applications
})
return c.JSON(fiber.Map{
"sites": len(sign.App.Sites),
"upstreams": len(upstreams),
"processes": len(processes),
"status": len(unhealthy) == 0,
"regions": len(navi.R.Regions),
"locations": len(locations),
"destinations": len(destinations),
"applications": len(applications),
})
}

View File

@ -11,6 +11,25 @@ import (
"github.com/samber/lo"
)
var InstancePool []*AppInstance
func GetFromPool(id string) *AppInstance {
val, ok := lo.Find(InstancePool, func(item *AppInstance) bool {
return item.Manifest.ID == id
})
return lo.Ternary(ok, val, nil)
}
func StartPool() []error {
var errors []error
for _, instance := range InstancePool {
if err := instance.Wake(); err != nil {
errors = append(errors, err)
}
}
return errors
}
type AppStatus = int8
const (
@ -21,8 +40,8 @@ const (
AppFailure
)
type WardenInstance struct {
Manifest WardenApplication `json:"manifest"`
type AppInstance struct {
Manifest Application `json:"manifest"`
Cmd *exec.Cmd `json:"-"`
Logger strings.Builder `json:"-"`
@ -30,7 +49,7 @@ type WardenInstance struct {
Status AppStatus `json:"status"`
}
func (v *WardenInstance) Wake() error {
func (v *AppInstance) Wake() error {
if v.Cmd != nil {
return nil
}
@ -52,7 +71,7 @@ func (v *WardenInstance) Wake() error {
}
}
func (v *WardenInstance) Start() error {
func (v *AppInstance) Start() error {
manifest := v.Manifest
if len(manifest.Command) <= 0 {
@ -83,7 +102,7 @@ func (v *WardenInstance) Start() error {
return v.Cmd.Start()
}
func (v *WardenInstance) Stop() error {
func (v *AppInstance) Stop() error {
if v.Cmd != nil && v.Cmd.Process != nil {
if err := v.Cmd.Process.Signal(os.Interrupt); err != nil {
v.Cmd.Process.Kill()
@ -96,6 +115,6 @@ func (v *WardenInstance) Stop() error {
return nil
}
func (v *WardenInstance) Logs() string {
func (v *AppInstance) Logs() string {
return v.Logger.String()
}

View File

@ -1,8 +1,8 @@
package warden
type WardenApplication struct {
ID string `json:"id" yaml:"id"`
Workdir string `json:"workdir" yaml:"workdir"`
Command []string `json:"command" yaml:"command"`
Environment []string `json:"environment" yaml:"environment"`
type Application struct {
ID string `json:"id" toml:"id"`
Workdir string `json:"workdir" toml:"workdir"`
Command []string `json:"command" toml:"command"`
Environment []string `json:"environment" toml:"environment"`
}