Files
chorus/admin/cmd/server.go
T

165 lines
5.1 KiB
Go

package cmd
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"git.ilapage.cn/OPC/chorus/admin/app/admin/router"
"git.ilapage.cn/OPC/chorus/admin/app/chorus"
"git.ilapage.cn/OPC/chorus/admin/common/database"
common "git.ilapage.cn/OPC/chorus/admin/common/middleware"
ext "git.ilapage.cn/OPC/chorus/admin/config"
safehttp "git.ilapage.cn/OPC/chorus/internal/platform/http"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/config/source/file"
"github.com/go-admin-team/go-admin-core/sdk"
coreapi "github.com/go-admin-team/go-admin-core/sdk/api"
coreconfig "github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/spf13/cobra"
"gorm.io/gorm"
)
var configPath string
var rootCmd = &cobra.Command{
Use: "chorus-admin",
SilenceUsage: true,
}
var serverCmd = &cobra.Command{
Use: "server",
Short: "Start the Chorus go-admin backend",
PreRunE: func(cmd *cobra.Command, args []string) error {
return setup(configPath)
},
RunE: func(cmd *cobra.Command, args []string) error {
return serve()
},
}
func init() {
serverCmd.Flags().StringVarP(&configPath, "config", "c", "", "go-admin settings file")
rootCmd.AddCommand(serverCmd)
}
func Execute() error {
return rootCmd.Execute()
}
func setup(path string) error {
if strings.TrimSpace(path) == "" {
return errors.New("--config is required; use config/settings.example.yml as a starting point")
}
coreconfig.ExtendConfig = &ext.ExtConfig
coreconfig.Setup(file.NewSource(file.WithPath(path)), database.Setup)
serviceConfig, err := serviceConfigFromEnvironment(os.LookupEnv)
if err != nil {
return err
}
chorus.Configure(func(db *gorm.DB) (*chorus.Service, error) {
return chorus.NewService(db, serviceConfig.config)
})
return nil
}
type configuredService struct {
config chorus.Config
}
func serviceConfigFromEnvironment(lookup func(string) (string, bool)) (configuredService, error) {
read := func(name string) string {
value, _ := lookup(name)
return strings.TrimSpace(value)
}
configured := configuredService{}
allowRaw := read("CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES")
if allowRaw == "" || allowRaw == "0" {
return configured, nil
}
if allowRaw != "1" {
return configuredService{}, errors.New("CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES must be 1 when set")
}
cooldown, err := positiveDuration(read("CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS"), time.Second)
if err != nil {
return configuredService{}, errors.New("CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS must be a positive integer")
}
timeout, err := positiveDuration(read("CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS"), time.Second)
if err != nil {
return configuredService{}, errors.New("CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS must be a positive integer")
}
maxResponse, err := positiveInt64(read("CHORUS_PROVIDER_MAX_RESPONSE_BYTES"))
if err != nil {
return configuredService{}, errors.New("CHORUS_PROVIDER_MAX_RESPONSE_BYTES must be a positive integer")
}
client, err := safehttp.New(safehttp.Config{Timeout: timeout, MaxRedirects: 3})
if err != nil {
return configuredService{}, errors.New("configure SSRF-safe connectivity client")
}
probe, err := chorus.NewLiveProbe(safehttp.NewProviderClient(client))
if err != nil {
return configuredService{}, errors.New("configure connectivity probe")
}
configured.config = chorus.Config{AllowConnectivityChecks: true, ConnectivityCooldown: cooldown, MaxResponseBytes: maxResponse, Probe: probe}
return configured, nil
}
func positiveDuration(raw string, unit time.Duration) (time.Duration, error) {
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil || value <= 0 {
return 0, errors.New("invalid positive duration")
}
return time.Duration(value) * unit, nil
}
func positiveInt64(raw string) (int64, error) {
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil || value <= 0 {
return 0, errors.New("invalid positive integer")
}
return value, nil
}
func serve() error {
if coreconfig.ApplicationConfig.Mode == pkg.ModeProd.String() {
gin.SetMode(gin.ReleaseMode)
}
engine := gin.New()
sdk.Runtime.SetEngine(engine)
engine.Use(common.Sentinel()).Use(common.RequestId(pkg.TrafficKey)).Use(coreapi.SetRequestLogger)
common.InitMiddleware(engine)
router.InitRouter()
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", coreconfig.ApplicationConfig.Host, coreconfig.ApplicationConfig.Port),
Handler: engine,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: time.Duration(coreconfig.ApplicationConfig.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(coreconfig.ApplicationConfig.WriterTimeout) * time.Second,
IdleTimeout: 60 * time.Second,
}
shutdown, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
result := make(chan error, 1)
go func() { result <- server.ListenAndServe() }()
select {
case <-shutdown.Done():
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
return server.Shutdown(ctx)
case err := <-result:
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
}
}