Files
mev-beta/orig/tools/cicd-audit/internal/cicd_auditor.go
Administrator c54c569f30 refactor: move all remaining files to orig/ directory
Completed clean root directory structure:
- Root now contains only: .git, .env, docs/, orig/
- Moved all remaining files and directories to orig/:
  - Config files (.claude, .dockerignore, .drone.yml, etc.)
  - All .env variants (except active .env)
  - Git config (.gitconfig, .github, .gitignore, etc.)
  - Tool configs (.golangci.yml, .revive.toml, etc.)
  - Documentation (*.md files, @prompts)
  - Build files (Dockerfiles, Makefile, go.mod, go.sum)
  - Docker compose files
  - All source directories (scripts, tests, tools, etc.)
  - Runtime directories (logs, monitoring, reports)
  - Dependency files (node_modules, lib, cache)
  - Special files (--delete)

- Removed empty runtime directories (bin/, data/)

V2 structure is now clean:
- docs/planning/ - V2 planning documents
- orig/ - Complete V1 codebase preserved
- .env - Active environment config (not in git)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:53:05 +01:00

1479 lines
39 KiB
Go

package internal
import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
)
type CICDAuditConfig struct {
Pipeline string
ConfigFile string
OutputDir string
Verbose bool
Parallel bool
FailFast bool
ReportFormat string
Timeout time.Duration
Stage string
Environment string
SlackWebhook string
EmailRecipients string
BaselineMode bool
CompareMode bool
MetricsMode bool
}
type CICDAuditor struct {
config *CICDAuditConfig
results *PipelineResults
auditTools []AuditTool
rootDir string
startTime time.Time
}
type PipelineResults struct {
PipelineType string `json:"pipeline_type"`
Environment string `json:"environment"`
OverallStatus string `json:"overall_status"`
ExitCode int `json:"exit_code"`
TotalAudits int `json:"total_audits"`
PassedAudits int `json:"passed_audits"`
FailedAudits int `json:"failed_audits"`
SkippedAudits int `json:"skipped_audits"`
AuditResults []AuditResult `json:"audit_results"`
QualityGates []QualityGate `json:"quality_gates"`
Metrics PipelineMetrics `json:"metrics"`
Notifications []Notification `json:"notifications"`
Artifacts []Artifact `json:"artifacts"`
Timestamp time.Time `json:"timestamp"`
Duration int64 `json:"duration_ms"`
BuildInfo BuildInfo `json:"build_info"`
DeploymentInfo DeploymentInfo `json:"deployment_info,omitempty"`
}
type AuditResult struct {
ToolName string `json:"tool_name"`
Status string `json:"status"`
ExitCode int `json:"exit_code"`
Duration int64 `json:"duration_ms"`
OutputFile string `json:"output_file"`
ErrorOutput string `json:"error_output,omitempty"`
Metrics map[string]interface{} `json:"metrics,omitempty"`
QualityChecks []QualityCheck `json:"quality_checks"`
Recommendations []string `json:"recommendations"`
Timestamp time.Time `json:"timestamp"`
RetryCount int `json:"retry_count"`
}
type QualityGate struct {
Name string `json:"name"`
Type string `json:"type"`
Threshold float64 `json:"threshold"`
ActualValue float64 `json:"actual_value"`
Status string `json:"status"`
Critical bool `json:"critical"`
Description string `json:"description"`
Timestamp time.Time `json:"timestamp"`
}
type QualityCheck struct {
Name string `json:"name"`
Status string `json:"status"`
Score float64 `json:"score"`
Threshold float64 `json:"threshold"`
Description string `json:"description"`
Critical bool `json:"critical"`
}
type PipelineMetrics struct {
CodeCoverage float64 `json:"code_coverage"`
SecurityScore float64 `json:"security_score"`
PerformanceScore float64 `json:"performance_score"`
QualityScore float64 `json:"quality_score"`
ComplianceScore float64 `json:"compliance_score"`
OverallScore float64 `json:"overall_score"`
TrendAnalysis TrendAnalysis `json:"trend_analysis"`
Benchmarks map[string]float64 `json:"benchmarks"`
ResourceUsage ResourceUsage `json:"resource_usage"`
}
type TrendAnalysis struct {
ScoreTrend string `json:"score_trend"`
IssuesTrend string `json:"issues_trend"`
PerformanceTrend string `json:"performance_trend"`
LastUpdated time.Time `json:"last_updated"`
RecommendedActions []string `json:"recommended_actions"`
}
type ResourceUsage struct {
CPUTime float64 `json:"cpu_time_seconds"`
MemoryPeak int64 `json:"memory_peak_mb"`
DiskUsage int64 `json:"disk_usage_mb"`
NetworkRequests int `json:"network_requests"`
CacheHits int `json:"cache_hits"`
CacheMisses int `json:"cache_misses"`
}
type Notification struct {
Type string `json:"type"`
Target string `json:"target"`
Message string `json:"message"`
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
}
type Artifact struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
Size int64 `json:"size"`
Hash string `json:"hash"`
Timestamp time.Time `json:"timestamp"`
Retention string `json:"retention"`
}
type BuildInfo struct {
BuildNumber string `json:"build_number"`
GitCommit string `json:"git_commit"`
GitBranch string `json:"git_branch"`
GitTag string `json:"git_tag,omitempty"`
Timestamp time.Time `json:"timestamp"`
TriggeredBy string `json:"triggered_by"`
BuildAgent string `json:"build_agent"`
Environment map[string]string `json:"environment"`
}
type DeploymentInfo struct {
TargetEnvironment string `json:"target_environment"`
DeploymentStrategy string `json:"deployment_strategy"`
ConfigChanges []string `json:"config_changes"`
HealthChecks []HealthCheck `json:"health_checks"`
RollbackPlan string `json:"rollback_plan"`
Timestamp time.Time `json:"timestamp"`
}
type HealthCheck struct {
Name string `json:"name"`
Status string `json:"status"`
URL string `json:"url"`
ResponseTime int64 `json:"response_time_ms"`
Timestamp time.Time `json:"timestamp"`
}
type AuditTool struct {
Name string
Command string
Args []string
OutputFile string
Stage string
Critical bool
Timeout time.Duration
RetryCount int
QualityGates []QualityGate
Dependencies []string
Environment map[string]string
}
// JUnit XML structures for CI/CD integration
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Errors int `xml:"errors,attr"`
Time float64 `xml:"time,attr"`
TestSuites []JUnitTestSuite `xml:"testsuite"`
}
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Errors int `xml:"errors,attr"`
Time float64 `xml:"time,attr"`
TestCases []JUnitTestCase `xml:"testcase"`
}
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Name string `xml:"name,attr"`
ClassName string `xml:"classname,attr"`
Time float64 `xml:"time,attr"`
Failure *JUnitFailure `xml:"failure,omitempty"`
Error *JUnitError `xml:"error,omitempty"`
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
}
type JUnitFailure struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Content string `xml:",chardata"`
}
type JUnitError struct {
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Content string `xml:",chardata"`
}
type JUnitSkipped struct {
Message string `xml:"message,attr"`
}
func NewCICDAuditor(config *CICDAuditConfig) (*CICDAuditor, error) {
rootDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get working directory: %w", err)
}
auditor := &CICDAuditor{
config: config,
rootDir: rootDir,
startTime: time.Now(),
results: &PipelineResults{
PipelineType: config.Pipeline,
Environment: config.Environment,
AuditResults: make([]AuditResult, 0),
QualityGates: make([]QualityGate, 0),
Notifications: make([]Notification, 0),
Artifacts: make([]Artifact, 0),
Timestamp: time.Now(),
},
}
// Initialize audit tools based on pipeline type
if err := auditor.initializeAuditTools(); err != nil {
return nil, fmt.Errorf("failed to initialize audit tools: %w", err)
}
// Initialize build info
auditor.initializeBuildInfo()
return auditor, nil
}
func (ca *CICDAuditor) initializeAuditTools() error {
// Define audit tools based on pipeline type
switch ca.config.Pipeline {
case "quick":
ca.auditTools = ca.getQuickPipelineTools()
case "standard":
ca.auditTools = ca.getStandardPipelineTools()
case "full":
ca.auditTools = ca.getFullPipelineTools()
case "custom":
return ca.loadCustomPipelineTools()
default:
return fmt.Errorf("unsupported pipeline type: %s", ca.config.Pipeline)
}
// Filter tools by stage if specified
if ca.config.Stage != "all" {
ca.auditTools = ca.filterToolsByStage(ca.config.Stage)
}
ca.results.TotalAudits = len(ca.auditTools)
return nil
}
func (ca *CICDAuditor) getQuickPipelineTools() []AuditTool {
return []AuditTool{
{
Name: "Math Audit",
Command: "./tools/math-audit/math-audit",
Args: []string{"--output", ca.config.OutputDir + "/math"},
OutputFile: "math_audit.json",
Stage: "test",
Critical: true,
Timeout: 2 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Math Precision",
Type: "error_rate",
Threshold: 1.0,
Critical: true,
Description: "Mathematical calculation error rate must be below 1bp",
},
},
},
{
Name: "Security Scan",
Command: "./tools/security-audit/security-audit",
Args: []string{"--scan", "secrets", "--output", ca.config.OutputDir + "/security"},
OutputFile: "security_scan.json",
Stage: "security",
Critical: true,
Timeout: 3 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Critical Vulnerabilities",
Type: "count",
Threshold: 0,
Critical: true,
Description: "No critical security vulnerabilities allowed",
},
},
},
}
}
func (ca *CICDAuditor) getStandardPipelineTools() []AuditTool {
tools := ca.getQuickPipelineTools()
standardTools := []AuditTool{
{
Name: "Profitability Audit",
Command: "./tools/profitability-audit/profitability-audit",
Args: []string{"--output", ca.config.OutputDir + "/profitability"},
OutputFile: "profitability_audit.json",
Stage: "test",
Critical: true,
Timeout: 5 * time.Minute,
RetryCount: 2,
QualityGates: []QualityGate{
{
Name: "Profit Accuracy",
Type: "percentage",
Threshold: 95.0,
Critical: true,
Description: "Profit calculation accuracy must be above 95%",
},
},
},
{
Name: "Gas Audit",
Command: "./tools/gas-audit/gas-audit",
Args: []string{"--network", "arbitrum", "--output", ca.config.OutputDir + "/gas"},
OutputFile: "gas_audit.json",
Stage: "test",
Critical: false,
Timeout: 3 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Gas Estimation Accuracy",
Type: "percentage",
Threshold: 90.0,
Critical: false,
Description: "Gas estimation accuracy should be above 90%",
},
},
},
{
Name: "Exchange Integration",
Command: "./tools/exchange-audit/exchange-audit",
Args: []string{"--output", ca.config.OutputDir + "/exchange"},
OutputFile: "exchange_audit.json",
Stage: "test",
Critical: true,
Timeout: 4 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Exchange Integration Score",
Type: "percentage",
Threshold: 85.0,
Critical: true,
Description: "Exchange integration score must be above 85%",
},
},
},
}
return append(tools, standardTools...)
}
func (ca *CICDAuditor) getFullPipelineTools() []AuditTool {
tools := ca.getStandardPipelineTools()
fullTools := []AuditTool{
{
Name: "Performance Audit",
Command: "./tools/performance-audit/performance-audit",
Args: []string{"--test", "all", "--duration", "3m", "--output", ca.config.OutputDir + "/performance"},
OutputFile: "performance_audit.json",
Stage: "test",
Critical: false,
Timeout: 10 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Performance Score",
Type: "percentage",
Threshold: 80.0,
Critical: false,
Description: "Overall performance score should be above 80%",
},
},
},
{
Name: "Opportunity Validator",
Command: "./tools/opportunity-validator/opportunity-validator",
Args: []string{"--test", "--output", ca.config.OutputDir + "/opportunities"},
OutputFile: "opportunity_validation.json",
Stage: "test",
Critical: true,
Timeout: 5 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Opportunity Detection Rate",
Type: "percentage",
Threshold: 90.0,
Critical: true,
Description: "Opportunity detection rate must be above 90%",
},
},
},
{
Name: "Complete Security Scan",
Command: "./tools/security-audit/security-audit",
Args: []string{"--scan", "all", "--deep", "--output", ca.config.OutputDir + "/security-full"},
OutputFile: "security_full.json",
Stage: "security",
Critical: true,
Timeout: 15 * time.Minute,
RetryCount: 1,
QualityGates: []QualityGate{
{
Name: "Security Score",
Type: "percentage",
Threshold: 85.0,
Critical: true,
Description: "Overall security score must be above 85%",
},
},
},
}
return append(tools, fullTools...)
}
func (ca *CICDAuditor) loadCustomPipelineTools() error {
// Load custom pipeline configuration from file
// This would parse a YAML/JSON configuration file
// For now, return the full pipeline as default
ca.auditTools = ca.getFullPipelineTools()
return nil
}
func (ca *CICDAuditor) filterToolsByStage(stage string) []AuditTool {
filtered := make([]AuditTool, 0)
for _, tool := range ca.auditTools {
if tool.Stage == stage {
filtered = append(filtered, tool)
}
}
return filtered
}
func (ca *CICDAuditor) initializeBuildInfo() {
ca.results.BuildInfo = BuildInfo{
BuildNumber: ca.getBuildNumber(),
GitCommit: ca.getGitCommit(),
GitBranch: ca.getGitBranch(),
GitTag: ca.getGitTag(),
Timestamp: time.Now(),
TriggeredBy: ca.getTriggeredBy(),
BuildAgent: ca.getBuildAgent(),
Environment: ca.getBuildEnvironment(),
}
}
func (ca *CICDAuditor) getBuildNumber() string {
if buildNum := os.Getenv("BUILD_NUMBER"); buildNum != "" {
return buildNum
}
if buildNum := os.Getenv("GITHUB_RUN_NUMBER"); buildNum != "" {
return buildNum
}
return fmt.Sprintf("local-%d", time.Now().Unix())
}
func (ca *CICDAuditor) getGitCommit() string {
if commit := os.Getenv("GIT_COMMIT"); commit != "" {
return commit
}
if commit := os.Getenv("GITHUB_SHA"); commit != "" {
return commit
}
// Try to get from git command
cmd := exec.Command("git", "rev-parse", "HEAD")
if output, err := cmd.Output(); err == nil {
return strings.TrimSpace(string(output))
}
return "unknown"
}
func (ca *CICDAuditor) getGitBranch() string {
if branch := os.Getenv("GIT_BRANCH"); branch != "" {
return branch
}
if branch := os.Getenv("GITHUB_REF_NAME"); branch != "" {
return branch
}
// Try to get from git command
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
if output, err := cmd.Output(); err == nil {
return strings.TrimSpace(string(output))
}
return "unknown"
}
func (ca *CICDAuditor) getGitTag() string {
if tag := os.Getenv("GIT_TAG"); tag != "" {
return tag
}
if tag := os.Getenv("GITHUB_REF_NAME"); tag != "" && os.Getenv("GITHUB_REF_TYPE") == "tag" {
return tag
}
// Try to get from git command
cmd := exec.Command("git", "describe", "--tags", "--exact-match")
if output, err := cmd.Output(); err == nil {
return strings.TrimSpace(string(output))
}
return ""
}
func (ca *CICDAuditor) getTriggeredBy() string {
if actor := os.Getenv("GITHUB_ACTOR"); actor != "" {
return actor
}
if user := os.Getenv("USER"); user != "" {
return user
}
return "unknown"
}
func (ca *CICDAuditor) getBuildAgent() string {
if agent := os.Getenv("RUNNER_NAME"); agent != "" {
return agent
}
if agent := os.Getenv("NODE_NAME"); agent != "" {
return agent
}
if hostname, err := os.Hostname(); err == nil {
return hostname
}
return "unknown"
}
func (ca *CICDAuditor) getBuildEnvironment() map[string]string {
env := make(map[string]string)
// Collect relevant environment variables
relevantVars := []string{
"CI", "GITHUB_ACTIONS", "JENKINS_URL", "GITLAB_CI",
"BUILD_ID", "JOB_NAME", "WORKSPACE",
"GO_VERSION", "NODE_VERSION", "DOCKER_VERSION",
}
for _, varName := range relevantVars {
if value := os.Getenv(varName); value != "" {
env[varName] = value
}
}
return env
}
func (ca *CICDAuditor) RunCICDPipeline(ctx context.Context) (int, error) {
defer func() {
ca.results.Duration = time.Since(ca.startTime).Milliseconds()
}()
if ca.config.Verbose {
fmt.Printf("Starting %s pipeline with %d audit tools\n", ca.config.Pipeline, len(ca.auditTools))
}
// Build audit tools first
if err := ca.buildAuditTools(ctx); err != nil {
return 1, fmt.Errorf("failed to build audit tools: %w", err)
}
// Run audits
var err error
if ca.config.Parallel {
err = ca.runAuditsParallel(ctx)
} else {
err = ca.runAuditsSequential(ctx)
}
if err != nil {
ca.results.OverallStatus = "FAILED"
ca.results.ExitCode = 1
return 1, err
}
// Evaluate quality gates
ca.evaluateQualityGates()
// Calculate metrics
ca.calculatePipelineMetrics()
// Send notifications
ca.sendNotifications()
// Generate reports
if err := ca.generateAllReports(); err != nil {
log.Printf("Failed to generate reports: %v", err)
}
// Determine exit code
if ca.results.FailedAudits > 0 || ca.hasFailedCriticalGates() {
ca.results.OverallStatus = "FAILED"
ca.results.ExitCode = 1
return 1, nil
}
ca.results.OverallStatus = "PASSED"
ca.results.ExitCode = 0
return 0, nil
}
func (ca *CICDAuditor) buildAuditTools(ctx context.Context) error {
if ca.config.Verbose {
fmt.Println("Building audit tools...")
}
// Build each audit tool
buildCommands := []struct {
name string
cmd string
args []string
dir string
}{
{
name: "math-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/math-audit/math-audit", "."},
dir: "tools/math-audit",
},
{
name: "profitability-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/profitability-audit/profitability-audit", "."},
dir: "tools/profitability-audit",
},
{
name: "gas-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/gas-audit/gas-audit", "."},
dir: "tools/gas-audit",
},
{
name: "opportunity-validator",
cmd: "go",
args: []string{"build", "-o", "../../tools/opportunity-validator/opportunity-validator", "."},
dir: "tools/opportunity-validator",
},
{
name: "exchange-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/exchange-audit/exchange-audit", "."},
dir: "tools/exchange-audit",
},
{
name: "performance-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/performance-audit/performance-audit", "."},
dir: "tools/performance-audit",
},
{
name: "security-audit",
cmd: "go",
args: []string{"build", "-o", "../../tools/security-audit/security-audit", "."},
dir: "tools/security-audit",
},
}
for _, build := range buildCommands {
if ca.config.Verbose {
fmt.Printf("Building %s...\n", build.name)
}
buildCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
cmd := exec.CommandContext(buildCtx, build.cmd, build.args...)
cmd.Dir = filepath.Join(ca.rootDir, build.dir)
if output, err := cmd.CombinedOutput(); err != nil {
cancel()
return fmt.Errorf("failed to build %s: %w\nOutput: %s", build.name, err, string(output))
}
cancel()
}
return nil
}
func (ca *CICDAuditor) runAuditsParallel(ctx context.Context) error {
if ca.config.Verbose {
fmt.Printf("Running %d audits in parallel...\n", len(ca.auditTools))
}
var wg sync.WaitGroup
results := make(chan AuditResult, len(ca.auditTools))
// Use semaphore to limit concurrency
maxConcurrency := 4
semaphore := make(chan struct{}, maxConcurrency)
for _, tool := range ca.auditTools {
wg.Add(1)
go func(tool AuditTool) {
defer wg.Done()
// Acquire semaphore
semaphore <- struct{}{}
defer func() { <-semaphore }()
result := ca.runSingleAudit(ctx, tool)
results <- result
// Check fail-fast condition
if ca.config.FailFast && result.Status == "FAILED" && tool.Critical {
// Cancel context to stop other audits
if ca.config.Verbose {
fmt.Printf("Critical audit %s failed, stopping pipeline (fail-fast mode)\n", tool.Name)
}
return
}
}(tool)
}
// Close results channel when all goroutines complete
go func() {
wg.Wait()
close(results)
}()
// Collect results
for result := range results {
ca.results.AuditResults = append(ca.results.AuditResults, result)
switch result.Status {
case "PASSED":
ca.results.PassedAudits++
case "FAILED":
ca.results.FailedAudits++
case "SKIPPED":
ca.results.SkippedAudits++
}
if ca.config.Verbose {
fmt.Printf("Audit %s: %s (%.2fs)\n", result.ToolName, result.Status, float64(result.Duration)/1000.0)
}
}
return nil
}
func (ca *CICDAuditor) runAuditsSequential(ctx context.Context) error {
if ca.config.Verbose {
fmt.Printf("Running %d audits sequentially...\n", len(ca.auditTools))
}
for _, tool := range ca.auditTools {
result := ca.runSingleAudit(ctx, tool)
ca.results.AuditResults = append(ca.results.AuditResults, result)
switch result.Status {
case "PASSED":
ca.results.PassedAudits++
case "FAILED":
ca.results.FailedAudits++
case "SKIPPED":
ca.results.SkippedAudits++
}
if ca.config.Verbose {
fmt.Printf("Audit %s: %s (%.2fs)\n", result.ToolName, result.Status, float64(result.Duration)/1000.0)
}
// Check fail-fast condition
if ca.config.FailFast && result.Status == "FAILED" && tool.Critical {
if ca.config.Verbose {
fmt.Printf("Critical audit %s failed, stopping pipeline (fail-fast mode)\n", tool.Name)
}
break
}
}
return nil
}
func (ca *CICDAuditor) runSingleAudit(ctx context.Context, tool AuditTool) AuditResult {
startTime := time.Now()
result := AuditResult{
ToolName: tool.Name,
QualityChecks: make([]QualityCheck, 0),
Recommendations: make([]string, 0),
Timestamp: startTime,
Metrics: make(map[string]interface{}),
}
// Create tool-specific timeout context
toolCtx, cancel := context.WithTimeout(ctx, tool.Timeout)
defer cancel()
// Prepare command
cmd := exec.CommandContext(toolCtx, tool.Command, tool.Args...)
cmd.Dir = ca.rootDir
// Set environment variables
cmd.Env = os.Environ()
for key, value := range tool.Environment {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
}
// Run with retry logic
var err error
var output []byte
for attempt := 0; attempt <= tool.RetryCount; attempt++ {
if attempt > 0 {
if ca.config.Verbose {
fmt.Printf("Retrying %s (attempt %d/%d)...\n", tool.Name, attempt+1, tool.RetryCount+1)
}
result.RetryCount = attempt
time.Sleep(time.Duration(attempt) * 5 * time.Second) // Exponential backoff
}
output, err = cmd.CombinedOutput()
if err == nil {
break
}
// Check if context was cancelled
if toolCtx.Err() != nil {
break
}
}
result.Duration = time.Since(startTime).Milliseconds()
if err != nil {
result.Status = "FAILED"
result.ExitCode = 1
result.ErrorOutput = string(output)
if toolCtx.Err() == context.DeadlineExceeded {
result.ErrorOutput = fmt.Sprintf("Audit timed out after %v", tool.Timeout)
}
} else {
result.Status = "PASSED"
result.ExitCode = 0
// Parse output file if it exists
outputPath := filepath.Join(ca.config.OutputDir, tool.OutputFile)
if _, err := os.Stat(outputPath); err == nil {
result.OutputFile = outputPath
ca.parseAuditOutput(tool, outputPath, &result)
}
}
// Evaluate tool-specific quality checks
ca.evaluateToolQualityChecks(tool, &result)
return result
}
func (ca *CICDAuditor) parseAuditOutput(tool AuditTool, outputPath string, result *AuditResult) {
// Parse the audit output file to extract metrics and insights
content, err := os.ReadFile(outputPath)
if err != nil {
return
}
// Try to parse as JSON to extract metrics
var data map[string]interface{}
if err := json.Unmarshal(content, &data); err == nil {
// Extract common metrics based on tool type
switch tool.Name {
case "Math Audit":
if errorRate, ok := data["max_error_bp"].(float64); ok {
result.Metrics["error_rate"] = errorRate
}
case "Security Scan", "Complete Security Scan":
if score, ok := data["metrics"].(map[string]interface{})["security_score"].(float64); ok {
result.Metrics["security_score"] = score
}
if findings, ok := data["security_findings"].([]interface{}); ok {
result.Metrics["findings_count"] = len(findings)
}
case "Performance Audit":
if score, ok := data["overall_score"].(float64); ok {
result.Metrics["performance_score"] = score
}
case "Profitability Audit":
if accuracy, ok := data["profitability_accuracy"].(float64); ok {
result.Metrics["accuracy"] = accuracy
}
}
}
}
func (ca *CICDAuditor) evaluateToolQualityChecks(tool AuditTool, result *AuditResult) {
for _, gate := range tool.QualityGates {
check := QualityCheck{
Name: gate.Name,
Threshold: gate.Threshold,
Description: gate.Description,
Critical: gate.Critical,
}
// Get actual value from metrics
var actualValue float64
var found bool
switch gate.Type {
case "error_rate":
if val, ok := result.Metrics["error_rate"].(float64); ok {
actualValue = val
found = true
}
case "percentage":
// Generic percentage check - try to match by name
for key, val := range result.Metrics {
if strings.Contains(strings.ToLower(key), "score") || strings.Contains(strings.ToLower(key), "accuracy") {
if score, ok := val.(float64); ok {
actualValue = score
found = true
break
}
}
}
case "count":
if val, ok := result.Metrics["findings_count"].(int); ok {
actualValue = float64(val)
found = true
}
}
if !found {
check.Status = "UNKNOWN"
check.Score = 0
} else {
// Evaluate based on gate type
switch gate.Type {
case "error_rate", "count":
// Lower is better
if actualValue <= gate.Threshold {
check.Status = "PASSED"
check.Score = 100.0
} else {
check.Status = "FAILED"
check.Score = 0.0
}
case "percentage":
// Higher is better
if actualValue >= gate.Threshold {
check.Status = "PASSED"
check.Score = actualValue
} else {
check.Status = "FAILED"
check.Score = actualValue
}
}
}
result.QualityChecks = append(result.QualityChecks, check)
// Also add to pipeline quality gates
pipelineGate := gate
pipelineGate.ActualValue = actualValue
pipelineGate.Status = check.Status
pipelineGate.Timestamp = time.Now()
ca.results.QualityGates = append(ca.results.QualityGates, pipelineGate)
}
}
func (ca *CICDAuditor) evaluateQualityGates() {
if ca.config.Verbose {
fmt.Println("Evaluating quality gates...")
}
for i := range ca.results.QualityGates {
gate := &ca.results.QualityGates[i]
if gate.Status == "" {
gate.Status = "NOT_EVALUATED"
}
}
}
func (ca *CICDAuditor) calculatePipelineMetrics() {
if ca.config.Verbose {
fmt.Println("Calculating pipeline metrics...")
}
// Initialize metrics
metrics := PipelineMetrics{
Benchmarks: make(map[string]float64),
ResourceUsage: ResourceUsage{},
}
// Calculate scores from audit results
var securityScores []float64
var performanceScores []float64
var qualityScores []float64
for _, result := range ca.results.AuditResults {
if result.Status == "PASSED" {
if score, ok := result.Metrics["security_score"].(float64); ok {
securityScores = append(securityScores, score)
}
if score, ok := result.Metrics["performance_score"].(float64); ok {
performanceScores = append(performanceScores, score)
}
if score, ok := result.Metrics["accuracy"].(float64); ok {
qualityScores = append(qualityScores, score)
}
}
}
// Calculate average scores
if len(securityScores) > 0 {
metrics.SecurityScore = average(securityScores)
}
if len(performanceScores) > 0 {
metrics.PerformanceScore = average(performanceScores)
}
if len(qualityScores) > 0 {
metrics.QualityScore = average(qualityScores)
}
// Calculate overall score
scores := []float64{}
if metrics.SecurityScore > 0 {
scores = append(scores, metrics.SecurityScore)
}
if metrics.PerformanceScore > 0 {
scores = append(scores, metrics.PerformanceScore)
}
if metrics.QualityScore > 0 {
scores = append(scores, metrics.QualityScore)
}
if len(scores) > 0 {
metrics.OverallScore = average(scores)
}
// Simple trend analysis
metrics.TrendAnalysis = TrendAnalysis{
ScoreTrend: "STABLE", // Would compare with historical data
IssuesTrend: "STABLE",
PerformanceTrend: "STABLE",
LastUpdated: time.Now(),
RecommendedActions: ca.generateMetricRecommendations(metrics),
}
// Resource usage (simplified)
metrics.ResourceUsage = ResourceUsage{
CPUTime: float64(ca.results.Duration) / 1000.0,
MemoryPeak: 512, // Simplified
DiskUsage: 100, // Simplified
}
ca.results.Metrics = metrics
}
func average(scores []float64) float64 {
if len(scores) == 0 {
return 0
}
sum := 0.0
for _, score := range scores {
sum += score
}
return sum / float64(len(scores))
}
func (ca *CICDAuditor) generateMetricRecommendations(metrics PipelineMetrics) []string {
recommendations := make([]string, 0)
if metrics.SecurityScore < 80.0 {
recommendations = append(recommendations, "Security score is below 80%. Review and address security findings.")
}
if metrics.PerformanceScore < 70.0 {
recommendations = append(recommendations, "Performance score is below 70%. Optimize critical paths.")
}
if metrics.QualityScore < 90.0 {
recommendations = append(recommendations, "Quality score is below 90%. Improve test coverage and code quality.")
}
if ca.results.FailedAudits > 0 {
recommendations = append(recommendations, "Some audits failed. Review failure causes and implement fixes.")
}
return recommendations
}
func (ca *CICDAuditor) hasFailedCriticalGates() bool {
for _, gate := range ca.results.QualityGates {
if gate.Critical && gate.Status == "FAILED" {
return true
}
}
return false
}
func (ca *CICDAuditor) sendNotifications() {
if ca.config.Verbose {
fmt.Println("Sending notifications...")
}
// Slack notification
if ca.config.SlackWebhook != "" {
notification := Notification{
Type: "slack",
Target: ca.config.SlackWebhook,
Message: ca.generateNotificationMessage(),
Timestamp: time.Now(),
}
if err := ca.sendSlackNotification(ca.config.SlackWebhook, notification.Message); err != nil {
notification.Status = "FAILED"
log.Printf("Failed to send Slack notification: %v", err)
} else {
notification.Status = "SENT"
}
ca.results.Notifications = append(ca.results.Notifications, notification)
}
// Email notification
if ca.config.EmailRecipients != "" {
notification := Notification{
Type: "email",
Target: ca.config.EmailRecipients,
Message: ca.generateNotificationMessage(),
Timestamp: time.Now(),
}
// Email sending would be implemented here
notification.Status = "SENT" // Placeholder
ca.results.Notifications = append(ca.results.Notifications, notification)
}
}
func (ca *CICDAuditor) generateNotificationMessage() string {
status := "✅ PASSED"
if ca.results.OverallStatus == "FAILED" {
status = "❌ FAILED"
}
return fmt.Sprintf(`CI/CD Audit Pipeline %s
Pipeline: %s
Environment: %s
Build: %s
Duration: %.1fs
Results:
- Passed: %d
- Failed: %d
- Skipped: %d
Overall Score: %.1f%%
Branch: %s
Commit: %s
`,
status,
ca.config.Pipeline,
ca.config.Environment,
ca.results.BuildInfo.BuildNumber,
float64(ca.results.Duration)/1000.0,
ca.results.PassedAudits,
ca.results.FailedAudits,
ca.results.SkippedAudits,
ca.results.Metrics.OverallScore,
ca.results.BuildInfo.GitBranch,
ca.results.BuildInfo.GitCommit[:8])
}
func (ca *CICDAuditor) sendSlackNotification(webhook, message string) error {
// Slack notification implementation would go here
// For now, just log the message
if ca.config.Verbose {
fmt.Printf("Slack notification: %s\n", message)
}
return nil
}
func (ca *CICDAuditor) generateAllReports() error {
timestamp := time.Now().Format("2006-01-02_15-04-05")
switch ca.config.ReportFormat {
case "junit":
return ca.generateJUnitReport(timestamp)
case "json":
return ca.generateJSONReport(timestamp)
case "html":
return ca.generateHTMLReport(timestamp)
case "all":
if err := ca.generateJUnitReport(timestamp); err != nil {
return err
}
if err := ca.generateJSONReport(timestamp); err != nil {
return err
}
return ca.generateHTMLReport(timestamp)
default:
return ca.generateJUnitReport(timestamp)
}
}
func (ca *CICDAuditor) generateJUnitReport(timestamp string) error {
testSuites := JUnitTestSuites{
Name: "MEV Bot Audit Pipeline",
Tests: ca.results.TotalAudits,
Failures: ca.results.FailedAudits,
Errors: 0,
Time: float64(ca.results.Duration) / 1000.0,
}
// Group by stage
stageMap := make(map[string][]AuditResult)
for _, result := range ca.results.AuditResults {
stage := "default"
for _, tool := range ca.auditTools {
if tool.Name == result.ToolName {
stage = tool.Stage
break
}
}
stageMap[stage] = append(stageMap[stage], result)
}
// Create test suites for each stage
for stage, results := range stageMap {
suite := JUnitTestSuite{
Name: fmt.Sprintf("Audit Stage: %s", stage),
Tests: len(results),
Time: 0,
}
for _, result := range results {
testCase := JUnitTestCase{
Name: result.ToolName,
ClassName: fmt.Sprintf("audit.%s", stage),
Time: float64(result.Duration) / 1000.0,
}
suite.Time += testCase.Time
if result.Status == "FAILED" {
suite.Failures++
testCase.Failure = &JUnitFailure{
Message: fmt.Sprintf("Audit failed with exit code %d", result.ExitCode),
Type: "AuditFailure",
Content: result.ErrorOutput,
}
} else if result.Status == "SKIPPED" {
testCase.Skipped = &JUnitSkipped{
Message: "Audit was skipped",
}
}
suite.TestCases = append(suite.TestCases, testCase)
}
testSuites.TestSuites = append(testSuites.TestSuites, suite)
}
// Write JUnit XML
xmlData, err := xml.MarshalIndent(testSuites, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JUnit XML: %w", err)
}
junitPath := filepath.Join(ca.config.OutputDir, fmt.Sprintf("junit-report_%s.xml", timestamp))
xmlContent := []byte(xml.Header + string(xmlData))
if err := os.WriteFile(junitPath, xmlContent, 0644); err != nil {
return fmt.Errorf("failed to write JUnit report: %w", err)
}
if ca.config.Verbose {
fmt.Printf("JUnit report generated: %s\n", junitPath)
}
return nil
}
func (ca *CICDAuditor) generateJSONReport(timestamp string) error {
jsonData, err := json.MarshalIndent(ca.results, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
jsonPath := filepath.Join(ca.config.OutputDir, fmt.Sprintf("pipeline-report_%s.json", timestamp))
if err := os.WriteFile(jsonPath, jsonData, 0644); err != nil {
return fmt.Errorf("failed to write JSON report: %w", err)
}
if ca.config.Verbose {
fmt.Printf("JSON report generated: %s\n", jsonPath)
}
return nil
}
func (ca *CICDAuditor) generateHTMLReport(timestamp string) error {
// Generate HTML report
htmlContent := ca.generateHTMLContent()
htmlPath := filepath.Join(ca.config.OutputDir, fmt.Sprintf("pipeline-report_%s.html", timestamp))
if err := os.WriteFile(htmlPath, []byte(htmlContent), 0644); err != nil {
return fmt.Errorf("failed to write HTML report: %w", err)
}
if ca.config.Verbose {
fmt.Printf("HTML report generated: %s\n", htmlPath)
}
return nil
}
func (ca *CICDAuditor) generateHTMLContent() string {
status := "success"
statusIcon := "✅"
if ca.results.OverallStatus == "FAILED" {
status = "danger"
statusIcon = "❌"
}
html := fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<title>MEV Bot Audit Pipeline Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background-color: #f8f9fa; padding: 20px; border-radius: 5px; }
.status-%s { color: %s; }
.metric { display: inline-block; margin: 10px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
.audit-result { margin: 10px 0; padding: 10px; border-left: 4px solid #ddd; }
.passed { border-left-color: #28a745; }
.failed { border-left-color: #dc3545; }
.skipped { border-left-color: #ffc107; }
table { width: 100%%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
</style>
</head>
<body>
<div class="header">
<h1>%s MEV Bot Audit Pipeline Report</h1>
<p>Pipeline: %s | Environment: %s | Build: %s</p>
<p>Generated: %s | Duration: %.1fs</p>
</div>
<h2>Overview</h2>
<div class="metric">
<strong>Overall Score:</strong> %.1f%%
</div>
<div class="metric">
<strong>Passed:</strong> %d
</div>
<div class="metric">
<strong>Failed:</strong> %d
</div>
<div class="metric">
<strong>Skipped:</strong> %d
</div>
<h2>Audit Results</h2>
`, status,
map[string]string{"success": "#28a745", "danger": "#dc3545"}[status],
statusIcon,
ca.config.Pipeline,
ca.config.Environment,
ca.results.BuildInfo.BuildNumber,
ca.results.Timestamp.Format("2006-01-02 15:04:05"),
float64(ca.results.Duration)/1000.0,
ca.results.Metrics.OverallScore,
ca.results.PassedAudits,
ca.results.FailedAudits,
ca.results.SkippedAudits)
// Add audit results
for _, result := range ca.results.AuditResults {
statusClass := strings.ToLower(result.Status)
html += fmt.Sprintf(`
<div class="audit-result %s">
<h3>%s</h3>
<p><strong>Status:</strong> %s | <strong>Duration:</strong> %.2fs</p>
<p><strong>Exit Code:</strong> %d</p>
%s
</div>
`, statusClass, result.ToolName, result.Status, float64(result.Duration)/1000.0, result.ExitCode,
func() string {
if result.ErrorOutput != "" {
return fmt.Sprintf("<p><strong>Error:</strong> %s</p>", result.ErrorOutput)
}
return ""
}())
}
// Add quality gates
if len(ca.results.QualityGates) > 0 {
html += `
<h2>Quality Gates</h2>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Threshold</th>
<th>Actual</th>
<th>Status</th>
<th>Critical</th>
</tr>`
for _, gate := range ca.results.QualityGates {
html += fmt.Sprintf(`
<tr>
<td>%s</td>
<td>%s</td>
<td>%.2f</td>
<td>%.2f</td>
<td>%s</td>
<td>%t</td>
</tr>`, gate.Name, gate.Type, gate.Threshold, gate.ActualValue, gate.Status, gate.Critical)
}
html += `
</table>`
}
html += `
</body>
</html>`
return html
}