Skip to content

工作流解析

此示例演示如何解析 GitHub Workflow 文件并提取作业信息、步骤和触发器。

解析 Workflow 文件

go
package main

import (
    "fmt"
    "log"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // 解析 workflow 文件
    workflow, err := parser.ParseFile(".github/workflows/ci.yml")
    if err != nil {
        log.Fatalf("解析 workflow 失败: %v", err)
    }

    fmt.Printf("Workflow: %s\n", workflow.Name)
    fmt.Printf("作业数: %d\n", len(workflow.Jobs))
}

访问 Workflow 触发器

go
// 访问 workflow 触发器(on 事件)
fmt.Println("\n触发器:")
switch on := workflow.On.(type) {
case string:
    fmt.Printf("  单个触发器: %s\n", on)
case []interface{}:
    fmt.Printf("  多个触发器:\n")
    for _, trigger := range on {
        fmt.Printf("    - %v\n", trigger)
    }
case map[string]interface{}:
    fmt.Printf("  复杂触发器:\n")
    for event, config := range on {
        fmt.Printf("    - %s: %v\n", event, config)
    }
}

分析作业

go
// 分析每个作业
fmt.Println("\n作业:")
for jobID, job := range workflow.Jobs {
    fmt.Printf("  %s:\n", jobID)
    if job.Name != "" {
        fmt.Printf("    名称: %s\n", job.Name)
    }
    
    // 检查 runs-on
    switch runsOn := job.RunsOn.(type) {
    case string:
        fmt.Printf("    运行在: %s\n", runsOn)
    case []interface{}:
        fmt.Printf("    运行在: %v\n", runsOn)
    }
    
    // 检查依赖关系
    if job.Needs != nil {
        switch needs := job.Needs.(type) {
        case string:
            fmt.Printf("    需要: %s\n", needs)
        case []interface{}:
            fmt.Printf("    需要: %v\n", needs)
        }
    }
    
    fmt.Printf("    步骤数: %d\n", len(job.Steps))
}

分析作业步骤

go
// 详细步骤分析
for jobID, job := range workflow.Jobs {
    fmt.Printf("\n=== 作业: %s ===\n", jobID)
    
    for i, step := range job.Steps {
        fmt.Printf("步骤 %d:\n", i+1)
        
        if step.Name != "" {
            fmt.Printf("  名称: %s\n", step.Name)
        }
        
        if step.ID != "" {
            fmt.Printf("  ID: %s\n", step.ID)
        }
        
        if step.Uses != "" {
            fmt.Printf("  使用: %s\n", step.Uses)
            
            // 显示步骤输入
            if len(step.With) > 0 {
                fmt.Printf("  输入:\n")
                for key, value := range step.With {
                    fmt.Printf("    %s: %v\n", key, value)
                }
            }
        }
        
        if step.Run != "" {
            fmt.Printf("  运行: %s\n", step.Run)
            if step.Shell != "" {
                fmt.Printf("  Shell: %s\n", step.Shell)
            }
        }
        
        if step.If != "" {
            fmt.Printf("  条件: %s\n", step.If)
        }
        
        // 显示步骤环境变量
        if len(step.Env) > 0 {
            fmt.Printf("  环境变量:\n")
            for key, value := range step.Env {
                fmt.Printf("    %s: %s\n", key, value)
            }
        }
        
        fmt.Println()
    }
}

检查可重用工作流

go
// 检查是否有作业使用可重用工作流
fmt.Println("\n可重用工作流作业:")
for jobID, job := range workflow.Jobs {
    if job.Uses != "" {
        fmt.Printf("  %s 使用: %s\n", jobID, job.Uses)
        
        // 显示传递给可重用工作流的输入
        if len(job.With) > 0 {
            fmt.Printf("    输入:\n")
            for key, value := range job.With {
                fmt.Printf("      %s: %v\n", key, value)
            }
        }
        
        // 显示传递给可重用工作流的密钥
        if job.Secrets != nil {
            fmt.Printf("    密钥: %v\n", job.Secrets)
        }
    }
}

解析多个工作流

go
package main

import (
    "fmt"
    "log"
    "path/filepath"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    // 解析 .github/workflows 目录中的所有工作流
    workflows, err := parser.ParseDir(".github/workflows")
    if err != nil {
        log.Fatalf("解析 workflows 失败: %v", err)
    }
    
    fmt.Printf("找到 %d 个工作流文件\n\n", len(workflows))
    
    for path, workflow := range workflows {
        analyzeWorkflow(path, workflow)
    }
}

func analyzeWorkflow(path string, workflow *parser.ActionFile) {
    fmt.Printf("=== %s ===\n", filepath.Base(path))
    
    if workflow.Name != "" {
        fmt.Printf("名称: %s\n", workflow.Name)
    }
    
    // 计算不同类型的触发器
    triggerCount := countTriggers(workflow.On)
    fmt.Printf("触发器: %d\n", triggerCount)
    
    // 分析作业
    fmt.Printf("作业: %d\n", len(workflow.Jobs))
    
    totalSteps := 0
    reusableJobs := 0
    
    for _, job := range workflow.Jobs {
        totalSteps += len(job.Steps)
        if job.Uses != "" {
            reusableJobs++
        }
    }
    
    fmt.Printf("总步骤数: %d\n", totalSteps)
    if reusableJobs > 0 {
        fmt.Printf("可重用工作流作业: %d\n", reusableJobs)
    }
    
    // 检查这是否是可重用工作流
    if parser.IsReusableWorkflow(workflow) {
        fmt.Printf("类型: 可重用工作流\n")
        
        inputs, _ := parser.ExtractInputsFromWorkflowCall(workflow)
        outputs, _ := parser.ExtractOutputsFromWorkflowCall(workflow)
        
        fmt.Printf("输入: %d\n", len(inputs))
        fmt.Printf("输出: %d\n", len(outputs))
    }
    
    fmt.Println()
}

