♻️ RoadSign v2 #6
@ -2,13 +2,14 @@ package deploy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds/conn"
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -72,7 +73,7 @@ var DeployCommands = []*cli.Command{
|
||||
return fmt.Errorf("couldn't connect server: %s", err.Error())
|
||||
}
|
||||
|
||||
var site sign.SiteConfig
|
||||
var site navi.SiteConfig
|
||||
if file, err := os.Open(ctx.Args().Get(2)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
|
||||
"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"
|
||||
@ -44,7 +45,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Load & init sign
|
||||
if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil {
|
||||
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.")
|
||||
|
@ -3,7 +3,7 @@ package hypertext
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
|
||||
"code.smartsheep.studio/goatworks/roadnavi/pkg/navi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@ -16,7 +16,7 @@ func UseProxies(app *fiber.App) {
|
||||
headers := ctx.GetReqHeaders()
|
||||
|
||||
// Filtering sites
|
||||
for _, site := range sign.App.Sites {
|
||||
for _, site := range navi.App.Sites {
|
||||
// Matching rules
|
||||
for _, rule := range site.Rules {
|
||||
if !lo.Contains(rule.Host, host) {
|
||||
@ -89,7 +89,7 @@ func UseProxies(app *fiber.App) {
|
||||
})
|
||||
}
|
||||
|
||||
func makeResponse(ctx *fiber.Ctx, site *sign.SiteConfig) error {
|
||||
func makeResponse(ctx *fiber.Ctx, site *navi.SiteConfig) error {
|
||||
// Modify request
|
||||
for _, transformer := range site.Transformers {
|
||||
if err := transformer.TransformRequest(ctx); err != nil {
|
||||
@ -98,7 +98,7 @@ func makeResponse(ctx *fiber.Ctx, site *sign.SiteConfig) error {
|
||||
}
|
||||
|
||||
// Forward
|
||||
err := sign.App.Forward(ctx, site)
|
||||
err := navi.App.Forward(ctx, site)
|
||||
|
||||
// Modify response
|
||||
for _, transformer := range site.Transformers {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package sign
|
||||
package navi
|
||||
|
||||
import (
|
||||
"io"
|
@ -1,4 +1,4 @@
|
||||
package sign
|
||||
package navi
|
||||
|
||||
import (
|
||||
"errors"
|
@ -1,12 +1,12 @@
|
||||
package sign
|
||||
package navi
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/sign/transformers"
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RoadApp struct {
|
||||
@ -18,14 +18,6 @@ func (v *RoadApp) Forward(ctx *fiber.Ctx, site *SiteConfig) error {
|
||||
return errors.New("invalid configuration")
|
||||
}
|
||||
|
||||
// Boot processes
|
||||
for _, process := range site.Processes {
|
||||
if err := process.BootProcess(); err != nil {
|
||||
log.Warn().Err(err).Msgf("An error occurred when booting process (%s) for %s", process.ID, site.ID)
|
||||
return fiber.ErrBadGateway
|
||||
}
|
||||
}
|
||||
|
||||
// Do forward
|
||||
idx := rand.Intn(len(site.Upstreams))
|
||||
upstream := site.Upstreams[idx]
|
||||
@ -47,7 +39,6 @@ type SiteConfig struct {
|
||||
Rules []*RouterRule `json:"rules" yaml:"rules"`
|
||||
Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"`
|
||||
Upstreams []*UpstreamInstance `json:"upstreams" yaml:"upstreams"`
|
||||
Processes []*ProcessInstance `json:"processes" yaml:"processes"`
|
||||
}
|
||||
|
||||
type RouterRule struct {
|
@ -1,4 +1,4 @@
|
||||
package sign
|
||||
package navi
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/lo"
|
||||
@ -17,7 +17,7 @@ func doPublish(c *fiber.Ctx) error {
|
||||
var site *sign.SiteConfig
|
||||
var upstream *sign.UpstreamInstance
|
||||
var process *sign.ProcessInstance
|
||||
for _, item := range sign.App.Sites {
|
||||
for _, item := range navi.App.Sites {
|
||||
if item.ID == c.Params("site") {
|
||||
site = item
|
||||
for _, stream := range item.Upstreams {
|
||||
@ -40,7 +40,7 @@ func doPublish(c *fiber.Ctx) error {
|
||||
|
||||
if upstream == nil && process == nil {
|
||||
return fiber.ErrNotFound
|
||||
} else if upstream != nil && upstream.GetType() != sign.UpstreamTypeFile {
|
||||
} else if upstream != nil && upstream.GetType() != navi.UpstreamTypeFile {
|
||||
return fiber.ErrUnprocessableEntity
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"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"
|
||||
@ -31,7 +32,7 @@ func getSiteConfig(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func doSyncSite(c *fiber.Ctx) error {
|
||||
var req sign.SiteConfig
|
||||
var req navi.SiteConfig
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return err
|
||||
|
@ -1,7 +1,6 @@
|
||||
package sideload
|
||||
|
||||
import (
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@ -15,7 +14,7 @@ func getStatistics(c *fiber.Ctx) error {
|
||||
})
|
||||
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 != sign.ProcessStarted
|
||||
return item.Status != navi.ProcessStarted
|
||||
})
|
||||
})
|
||||
|
||||
|
2907
pkg/sideload/view/yarn.lock
Normal file
2907
pkg/sideload/view/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
144
pkg/sign/pm.go
144
pkg/sign/pm.go
@ -1,144 +0,0 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProcessStatus = int8
|
||||
|
||||
const (
|
||||
ProcessCreated = ProcessStatus(iota)
|
||||
ProcessStarting
|
||||
ProcessStarted
|
||||
ProcessExited
|
||||
ProcessFailure
|
||||
)
|
||||
|
||||
type ProcessInstance 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"`
|
||||
Prepares [][]string `json:"prepares" yaml:"prepares"`
|
||||
Preheat bool `json:"preheat" yaml:"preheat"`
|
||||
|
||||
Cmd *exec.Cmd `json:"-"`
|
||||
Logger strings.Builder `json:"-"`
|
||||
|
||||
Status ProcessStatus `json:"status"`
|
||||
}
|
||||
|
||||
func (v *ProcessInstance) BootProcess() error {
|
||||
if v.Cmd != nil {
|
||||
return nil
|
||||
}
|
||||
if err := v.PrepareProcess(); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Cmd == nil {
|
||||
return v.StartProcess()
|
||||
}
|
||||
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
|
||||
return v.StartProcess()
|
||||
}
|
||||
if v.Cmd.ProcessState.Exited() {
|
||||
return v.StartProcess()
|
||||
} else if v.Cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("process already dead")
|
||||
}
|
||||
if v.Cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("cannot start process")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *ProcessInstance) PrepareProcess() error {
|
||||
for _, script := range v.Prepares {
|
||||
if len(script) <= 0 {
|
||||
continue
|
||||
}
|
||||
cmd := exec.Command(script[0], script[1:]...)
|
||||
cmd.Dir = filepath.Join(v.Workdir)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ProcessInstance) StartProcess() error {
|
||||
if len(v.Command) <= 0 {
|
||||
return fmt.Errorf("you need set the command for %s to enable process manager", v.ID)
|
||||
}
|
||||
|
||||
v.Cmd = exec.Command(v.Command[0], v.Command[1:]...)
|
||||
v.Cmd.Dir = filepath.Join(v.Workdir)
|
||||
v.Cmd.Env = append(v.Cmd.Env, v.Environment...)
|
||||
v.Cmd.Stdout = &v.Logger
|
||||
v.Cmd.Stderr = &v.Logger
|
||||
|
||||
// Monitor
|
||||
go func() {
|
||||
for {
|
||||
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
|
||||
v.Status = ProcessStarting
|
||||
} else if !v.Cmd.ProcessState.Exited() {
|
||||
v.Status = ProcessStarted
|
||||
} else {
|
||||
v.Status = lo.Ternary(v.Cmd.ProcessState.Success(), ProcessExited, ProcessFailure)
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
return v.Cmd.Start()
|
||||
}
|
||||
|
||||
func (v *ProcessInstance) StopProcess() error {
|
||||
if v.Cmd != nil && v.Cmd.Process != nil {
|
||||
if err := v.Cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
v.Cmd.Process.Kill()
|
||||
return err
|
||||
} else {
|
||||
v.Cmd = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ProcessInstance) GetLogs() string {
|
||||
return v.Logger.String()
|
||||
}
|
||||
|
||||
func (v *RoadApp) PreheatProcesses(callbacks ...func(total int, success int)) {
|
||||
var processes []*ProcessInstance
|
||||
for _, site := range v.Sites {
|
||||
for _, process := range site.Processes {
|
||||
if process.Preheat {
|
||||
processes = append(processes, process)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := 0
|
||||
for _, process := range processes {
|
||||
if process.BootProcess() == nil {
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
if len(callbacks) > 0 {
|
||||
for _, callback := range callbacks {
|
||||
callback(len(processes), success)
|
||||
}
|
||||
}
|
||||
}
|
101
pkg/warden/executor.go
Normal file
101
pkg/warden/executor.go
Normal file
@ -0,0 +1,101 @@
|
||||
package warden
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type AppStatus = int8
|
||||
|
||||
const (
|
||||
AppCreated = AppStatus(iota)
|
||||
AppStarting
|
||||
AppStarted
|
||||
AppExited
|
||||
AppFailure
|
||||
)
|
||||
|
||||
type WardenInstance struct {
|
||||
Manifest WardenApplication `json:"manifest"`
|
||||
|
||||
Cmd *exec.Cmd `json:"-"`
|
||||
Logger strings.Builder `json:"-"`
|
||||
|
||||
Status AppStatus `json:"status"`
|
||||
}
|
||||
|
||||
func (v *WardenInstance) Wake() error {
|
||||
if v.Cmd != nil {
|
||||
return nil
|
||||
}
|
||||
if v.Cmd == nil {
|
||||
return v.Start()
|
||||
}
|
||||
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
|
||||
return v.Start()
|
||||
}
|
||||
if v.Cmd.ProcessState.Exited() {
|
||||
return v.Start()
|
||||
} else if v.Cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("process already dead")
|
||||
}
|
||||
if v.Cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("cannot start process")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *WardenInstance) Start() error {
|
||||
manifest := v.Manifest
|
||||
|
||||
if len(manifest.Command) <= 0 {
|
||||
return fmt.Errorf("you need set the command for %s to enable process manager", manifest.ID)
|
||||
}
|
||||
|
||||
v.Cmd = exec.Command(manifest.Command[0], manifest.Command[1:]...)
|
||||
v.Cmd.Dir = filepath.Join(manifest.Workdir)
|
||||
v.Cmd.Env = append(v.Cmd.Env, manifest.Environment...)
|
||||
v.Cmd.Stdout = &v.Logger
|
||||
v.Cmd.Stderr = &v.Logger
|
||||
|
||||
// Monitor
|
||||
go func() {
|
||||
for {
|
||||
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
|
||||
v.Status = AppStarting
|
||||
} else if !v.Cmd.ProcessState.Exited() {
|
||||
v.Status = AppStarted
|
||||
} else {
|
||||
v.Status = lo.Ternary(v.Cmd.ProcessState.Success(), AppExited, AppFailure)
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
return v.Cmd.Start()
|
||||
}
|
||||
|
||||
func (v *WardenInstance) Stop() error {
|
||||
if v.Cmd != nil && v.Cmd.Process != nil {
|
||||
if err := v.Cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
v.Cmd.Process.Kill()
|
||||
return err
|
||||
} else {
|
||||
v.Cmd = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *WardenInstance) Logs() string {
|
||||
return v.Logger.String()
|
||||
}
|
8
pkg/warden/manifest.go
Normal file
8
pkg/warden/manifest.go
Normal file
@ -0,0 +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"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user