feat: backend executor implement

This commit is contained in:
2026-08-16 17:12:06 +08:00
parent 390e0565d3
commit 7755da72e7
31 changed files with 2762 additions and 184 deletions
+84 -21
View File
@@ -19,7 +19,11 @@ const (
// DefaultPath is the only default location used by the daemon entrypoint.
DefaultPath = "/etc/yms-daemon/yms-daemon.toml"
BackendTypeNative = "native"
EnvironmentDev = "dev"
EnvironmentProd = "prod"
BackendTypeNative = "native"
BackendTypeContainer = "container"
BackendPort8080 = 8080
BackendPort8081 = 8081
@@ -34,14 +38,32 @@ const (
nativeSlotJAR8081 = "/home/yms/lib/glory-soft-yms-8081.jar"
nativeHealthURL8080 = "http://127.0.0.1:8080/yms/actuator/health"
nativeHealthURL8081 = "http://127.0.0.1:8081/yms/actuator/health"
containerName8080 = "backend-8080"
containerName8081 = "backend-8081"
)
const (
ContainerConfigSource = "/home/yms/conf/yms.yaml"
ContainerConfigTarget = "/app/config/yms.yaml"
ContainerTmpSource = "/home/yms/tmp"
ContainerTmpTarget = "/home/yms/tmp"
ContainerPortEnvironment = "SERVER_PORT"
ContainerConfigEnvironment = "SPRING_CONFIG_LOCATION"
ContainerConfigLocation = "file:/app/config/yms.yaml"
)
// Config is the complete local deployment configuration currently understood by the daemon.
type Config struct {
Daemon Daemon `toml:"daemon"`
Backend Backend `toml:"backend"`
}
// Backend describes the explicitly selected backend runtime and its native blue/green slots.
// Daemon contains machine-wide behavior that is independent of a component runtime.
type Daemon struct {
Environment string `toml:"environment"`
}
// Backend describes the explicitly selected backend runtime and its blue/green slots.
type Backend struct {
Type string `toml:"type"`
ReleaseDir string `toml:"release_dir"`
@@ -50,20 +72,21 @@ type Backend struct {
Slot BackendSlots `toml:"slot"`
}
// BackendSlots lists the only native backend ports supported by the current deployment contract.
// BackendSlots lists the only backend ports supported by the current deployment contract.
type BackendSlots struct {
Port8080 BackendSlot `toml:"8080"`
Port8081 BackendSlot `toml:"8081"`
}
// BackendSlot contains values that are passed to the native backend executor without derivation.
// BackendSlot contains exact values for one native or container backend slot.
type BackendSlot struct {
Unit string `toml:"unit"`
JAR string `toml:"jar"`
ContainerName string `toml:"container_name"`
HealthEndpoint string `toml:"health_endpoint"`
}
// Load opens path, performs strict TOML decoding, and validates the native backend contract.
// Load opens path, performs strict TOML decoding, and validates the selected backend contract.
func Load(path string) (Config, error) {
if err := validateAbsolutePath("deployment configuration", path); err != nil {
return Config{}, err
@@ -106,7 +129,14 @@ func validateExactDocumentKeys(document []byte) error {
if err := toml.Unmarshal(document, &root); err != nil {
return err
}
if err := rejectUnknownKeys(root, "", "backend"); err != nil {
if err := rejectUnknownKeys(root, "", "daemon", "backend"); err != nil {
return err
}
daemon, err := exactTable(root, "", "daemon")
if err != nil {
return err
}
if err := rejectUnknownKeys(daemon, "daemon", "environment"); err != nil {
return err
}
backend, err := exactTable(root, "", "backend")
@@ -128,7 +158,7 @@ func validateExactDocumentKeys(document []byte) error {
if err != nil {
return err
}
if err := rejectUnknownKeys(slot, "backend.slot."+port, "unit", "jar", "health_endpoint"); err != nil {
if err := rejectUnknownKeys(slot, "backend.slot."+port, "unit", "jar", "container_name", "health_endpoint"); err != nil {
return err
}
}
@@ -176,23 +206,46 @@ func rejectUnknownKeys(table map[string]any, parent string, allowed ...string) e
// Validate rejects incomplete or altered local deployment identifiers.
func (c Config) Validate() error {
if c.Backend.Type != BackendTypeNative {
return fmt.Errorf("backend.type must be %q", BackendTypeNative)
}
if c.Backend.ReleaseDir != nativeReleaseDir {
return fmt.Errorf("backend.release_dir must be %q", nativeReleaseDir)
}
if c.Backend.ActiveJAR != nativeActiveJAR {
return fmt.Errorf("backend.active_jar must be %q", nativeActiveJAR)
switch c.Daemon.Environment {
case EnvironmentDev, EnvironmentProd:
default:
return fmt.Errorf("daemon.environment must be %q or %q", EnvironmentDev, EnvironmentProd)
}
if err := validateAbsolutePath("backend.systemctl_path", c.Backend.SystemctlPath); err != nil {
return err
}
if err := validateSlot("backend.slot.8080", c.Backend.Slot.Port8080, nativeUnit8080, nativeSlotJAR8080, nativeHealthURL8080); err != nil {
return err
}
if err := validateSlot("backend.slot.8081", c.Backend.Slot.Port8081, nativeUnit8081, nativeSlotJAR8081, nativeHealthURL8081); err != nil {
return err
switch c.Backend.Type {
case BackendTypeNative:
if c.Backend.Slot.Port8080.ContainerName != "" || c.Backend.Slot.Port8081.ContainerName != "" {
return errors.New("native backend slots do not accept container_name")
}
if c.Backend.ReleaseDir != nativeReleaseDir {
return fmt.Errorf("backend.release_dir must be %q", nativeReleaseDir)
}
if c.Backend.ActiveJAR != nativeActiveJAR {
return fmt.Errorf("backend.active_jar must be %q", nativeActiveJAR)
}
if err := validateNativeSlot("backend.slot.8080", c.Backend.Slot.Port8080, nativeUnit8080, nativeSlotJAR8080, nativeHealthURL8080); err != nil {
return err
}
if err := validateNativeSlot("backend.slot.8081", c.Backend.Slot.Port8081, nativeUnit8081, nativeSlotJAR8081, nativeHealthURL8081); err != nil {
return err
}
case BackendTypeContainer:
if c.Backend.ReleaseDir != "" || c.Backend.ActiveJAR != "" {
return errors.New("container backend does not accept release_dir or active_jar")
}
if c.Backend.Slot.Port8080.Unit != "" || c.Backend.Slot.Port8080.JAR != "" || c.Backend.Slot.Port8081.Unit != "" || c.Backend.Slot.Port8081.JAR != "" {
return errors.New("container backend slots do not accept unit or jar")
}
if err := validateContainerSlot("backend.slot.8080", c.Backend.Slot.Port8080, containerName8080, nativeHealthURL8080); err != nil {
return err
}
if err := validateContainerSlot("backend.slot.8081", c.Backend.Slot.Port8081, containerName8081, nativeHealthURL8081); err != nil {
return err
}
default:
return fmt.Errorf("backend.type must be %q or %q", BackendTypeNative, BackendTypeContainer)
}
return nil
}
@@ -209,7 +262,7 @@ func (b Backend) SlotForPort(port int) (BackendSlot, error) {
}
}
func validateSlot(field string, slot BackendSlot, unit string, jar string, endpoint string) error {
func validateNativeSlot(field string, slot BackendSlot, unit string, jar string, endpoint string) error {
if slot.Unit != unit {
return fmt.Errorf("%s.unit must be %q", field, unit)
}
@@ -222,6 +275,16 @@ func validateSlot(field string, slot BackendSlot, unit string, jar string, endpo
return nil
}
func validateContainerSlot(field string, slot BackendSlot, containerName string, endpoint string) error {
if slot.ContainerName != containerName {
return fmt.Errorf("%s.container_name must be %q", field, containerName)
}
if slot.HealthEndpoint != endpoint {
return fmt.Errorf("%s.health_endpoint must be %q", field, endpoint)
}
return nil
}
func validateAbsolutePath(field string, value string) error {
if value == "" {
return fmt.Errorf("%s is required", field)