func countTriggers(on interface{}) int {
    switch triggers := on.(type) {
    case string:
        return 1
    case []interface{}:
        return len(triggers)
    case map[string]interface{}:
        return len(triggers)
    default:
        return 0
    }
}

环境变量和密钥

go
// 访问工作流级别的环境变量
if len(workflow.Env) > 0 {
    fmt.Println("\n工作流环境变量:")
    for key, value := range workflow.Env {
        fmt.Printf("  %s: %s\n", key, value)
    }
}

// 访问作业级别的环境变量
for jobID, job := range workflow.Jobs {
    if len(job.Env) > 0 {
        fmt.Printf("\n作业 %s 环境变量:\n", jobID)
        for key, value := range job.Env {
            fmt.Printf("  %s: %s\n", key, value)
        }
    }
}

完整的工作流分析示例

go
package main

import (
    "fmt"
    "log"
    "os"
    "github.com/scagogogo/github-action-parser/pkg/parser"
)

func main() {
    if len(os.Args) < 2 {
        log.Fatal("用法: go run main.go <workflow.yml>")
    }
    
    workflowFile := os.Args[1]
    
    workflow, err := parser.ParseFile(workflowFile)
    if err != nil {
        log.Fatalf("解析 %s 失败: %v", workflowFile, err)
    }
    
    analyzeCompleteWorkflow(workflow)
}

func analyzeCompleteWorkflow(workflow *parser.ActionFile) {
    fmt.Printf("=== 工作流分析 ===\n")
    
    if workflow.Name != "" {
        fmt.Printf("名称: %s\n", workflow.Name)
    }
    
    // 分析触发器
    fmt.Printf("\n触发器:\n")
    analyzeTriggers(workflow.On)
    
    // 分析作业
    fmt.Printf("\n作业 (%d):\n", len(workflow.Jobs))
    for jobID, job := range workflow.Jobs {
        analyzeJob(jobID, job)
    }
    
    // 检查是否可重用
    if parser.IsReusableWorkflow(workflow) {
        fmt.Printf("\n=== 可重用工作流 ===\n")
        analyzeReusableWorkflow(workflow)
    }
}

func analyzeTriggers(on interface{}) {
    switch triggers := on.(type) {
    case string:
        fmt.Printf("  - %s\n", triggers)
    case []interface{}:
        for _, trigger := range triggers {
            fmt.Printf("  - %v\n", trigger)
        }
    case map[string]interface{}:
        for event, config := range triggers {
            fmt.Printf("  - %s:\n", event)
            if configMap, ok := config.(map[string]interface{}); ok {
                for key, value := range configMap {
                    fmt.Printf("      %s: %v\n", key, value)
                }
            }
        }
    }
}

func analyzeJob(jobID string, job parser.Job) {
    fmt.Printf("\n  %s:\n", jobID)
    
    if job.Name != "" {
        fmt.Printf("    名称: %s\n", job.Name)
    }
    
    if job.Uses != "" {
        fmt.Printf("    使用: %s (可重用工作流)\n", job.Uses)
    } else {
        fmt.Printf("    步骤: %d\n", len(job.Steps))
        
        // 显示前几个步骤
        for i, step := range job.Steps {
            if i >= 3 { // 限制为前 3 个步骤
                fmt.Printf("    ... 还有 %d 个步骤\n", len(job.Steps)-3)
                break
            }
            
            stepDesc := fmt.Sprintf("步骤 %d", i+1)
            if step.Name != "" {
                stepDesc = step.Name
            } else if step.Uses != "" {
                stepDesc = fmt.Sprintf("使用 %s", step.Uses)
            } else if step.Run != "" {
                stepDesc = "运行命令"
            }
            
            fmt.Printf("      %d. %s\n", i+1, stepDesc)
        }
    }
}

func analyzeReusableWorkflow(workflow *parser.ActionFile) {
    inputs, err := parser.ExtractInputsFromWorkflowCall(workflow)
    if err == nil && len(inputs) > 0 {
        fmt.Printf("输入 (%d):\n", len(inputs))
        for name, input := range inputs {
            required := ""
            if input.Required {
                required = " (必需)"
            }
            fmt.Printf("  - %s%s: %s\n", name, required, input.Description)
        }
    }
    
    outputs, err := parser.ExtractOutputsFromWorkflowCall(workflow)
    if err == nil && len(outputs) > 0 {
        fmt.Printf("输出 (%d):\n", len(outputs))
        for name, output := range outputs {
            fmt.Printf("  - %s: %s\n", name, output.Description)
        }
    }
}

下一步

基于 MIT 许可证发布。