新增 auto-check 工具:网段扫描+SSH登录+压力测试永久循环工作流
- pkg/discovery: TCP端口探测存活主机,ARP表解析MAC作为设备唯一标识 - pkg/sshclient: SSH连接(密码/密钥认证)与远程命令执行 - pkg/stress: stress-ng/stressapptest 压测(cpu/memory/disk/memnative/full)+温度与dmesg监控 - pkg/report: 检测报告生成(文本/JSON) - pkg/workflow: 永久循环工作流(间隔可配),MAC唯一标识设备,增量测试+状态变化打印 - pkg/config: YAML分类配置(scan/ssh/stress/report/workflow) - cmd: cobra入口,仅 --config 指定配置文件
This commit is contained in:
262
auto-check/pkg/report/report.go
Normal file
262
auto-check/pkg/report/report.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PhaseResult 单阶段结果
|
||||
type PhaseResult struct {
|
||||
Phase string `json:"phase" yaml:"phase"`
|
||||
Status string `json:"status" yaml:"status"` // success / partial / failed
|
||||
Hosts []HostResult `json:"hosts" yaml:"hosts"`
|
||||
Summary string `json:"summary" yaml:"summary"`
|
||||
}
|
||||
|
||||
// HostResult 单台主机结果
|
||||
type HostResult struct {
|
||||
IP string `json:"ip" yaml:"ip"`
|
||||
Alive bool `json:"alive" yaml:"alive"`
|
||||
OpenPort int `json:"open_port,omitempty" yaml:"open_port,omitempty"`
|
||||
SSHLogin bool `json:"ssh_login" yaml:"ssh_login"`
|
||||
SSHErr string `json:"ssh_error,omitempty" yaml:"ssh_error,omitempty"`
|
||||
Stress []StressResult `json:"stress,omitempty" yaml:"stress,omitempty"`
|
||||
}
|
||||
|
||||
// StressResult 单项压力测试结果
|
||||
type StressResult struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Status string `json:"status" yaml:"status"` // pass / fail / skip
|
||||
Duration string `json:"duration" yaml:"duration"`
|
||||
Output string `json:"output,omitempty" yaml:"output,omitempty"`
|
||||
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Report 检测报告
|
||||
type Report struct {
|
||||
StartTime time.Time `json:"start_time" yaml:"start_time"`
|
||||
EndTime time.Time `json:"end_time" yaml:"end_time"`
|
||||
Duration string `json:"duration" yaml:"duration"`
|
||||
Config ConfigSummary `json:"config" yaml:"config"`
|
||||
Phases []PhaseResult `json:"phases" yaml:"phases"`
|
||||
}
|
||||
|
||||
// ConfigSummary 配置摘要(脱敏)
|
||||
type ConfigSummary struct {
|
||||
CIDR string `json:"cidr" yaml:"cidr"`
|
||||
Port int `json:"port" yaml:"port"`
|
||||
User string `json:"user" yaml:"user"`
|
||||
Concurrency int `json:"concurrency" yaml:"concurrency"`
|
||||
StressTypes string `json:"stress_types,omitempty" yaml:"stress_types,omitempty"`
|
||||
}
|
||||
|
||||
// Builder 报告构建器
|
||||
type Builder struct {
|
||||
report *Report
|
||||
phase *PhaseResult
|
||||
}
|
||||
|
||||
// NewBuilder 创建报告构建器
|
||||
func NewBuilder(cfg ConfigSummary) *Builder {
|
||||
return &Builder{
|
||||
report: &Report{
|
||||
StartTime: time.Now(),
|
||||
Config: cfg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StartPhase 开始新阶段
|
||||
func (b *Builder) StartPhase(name string) {
|
||||
b.phase = &PhaseResult{
|
||||
Phase: name,
|
||||
}
|
||||
}
|
||||
|
||||
// AddHost 添加主机结果到当前阶段
|
||||
func (b *Builder) AddHost(h HostResult) {
|
||||
if b.phase != nil {
|
||||
b.phase.Hosts = append(b.phase.Hosts, h)
|
||||
}
|
||||
}
|
||||
|
||||
// AddStressResult 为指定 IP 添加压力测试结果
|
||||
func (b *Builder) AddStressResult(ip string, sr StressResult) {
|
||||
if b.phase == nil {
|
||||
return
|
||||
}
|
||||
for i := range b.phase.Hosts {
|
||||
if b.phase.Hosts[i].IP == ip {
|
||||
b.phase.Hosts[i].Stress = append(b.phase.Hosts[i].Stress, sr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EndPhase 结束当前阶段,设置状态和摘要
|
||||
func (b *Builder) EndPhase(summary string) {
|
||||
if b.phase == nil {
|
||||
return
|
||||
}
|
||||
b.phase.Summary = summary
|
||||
|
||||
total := len(b.phase.Hosts)
|
||||
success := 0
|
||||
for _, h := range b.phase.Hosts {
|
||||
switch b.phase.Phase {
|
||||
case "设备发现":
|
||||
if h.Alive {
|
||||
success++
|
||||
}
|
||||
case "SSH 登录":
|
||||
if h.SSHLogin {
|
||||
success++
|
||||
}
|
||||
case "压力测试":
|
||||
if len(h.Stress) > 0 {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case success == total:
|
||||
b.phase.Status = "success"
|
||||
case success > 0:
|
||||
b.phase.Status = "partial"
|
||||
default:
|
||||
b.phase.Status = "failed"
|
||||
}
|
||||
|
||||
b.report.Phases = append(b.report.Phases, *b.phase)
|
||||
b.phase = nil
|
||||
}
|
||||
|
||||
// Build 生成最终报告
|
||||
func (b *Builder) Build() *Report {
|
||||
b.report.EndTime = time.Now()
|
||||
b.report.Duration = b.report.EndTime.Sub(b.report.StartTime).Round(time.Millisecond).String()
|
||||
return b.report
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 输出格式化
|
||||
// ============================
|
||||
|
||||
// ToText 输出纯文本报告
|
||||
func (r *Report) ToText() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("╔══════════════════════════════════════════════╗\n")
|
||||
sb.WriteString("║ auto-check 检测报告 ║\n")
|
||||
sb.WriteString("╚══════════════════════════════════════════════╝\n\n")
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" 生成时间: %s\n", r.StartTime.Format("2006-01-02 15:04:05")))
|
||||
sb.WriteString(fmt.Sprintf(" 总耗时: %s\n\n", r.Duration))
|
||||
|
||||
// 配置摘要
|
||||
sb.WriteString("── 配置摘要 ──\n")
|
||||
sb.WriteString(fmt.Sprintf(" 网段: %s\n", r.Config.CIDR))
|
||||
sb.WriteString(fmt.Sprintf(" SSH端口: %d\n", r.Config.Port))
|
||||
sb.WriteString(fmt.Sprintf(" SSH用户: %s\n", r.Config.User))
|
||||
sb.WriteString(fmt.Sprintf(" 并发数: %d\n", r.Config.Concurrency))
|
||||
if r.Config.StressTypes != "" {
|
||||
sb.WriteString(fmt.Sprintf(" 压测类型: %s\n", r.Config.StressTypes))
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
// 各阶段结果
|
||||
for _, phase := range r.Phases {
|
||||
statusIcon := "✅"
|
||||
switch phase.Status {
|
||||
case "partial":
|
||||
statusIcon = "⚠️"
|
||||
case "failed":
|
||||
statusIcon = "❌"
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("══ %s %s ══\n", phase.Phase, statusIcon))
|
||||
sb.WriteString(fmt.Sprintf(" %s\n\n", phase.Summary))
|
||||
|
||||
if phase.Phase == "设备发现" || phase.Phase == "SSH 登录" {
|
||||
for _, h := range phase.Hosts {
|
||||
if phase.Phase == "设备发现" && !h.Alive {
|
||||
continue
|
||||
}
|
||||
if phase.Phase == "SSH 登录" && !h.SSHLogin {
|
||||
sb.WriteString(fmt.Sprintf(" ✗ %-15s %s\n", h.IP, h.SSHErr))
|
||||
continue
|
||||
}
|
||||
icon := "✓"
|
||||
if phase.Phase == "SSH 登录" {
|
||||
icon = "✓"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" %s %-15s\n", icon, h.IP))
|
||||
}
|
||||
}
|
||||
|
||||
if phase.Phase == "压力测试" {
|
||||
for _, h := range phase.Hosts {
|
||||
if len(h.Stress) == 0 {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" ┌─ %s\n", h.IP))
|
||||
for _, s := range h.Stress {
|
||||
icon := "✓"
|
||||
if s.Status != "pass" {
|
||||
icon = "✗"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" │ %s %-10s 耗时: %s\n", icon, s.Type, s.Duration))
|
||||
if s.Error != "" {
|
||||
sb.WriteString(fmt.Sprintf(" │ 错误: %s\n", s.Error))
|
||||
}
|
||||
}
|
||||
sb.WriteString(" └──────────────\n")
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// ToJSON 输出 JSON 报告
|
||||
func (r *Report) ToJSON() (string, error) {
|
||||
data, err := json.MarshalIndent(r, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// SaveFile 保存报告到文件
|
||||
func (r *Report) SaveFile(path string, format string) error {
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建目录失败: %w", err)
|
||||
}
|
||||
|
||||
var content string
|
||||
switch format {
|
||||
case "json":
|
||||
var err error
|
||||
content, err = r.ToJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
content = r.ToText()
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// DefaultReportPath 默认报告路径
|
||||
func DefaultReportPath() string {
|
||||
timestamp := time.Now().Format("20060102-150405")
|
||||
return filepath.Join(".", "reports", fmt.Sprintf("report-%s.txt", timestamp))
|
||||
}
|
||||
Reference in New Issue
Block a user