Tui fix focus
This commit is contained in:
@@ -1,164 +1,228 @@
|
||||
package wizard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sunhpc/pkg/database"
|
||||
"sunhpc/pkg/logger"
|
||||
"sunhpc/pkg/utils"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// 配置项映射:定义每个配置项对应的表名、键名
|
||||
var configMappings = []struct {
|
||||
table string
|
||||
key string
|
||||
getVal func(m *model) interface{} // 动态获取配置值的函数
|
||||
}{
|
||||
// attributes 表
|
||||
{"attributes", "license", func(m *model) any { return m.config.License }},
|
||||
{"attributes", "accepted", func(m *model) any { return m.config.AgreementAccepted }},
|
||||
{"attributes", "country", func(m *model) any { return m.config.Country }},
|
||||
{"attributes", "region", func(m *model) any { return m.config.Region }},
|
||||
{"attributes", "timezone", func(m *model) any { return m.config.Timezone }},
|
||||
{"attributes", "homepage", func(m *model) any { return m.config.HomePage }},
|
||||
{"attributes", "dbaddress", func(m *model) any { return m.config.DBAddress }},
|
||||
{"attributes", "software", func(m *model) any { return m.config.Software }},
|
||||
type ConfigMapping struct {
|
||||
Title string `toml:"title"`
|
||||
Base struct {
|
||||
ClusterName string `toml:"cluster_name"`
|
||||
Country string `toml:"country"`
|
||||
State string `toml:"state"`
|
||||
City string `toml:"city"`
|
||||
HomePage string `toml:"homepage"`
|
||||
Contact string `toml:"contact"`
|
||||
License string `toml:"license"`
|
||||
BaseDir string `toml:"base_dir"`
|
||||
WorkDir string `toml:"work_dir"`
|
||||
DistroDir string `toml:"distro_dir"`
|
||||
Partition string `toml:"partition"`
|
||||
Distribution string `toml:"distribution"`
|
||||
Timezone string `toml:"timezone"`
|
||||
SafePort string `toml:"safe_port"`
|
||||
SafeDirs string `toml:"safe_dirs"`
|
||||
SafeSecurity string `toml:"safe_security"`
|
||||
PluginDirs string `toml:"plugin_dirs"`
|
||||
PluginPort string `toml:"plugin_port"`
|
||||
GangliaAddr string `toml:"ganglia_addr"`
|
||||
} `toml:"base"`
|
||||
Pxelinux struct {
|
||||
NextServer string `toml:"next_server"`
|
||||
PxeFilename string `toml:"pxe_filename"`
|
||||
PxeLinuxDir string `toml:"pxelinux_dir"`
|
||||
BootArgs string `toml:"boot_args"`
|
||||
} `toml:"pxelinux"`
|
||||
Public struct {
|
||||
PublicHostname string `toml:"public_hostname"`
|
||||
PublicInterface string `toml:"public_interface"`
|
||||
PublicAddress string `toml:"public_address"`
|
||||
PublicNetmask string `toml:"public_netmask"`
|
||||
PublicGateway string `toml:"public_gateway"`
|
||||
PublicNetwork string `toml:"public_network"`
|
||||
PublicDomain string `toml:"public_domain"`
|
||||
PublicCIDR string `toml:"public_cidr"`
|
||||
PublicDNS string `toml:"public_dns"`
|
||||
PublicMac string `toml:"public_mac"`
|
||||
PublicMTU string `toml:"public_mtu"`
|
||||
PublicNTP string `toml:"public_ntp"`
|
||||
} `toml:"public"`
|
||||
Private struct {
|
||||
PrivateHostname string `toml:"private_hostname"`
|
||||
PrivateInterface string `toml:"private_interface"`
|
||||
PrivateAddress string `toml:"private_address"`
|
||||
PrivateNetmask string `toml:"private_netmask"`
|
||||
PrivateNetwork string `toml:"private_network"`
|
||||
PrivateDomain string `toml:"private_domain"`
|
||||
PrivateCIDR string `toml:"private_cidr"`
|
||||
PrivateMac string `toml:"private_mac"`
|
||||
PrivateMTU string `toml:"private_mtu"`
|
||||
} `toml:"private"`
|
||||
}
|
||||
|
||||
// nodes 表
|
||||
{"nodes", "name", func(m *model) any { return m.config.Hostname }},
|
||||
func NewConfigWithDefault() *ConfigMapping {
|
||||
return &ConfigMapping{
|
||||
Title: "Cluster Configuration",
|
||||
Base: struct {
|
||||
ClusterName string `toml:"cluster_name"`
|
||||
Country string `toml:"country"`
|
||||
State string `toml:"state"`
|
||||
City string `toml:"city"`
|
||||
HomePage string `toml:"homepage"`
|
||||
Contact string `toml:"contact"`
|
||||
License string `toml:"license"`
|
||||
BaseDir string `toml:"base_dir"`
|
||||
WorkDir string `toml:"work_dir"`
|
||||
DistroDir string `toml:"distro_dir"`
|
||||
Partition string `toml:"partition"`
|
||||
Distribution string `toml:"distribution"`
|
||||
Timezone string `toml:"timezone"`
|
||||
SafePort string `toml:"safe_port"`
|
||||
SafeDirs string `toml:"safe_dirs"`
|
||||
SafeSecurity string `toml:"safe_security"`
|
||||
PluginDirs string `toml:"plugin_dirs"`
|
||||
PluginPort string `toml:"plugin_port"`
|
||||
GangliaAddr string `toml:"ganglia_addr"`
|
||||
}{
|
||||
ClusterName: "SunHPC_Cluster",
|
||||
Country: "CN",
|
||||
State: "Beijing",
|
||||
City: "Beijing",
|
||||
HomePage: "https://www.sunhpc.com",
|
||||
Contact: "admin@sunhpc.com",
|
||||
License: "MIT",
|
||||
BaseDir: "install",
|
||||
WorkDir: "/export",
|
||||
DistroDir: "/export/sunhpc",
|
||||
Partition: "default",
|
||||
Distribution: "sunhpc-dist",
|
||||
Timezone: "Asia/Shanghai",
|
||||
SafePort: "372",
|
||||
SafeDirs: "safe.d",
|
||||
SafeSecurity: "safe-security",
|
||||
PluginDirs: "/etc/sunhpc/plugin",
|
||||
PluginPort: "12123",
|
||||
GangliaAddr: "224.0.0.3",
|
||||
},
|
||||
Pxelinux: struct {
|
||||
NextServer string `toml:"next_server"`
|
||||
PxeFilename string `toml:"pxe_filename"`
|
||||
PxeLinuxDir string `toml:"pxelinux_dir"`
|
||||
BootArgs string `toml:"boot_args"`
|
||||
}{
|
||||
NextServer: "192.168.1.1",
|
||||
PxeFilename: "pxelinux.0",
|
||||
PxeLinuxDir: "/tftpboot/pxelinux",
|
||||
BootArgs: "net.ifnames=0 biosdevname=0",
|
||||
},
|
||||
Public: struct {
|
||||
PublicHostname string `toml:"public_hostname"`
|
||||
PublicInterface string `toml:"public_interface"`
|
||||
PublicAddress string `toml:"public_address"`
|
||||
PublicNetmask string `toml:"public_netmask"`
|
||||
PublicGateway string `toml:"public_gateway"`
|
||||
PublicNetwork string `toml:"public_network"`
|
||||
PublicDomain string `toml:"public_domain"`
|
||||
PublicCIDR string `toml:"public_cidr"`
|
||||
PublicDNS string `toml:"public_dns"`
|
||||
PublicMac string `toml:"public_mac"`
|
||||
PublicMTU string `toml:"public_mtu"`
|
||||
PublicNTP string `toml:"public_ntp"`
|
||||
}{
|
||||
PublicHostname: "cluster.hpc.org",
|
||||
PublicInterface: "eth0",
|
||||
PublicAddress: "",
|
||||
PublicNetmask: "",
|
||||
PublicGateway: "",
|
||||
PublicNetwork: "",
|
||||
PublicDomain: "hpc.org",
|
||||
PublicCIDR: "",
|
||||
PublicDNS: "",
|
||||
PublicMac: "00:11:22:33:44:55",
|
||||
PublicMTU: "1500",
|
||||
PublicNTP: "pool.ntp.org",
|
||||
},
|
||||
Private: struct {
|
||||
PrivateHostname string `toml:"private_hostname"`
|
||||
PrivateInterface string `toml:"private_interface"`
|
||||
PrivateAddress string `toml:"private_address"`
|
||||
PrivateNetmask string `toml:"private_netmask"`
|
||||
PrivateNetwork string `toml:"private_network"`
|
||||
PrivateDomain string `toml:"private_domain"`
|
||||
PrivateCIDR string `toml:"private_cidr"`
|
||||
PrivateMac string `toml:"private_mac"`
|
||||
PrivateMTU string `toml:"private_mtu"`
|
||||
}{
|
||||
PrivateHostname: "sunhpc",
|
||||
PrivateInterface: "eth1",
|
||||
PrivateAddress: "172.16.9.254",
|
||||
PrivateNetmask: "255.255.255.0",
|
||||
PrivateNetwork: "172.16.9.0",
|
||||
PrivateDomain: "example.com",
|
||||
PrivateCIDR: "172.16.9.0/24",
|
||||
PrivateMac: "00:11:22:33:44:66",
|
||||
PrivateMTU: "1500",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 公网设置表
|
||||
{"public_network", "public_interface", func(m *model) any { return m.config.PublicInterface }},
|
||||
{"public_network", "ip_address", func(m *model) any { return m.config.PublicIPAddress }},
|
||||
{"public_network", "netmask", func(m *model) any { return m.config.PublicNetmask }},
|
||||
{"public_network", "gateway", func(m *model) any { return m.config.PublicGateway }},
|
||||
// 内网配置表
|
||||
{"internal_network", "internal_interface", func(m *model) any { return m.config.InternalInterface }},
|
||||
{"internal_network", "internal_ip", func(m *model) any { return m.config.InternalIPAddress }},
|
||||
{"internal_network", "internal_mask", func(m *model) any { return m.config.InternalNetmask }},
|
||||
// DNS配置表
|
||||
{"dns_config", "dns_primary", func(m *model) any { return m.config.DNSPrimary }},
|
||||
{"dns_config", "dns_secondary", func(m *model) any { return m.config.DNSSecondary }},
|
||||
func loadConfig() (*ConfigMapping, error) {
|
||||
configs := NewConfigWithDefault()
|
||||
cfgfile := "/etc/sunhpc/config.toml"
|
||||
|
||||
// 尝试解析配置文件
|
||||
if _, err := toml.DecodeFile(cfgfile, configs); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// 文件不存在,直接返回默认配置
|
||||
logger.Infof("Config file %s not exist, use default config", cfgfile)
|
||||
return configs, nil
|
||||
}
|
||||
// 其他错误,返回错误
|
||||
logger.Debugf("[DEBUG] Parse config file %s failed: %v", cfgfile, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Infof("Load config file %s success", cfgfile)
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// saveConfig 入口函数:保存所有配置到数据库
|
||||
func (m *model) saveConfig() error {
|
||||
|
||||
conn, err := database.GetDB() // 假设database包已实现getDB()获取连接
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取数据库连接失败: %w", err)
|
||||
logger.Errorf("Database connection failed: %v", err)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
m.force = false // 初始化全量覆盖标识
|
||||
|
||||
// 遍历所有配置项,逐个处理
|
||||
for _, item := range configMappings {
|
||||
val := item.getVal(m)
|
||||
exists, err := m.checkExists(conn, item.table, item.key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查%s.%s是否存在失败: %w", item.table, item.key, err)
|
||||
}
|
||||
|
||||
// 根据存在性和用户选择处理
|
||||
if !exists {
|
||||
// 不存在则直接插入
|
||||
if err := m.upsertConfig(conn, item.table, item.key, val, false); err != nil {
|
||||
return fmt.Errorf("插入%s.%s失败: %w", item.table, item.key, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 已存在:判断是否全量覆盖
|
||||
if m.force {
|
||||
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
|
||||
return fmt.Errorf("强制更新%s.%s失败: %w", item.table, item.key, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 询问用户操作
|
||||
choice, err := m.askUserChoice(item.table, item.key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取用户选择失败: %w", err)
|
||||
}
|
||||
|
||||
switch strings.ToLower(choice) {
|
||||
case "y", "yes":
|
||||
// 单条覆盖
|
||||
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
|
||||
return fmt.Errorf("更新%s.%s失败: %w", item.table, item.key, err)
|
||||
}
|
||||
case "a", "all":
|
||||
// 全量覆盖,后续不再询问
|
||||
m.force = true
|
||||
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
|
||||
return fmt.Errorf("全量更新%s.%s失败: %w", item.table, item.key, err)
|
||||
}
|
||||
case "n", "no":
|
||||
// 跳过当前项
|
||||
fmt.Printf("跳过%s.%s的更新\n", item.table, item.key)
|
||||
default:
|
||||
fmt.Printf("无效选择%s,跳过%s.%s的更新\n", choice, item.table, item.key)
|
||||
}
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
logger.Debugf("[DEBUG] Load config file failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
t_value := m.config.PrivateIPAddress
|
||||
c_value := config.Private.PrivateAddress
|
||||
|
||||
logger.Debugf("t_value: %s\n", t_value)
|
||||
logger.Debugf("c_value: %s\n", c_value)
|
||||
|
||||
logger.Debugf("config.Base.ClusterName: %s\n", config.Base.ClusterName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkExists 集中判断配置项是否存在(核心判断逻辑)
|
||||
func (m *model) checkExists(conn *sql.DB, table, key string) (bool, error) {
|
||||
var count int
|
||||
// 通用存在性检查SQL(假设所有表都有key字段作为主键)
|
||||
query := fmt.Sprintf("SELECT COUNT(1) FROM %s WHERE `key` = ?", table)
|
||||
err := conn.QueryRow(query, key).Scan(&count)
|
||||
if err != nil {
|
||||
// 表不存在也视为"不存在"(可选:根据实际需求调整,比如先建表)
|
||||
if strings.Contains(err.Error(), "table not found") {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// upsertConfig 统一处理插入/更新逻辑
|
||||
func (m *model) upsertConfig(conn *sql.DB, table, key string, val interface{}, update bool) error {
|
||||
var query string
|
||||
if !update {
|
||||
// 插入:假设表结构为(key, value)
|
||||
query = fmt.Sprintf("INSERT INTO %s (`key`, `value`) VALUES (?, ?)", table)
|
||||
} else {
|
||||
// 更新
|
||||
query = fmt.Sprintf("UPDATE %s SET `value` = ? WHERE `key` = ?", table)
|
||||
}
|
||||
|
||||
// 处理参数顺序(更新和插入的参数顺序不同)
|
||||
var args []interface{}
|
||||
if !update {
|
||||
args = []interface{}{key, val}
|
||||
} else {
|
||||
args = []interface{}{val, key}
|
||||
}
|
||||
|
||||
_, err := conn.Exec(query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// askUserChoice 询问用户操作选择
|
||||
func (m *model) askUserChoice(table, key string) (string, error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("配置项%s.%s已存在,选择操作(y/yes=覆盖, n/no=跳过, a/all=全量覆盖后续所有): ", table, key)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 去除空格和换行
|
||||
return strings.TrimSpace(input), nil
|
||||
}
|
||||
|
||||
// 获取系统网络接口
|
||||
func getNetworkInterfaces() []string {
|
||||
// 实现获取系统网络接口的逻辑
|
||||
|
||||
Reference in New Issue
Block a user