f536987a7e
- doc: add comment
176 lines
6.7 KiB
Go
176 lines
6.7 KiB
Go
// Package hostnginx 负责解析、渲染并校验当前宿主 Nginx 配置中被 daemon 管理的后端 upstream 块,
|
|
// 保证 upstream 中的后端服务器格式与端口取值精确符合部署契约。
|
|
package hostnginx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// managedBegin 受管理 upstream 块的起始标记行。
|
|
managedBegin = "# yms-update managed upstream begin"
|
|
// managedEnd 受管理 upstream 块的结束标记行。
|
|
managedEnd = "# yms-update managed upstream end"
|
|
// port8080 蓝绿发布中 8080 槽位的端口号。
|
|
port8080 = 8080
|
|
// port8081 蓝绿发布中 8081 槽位的端口号。
|
|
port8081 = 8081
|
|
)
|
|
|
|
// ActiveBackendPort 从 content 的受管理 upstream 块中读取唯一一条未注释的后端服务器端口。
|
|
// 若不存在活动服务器、存在多个活动服务器或块格式非法,均返回错误。
|
|
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 返回一份仅 activePort 未注释的完整配置,其余后端服务器行均被注释。
|
|
// activePort 只允许 8080 或 8081,渲染结果会再经 ActiveBackendPort 校验后才返回。
|
|
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
|
|
}
|
|
|
|
// managedBlock 受管理 upstream 块的解析结果,保留原始行供渲染时整体重建。
|
|
type managedBlock struct {
|
|
// lines 配置文件的全部行,渲染时按行号直接改写并重新拼接。
|
|
lines []string
|
|
// servers 块内解析出的后端服务器,按出现顺序排列。
|
|
servers []managedServer
|
|
}
|
|
|
|
// managedServer 受管理 upstream 块内一条后端服务器行的解析结果。
|
|
type managedServer struct {
|
|
// line 服务器行在 lines 中的下标。
|
|
line int
|
|
// port 服务器地址中的端口号,只允许 8080 或 8081。
|
|
port int
|
|
// commented 为 true 表示该行以 "# server " 开头(当前未启用)。
|
|
commented bool
|
|
}
|
|
|
|
// parseManagedBlock 解析 content 中由 managedBegin 与 managedEnd 标记的受管理 upstream 块。
|
|
// 它要求存在且仅存在一对有序标记、块内恰好包含 8080 与 8081 两条后端服务器且无多余非空行,
|
|
// 任一约束不满足即返回错误。
|
|
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
|
|
}
|
|
|
|
// parseManagedServer 解析单行 line 是否为后端服务器行,返回解析结果与 found。
|
|
// found 为 false 表示该行既非注释服务器也非活动服务器(如空行或其他行),由调用方继续处理;
|
|
// 若该行格式或端口不符合契约则返回错误。
|
|
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
|
|
}
|