✨ Uses v2 configration schema
This commit is contained in:
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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, ®ion); 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, ®ion)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -39,7 +40,7 @@ func ReadInConfig(root string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
App = instance
|
||||
R = instance
|
||||
|
||||
return nil
|
||||
}
|
@ -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()
|
||||
|
@ -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
78
pkg/navi/struct.go
Normal 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, "")
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
17
pkg/navi/warden.go
Normal 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()
|
||||
}
|
||||
}
|
26
pkg/sideload/applications.go
Normal file
26
pkg/sideload/applications.go
Normal 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())
|
||||
}
|
||||
}
|
@ -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,
|
@ -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())
|
||||
}
|
||||
}
|
@ -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
67
pkg/sideload/regions.go
Normal 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)
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
Reference in New Issue
Block a user