// 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 }