feat: backend native executor implement

This commit is contained in:
2026-08-16 01:27:30 +08:00
parent fbce91d797
commit 390e0565d3
44 changed files with 6430 additions and 31 deletions
+155
View File
@@ -0,0 +1,155 @@
// Package hostnginx manages the exact backend upstream block used by the current host Nginx deployment.
package hostnginx
import (
"errors"
"fmt"
"net"
"strconv"
"strings"
)
const (
managedBegin = "# yms-update managed upstream begin"
managedEnd = "# yms-update managed upstream end"
port8080 = 8080
port8081 = 8081
)
// ActiveBackendPort reads the one uncommented backend server in the managed upstream block.
func ActiveBackendPort(content []byte) (int, error) {
block, err := parseManagedBlock(content)
if err != nil {
return 0, err
}
active := 0
for _, server := range block.servers {
if !server.commented {
if active != 0 {
return 0, errors.New("managed Nginx upstream contains more than one active backend server")
}
active = server.port
}
}
if active == 0 {
return 0, errors.New("managed Nginx upstream does not contain an active backend server")
}
return active, nil
}
// RenderBackendPort returns a complete configuration with only activePort uncommented.
func RenderBackendPort(content []byte, activePort int) ([]byte, error) {
if activePort != port8080 && activePort != port8081 {
return nil, fmt.Errorf("host Nginx backend port must be 8080 or 8081: %d", activePort)
}
block, err := parseManagedBlock(content)
if err != nil {
return nil, err
}
lines := block.lines
for _, server := range block.servers {
indentLength := len(lines[server.line]) - len(strings.TrimLeft(lines[server.line], " \t"))
indent := lines[server.line][:indentLength]
serverText := strings.TrimSpace(lines[server.line])
serverText = strings.TrimPrefix(serverText, "# ")
if server.port == activePort {
lines[server.line] = indent + serverText
} else {
lines[server.line] = indent + "# " + serverText
}
}
rendered := []byte(strings.Join(lines, "\n"))
if _, err := ActiveBackendPort(rendered); err != nil {
return nil, fmt.Errorf("validate rendered host Nginx backend upstream: %w", err)
}
return rendered, nil
}
type managedBlock struct {
lines []string
servers []managedServer
}
type managedServer struct {
line int
port int
commented bool
}
func parseManagedBlock(content []byte) (managedBlock, error) {
if len(content) == 0 {
return managedBlock{}, errors.New("host Nginx configuration is empty")
}
lines := strings.Split(string(content), "\n")
beginLine := -1
endLine := -1
for index, line := range lines {
switch strings.TrimSpace(line) {
case managedBegin:
if beginLine != -1 {
return managedBlock{}, errors.New("host Nginx configuration contains duplicate managed upstream begin markers")
}
beginLine = index
case managedEnd:
if endLine != -1 {
return managedBlock{}, errors.New("host Nginx configuration contains duplicate managed upstream end markers")
}
endLine = index
}
}
if beginLine == -1 || endLine == -1 || endLine <= beginLine {
return managedBlock{}, errors.New("host Nginx configuration requires one ordered managed upstream marker pair")
}
servers := make([]managedServer, 0, 2)
seenPorts := make(map[int]struct{}, 2)
for index := beginLine + 1; index < endLine; index++ {
server, found, err := parseManagedServer(lines[index], index)
if err != nil {
return managedBlock{}, err
}
if !found {
if strings.TrimSpace(lines[index]) != "" {
return managedBlock{}, fmt.Errorf("managed Nginx upstream contains an unexpected line: %q", strings.TrimSpace(lines[index]))
}
continue
}
if _, duplicate := seenPorts[server.port]; duplicate {
return managedBlock{}, fmt.Errorf("managed Nginx upstream contains duplicate port %d", server.port)
}
seenPorts[server.port] = struct{}{}
servers = append(servers, server)
}
for _, port := range []int{port8080, port8081} {
if _, found := seenPorts[port]; !found {
return managedBlock{}, fmt.Errorf("managed Nginx upstream is missing port %d", port)
}
}
if len(servers) != 2 {
return managedBlock{}, fmt.Errorf("managed Nginx upstream must contain exactly two backend servers, got %d", len(servers))
}
return managedBlock{lines: lines, servers: servers}, nil
}
func parseManagedServer(line string, lineIndex int) (managedServer, bool, error) {
trimmed := strings.TrimSpace(line)
commented := strings.HasPrefix(trimmed, "# server ")
active := strings.HasPrefix(trimmed, "server ")
if !commented && !active {
return managedServer{}, false, nil
}
serverText := strings.TrimPrefix(trimmed, "# ")
fields := strings.Fields(serverText)
if len(fields) != 4 || fields[0] != "server" || fields[2] != "max_fails=1" || fields[3] != "fail_timeout=2s;" {
return managedServer{}, false, fmt.Errorf("managed Nginx upstream server line has an unsupported format: %q", trimmed)
}
_, portText, err := net.SplitHostPort(fields[1])
if err != nil {
return managedServer{}, false, fmt.Errorf("parse managed Nginx upstream address %q: %w", fields[1], err)
}
port, err := strconv.Atoi(portText)
if err != nil || (port != port8080 && port != port8081) {
return managedServer{}, false, fmt.Errorf("managed Nginx upstream contains unsupported backend port %q", portText)
}
return managedServer{line: lineIndex, port: port, commented: commented}, true, nil
}