nano-run/server/runner/handler.go

237 lines
5.7 KiB
Go
Raw Normal View History

2020-09-28 13:46:37 +00:00
package runner
2020-09-10 10:11:34 +00:00
import (
"context"
2020-10-06 07:03:07 +00:00
"html/template"
2020-09-10 10:11:34 +00:00
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
2020-10-01 13:09:40 +00:00
"github.com/Masterminds/sprig"
2020-09-28 13:46:37 +00:00
"github.com/gin-gonic/gin"
2020-11-07 13:38:13 +00:00
"github.com/robfig/cron/v3"
2020-09-10 10:11:34 +00:00
"gopkg.in/yaml.v2"
2020-09-28 13:46:37 +00:00
"nano-run/server"
2020-10-06 07:03:07 +00:00
embedded_static "nano-run/server/runner/embedded/static"
embedded_templates "nano-run/server/runner/embedded/templates"
2020-09-28 13:46:37 +00:00
"nano-run/server/ui"
"nano-run/worker"
2020-09-10 10:11:34 +00:00
)
2020-10-06 07:03:07 +00:00
//go:generate go-bindata -pkg templates -o embedded/templates/bindata.go -nometadata -prefix ../../templates/ ../../templates/
//go:generate go-bindata -fs -pkg static -o embedded/static/bindata.go -prefix ../../templates/static/ ../../templates/static/...
2020-09-10 10:11:34 +00:00
type Config struct {
2020-10-01 13:09:40 +00:00
UIDirectory string `yaml:"ui_directory"`
WorkingDirectory string `yaml:"working_directory"`
ConfigDirectory string `yaml:"config_directory"`
Bind string `yaml:"bind"`
GracefulShutdown time.Duration `yaml:"graceful_shutdown"`
2020-10-06 07:03:07 +00:00
DisableUI bool `yaml:"disable_ui"`
2020-10-01 13:09:40 +00:00
Auth ui.Authorization `yaml:"auth,omitempty"`
2020-09-10 10:11:34 +00:00
TLS struct {
Enable bool `yaml:"enable"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
} `yaml:"tls,omitempty"`
}
const (
defaultGracefulShutdown = 5 * time.Second
defaultBind = "127.0.0.1:8989"
)
func DefaultConfig() Config {
var cfg Config
cfg.Bind = defaultBind
cfg.WorkingDirectory = filepath.Join("run")
cfg.ConfigDirectory = filepath.Join("conf.d")
2020-09-28 13:46:37 +00:00
cfg.UIDirectory = filepath.Join("ui")
2020-09-10 10:11:34 +00:00
cfg.GracefulShutdown = defaultGracefulShutdown
return cfg
}
func (cfg Config) CreateDirs() error {
err := os.MkdirAll(cfg.WorkingDirectory, 0755)
if err != nil {
return err
}
return os.MkdirAll(cfg.ConfigDirectory, 0755)
}
func (cfg *Config) LoadFile(file string) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
err = yaml.Unmarshal(data, cfg)
if err != nil {
return err
}
if !filepath.IsAbs(cfg.WorkingDirectory) {
cfg.WorkingDirectory = filepath.Join(filepath.Dir(file), cfg.WorkingDirectory)
}
if !filepath.IsAbs(cfg.ConfigDirectory) {
cfg.ConfigDirectory = filepath.Join(filepath.Dir(file), cfg.ConfigDirectory)
}
return nil
}
func (cfg Config) SaveFile(file string) error {
data, err := yaml.Marshal(cfg)
if err != nil {
return err
}
return ioutil.WriteFile(file, data, 0600)
}
func (cfg Config) Create(global context.Context) (*Server, error) {
2020-09-28 13:46:37 +00:00
units, err := server.Units(cfg.ConfigDirectory)
2020-09-10 10:11:34 +00:00
if err != nil {
return nil, err
2020-09-10 10:11:34 +00:00
}
2020-09-28 13:46:37 +00:00
workers, err := server.Workers(cfg.WorkingDirectory, units)
2020-09-10 10:11:34 +00:00
if err != nil {
return nil, err
2020-09-10 10:11:34 +00:00
}
2020-11-07 13:38:13 +00:00
cronEntries, cronEngine, err := server.Cron(workers, units)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(global)
2020-09-28 13:46:37 +00:00
router := gin.Default()
router.Use(func(gctx *gin.Context) {
gctx.Request = gctx.Request.WithContext(global)
gctx.Next()
})
2020-10-06 07:03:07 +00:00
cfg.installUI(router, units, workers)
2020-10-01 13:09:40 +00:00
server.Attach(router.Group("/api/"), units, workers)
2020-09-28 13:46:37 +00:00
srv := &Server{
2020-11-07 13:38:13 +00:00
Handler: router,
workers: workers,
cronEngine: cronEngine,
cronEntries: cronEntries,
units: units,
done: make(chan struct{}),
cancel: cancel,
}
go srv.run(ctx)
return srv, nil
}
func (cfg Config) Run(global context.Context) error {
2020-09-10 10:11:34 +00:00
ctx, cancel := context.WithCancel(global)
defer cancel()
srv, err := cfg.Create(global)
if err != nil {
return err
}
defer srv.Close()
2020-09-10 10:11:34 +00:00
2020-10-01 13:09:40 +00:00
httpServer := http.Server{
2020-09-10 10:11:34 +00:00
Addr: cfg.Bind,
Handler: srv,
2020-09-10 10:11:34 +00:00
}
done := make(chan struct{})
2020-09-10 10:11:34 +00:00
go func() {
defer cancel()
<-ctx.Done()
t, c := context.WithTimeout(context.Background(), cfg.GracefulShutdown)
2020-10-01 13:09:40 +00:00
_ = httpServer.Shutdown(t)
2020-09-10 10:11:34 +00:00
c()
close(done)
2020-09-10 10:11:34 +00:00
}()
if cfg.TLS.Enable {
2020-10-01 13:09:40 +00:00
err = httpServer.ListenAndServeTLS(cfg.TLS.Cert, cfg.TLS.Key)
2020-09-10 10:11:34 +00:00
} else {
2020-10-01 13:09:40 +00:00
err = httpServer.ListenAndServe()
2020-09-10 10:11:34 +00:00
}
cancel()
<-done
2020-09-19 12:03:39 +00:00
return err
2020-09-10 10:11:34 +00:00
}
2020-10-06 07:03:07 +00:00
func (cfg Config) installUI(router *gin.Engine, units []server.Unit, workers []*worker.Worker) {
if cfg.DisableUI {
log.Println("ui disabled")
return
}
uiPath := filepath.Join(cfg.UIDirectory, "*.html")
uiGroup := router.Group("/ui/")
if v, err := filepath.Glob(uiPath); err == nil && len(v) > 0 {
cfg.useDirectoryUI(router, uiGroup)
} else {
log.Println("using embedded UI")
cfg.useEmbeddedUI(router, uiGroup)
}
router.GET("/", func(gctx *gin.Context) {
gctx.Redirect(http.StatusTemporaryRedirect, "ui")
})
ui.Attach(uiGroup, units, workers, cfg.Auth)
}
func (cfg Config) useDirectoryUI(router *gin.Engine, uiGroup gin.IRouter) {
uiPath := filepath.Join(cfg.UIDirectory, "*.html")
router.SetFuncMap(sprig.HtmlFuncMap())
router.LoadHTMLGlob(uiPath)
uiGroup.Static("/static/", filepath.Join(cfg.UIDirectory, "static"))
}
func (cfg Config) useEmbeddedUI(router *gin.Engine, uiGroup gin.IRouter) {
root := template.New("").Funcs(sprig.HtmlFuncMap())
for _, src := range embedded_templates.AssetNames() {
_, _ = root.New(src).Parse(string(embedded_templates.MustAsset(src)))
}
router.SetHTMLTemplate(root)
uiGroup.StaticFS("/static/", embedded_static.AssetFile())
}
type Server struct {
http.Handler
2020-11-07 13:38:13 +00:00
workers []*worker.Worker
units []server.Unit
cronEntries []*server.CronEntry
cronEngine *cron.Cron
cancel func()
done chan struct{}
err error
}
2020-09-28 13:46:37 +00:00
func (srv *Server) Units() []server.Unit { return srv.units }
2020-11-07 13:38:13 +00:00
func (srv *Server) Workers() []*worker.Worker { return srv.workers }
func (srv *Server) Close() {
for _, wrk := range srv.workers {
wrk.Close()
}
srv.cancel()
2020-11-07 13:38:13 +00:00
<-srv.cronEngine.Stop().Done()
<-srv.done
}
func (srv *Server) Err() error {
return srv.err
}
func (srv *Server) run(ctx context.Context) {
2020-11-07 13:38:13 +00:00
srv.cronEngine.Start()
2020-09-28 13:46:37 +00:00
err := server.Run(ctx, srv.workers)
if err != nil {
log.Println("workers stopped:", err)
}
srv.err = err
close(srv.done)
}