优先使用本平台仓库里面的actions,来加速actions的拉取 (#4)
修改内容: 在发送给act_runner之前,拦截工作流字符串,检查其中的像这样 uses:actions/checkout@v4这种短链接,通过查询数据库检查平台是否含有相应的库,以加速actions仓库的拉取 特性: 现在的版本只是使用本地平台的仓库进行加速,是信任上面传来的gitea_default_actions_url,其是由app.ini的DEFAULT_ACTIONS_URL字段决定的 也就是说即使工作流中的某些短链接actions没有被加速 , 而且gitea_default_actions_url的仓库也没有,就会报错。 这里只能用如下方式配置默认DEFAULT_ACTIONS_URL , 没有配置的话默认是github ``` [actions] DEFAULT_ACTIONS_URL = self or github ``` 而本提交可以智能判断本平台上是否已有该action,没有的话才使用默认URL,这样在缺省配置的情况下智能优先选择本平台action Reviewed-on: #4 Co-authored-by: zzy <1787223498@qq.com> Co-committed-by: zzy <1787223498@qq.com>
This commit is contained in:
176
services/actions/modify_workflow.go
Normal file
176
services/actions/modify_workflow.go
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team,
|
||||
* Create: 2025-12-02
|
||||
* Description: Modify workflow payload to convert short-form action references to full URLs
|
||||
*/
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
type ActionReference struct {
|
||||
ShortRef string // 短格式引用,如 "actions/checkout@v4"
|
||||
LineNumber int // 行号(从1开始)
|
||||
}
|
||||
|
||||
func IdentifyShortFormActions(payload string) ([]ActionReference, error) {
|
||||
log.Info("Identifying short-form action references in workflow")
|
||||
|
||||
var results []ActionReference
|
||||
shortFormRegex := regexp.MustCompile(`^(\s*uses:\s*)(['"]?)([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+@[a-zA-Z0-9._-]+)(['"]?)(\s*)$`)
|
||||
|
||||
lines := strings.Split(payload, "\n")
|
||||
|
||||
for lineNum, line := range lines {
|
||||
matches := shortFormRegex.FindStringSubmatch(line)
|
||||
if len(matches) == 6 {
|
||||
shortRef := matches[3]
|
||||
|
||||
// 跳过已经是完整URL的情况
|
||||
if strings.HasPrefix(shortRef, "http://") || strings.HasPrefix(shortRef, "https://") {
|
||||
continue
|
||||
}
|
||||
|
||||
ref := ActionReference{
|
||||
ShortRef: shortRef,
|
||||
LineNumber: lineNum + 1, // 行号从1开始
|
||||
}
|
||||
|
||||
results = append(results, ref)
|
||||
log.Info("Found short-form action at line %d: %s", ref.LineNumber, ref.ShortRef)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Total short-form actions found: %d", len(results))
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func FilterAvailableActions(ctx context.Context, shortRefs []ActionReference, baseURL string) []ActionReference {
|
||||
if len(shortRefs) == 0 {
|
||||
return shortRefs
|
||||
}
|
||||
|
||||
log.Info("Filtering available actions from %d references using base URL: %s", len(shortRefs), baseURL)
|
||||
|
||||
var availableRefs []ActionReference
|
||||
|
||||
for _, ref := range shortRefs {
|
||||
// 从 shortRef 中提取仓库路径(去除版本号)
|
||||
// 例如: actions/checkout@v4 -> actions/checkout
|
||||
repoPath := ref.ShortRef
|
||||
if atIndex := strings.Index(repoPath, "@"); atIndex != -1 {
|
||||
repoPath = repoPath[:atIndex]
|
||||
}
|
||||
|
||||
// 分割 owner/repo
|
||||
parts := strings.SplitN(repoPath, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
log.Warn("Invalid repository path at line %d: %s (skipping)", ref.LineNumber, repoPath)
|
||||
continue
|
||||
}
|
||||
|
||||
ownerName := parts[0]
|
||||
repoName := parts[1]
|
||||
|
||||
// 使用传入的 context(来自事务)进行数据库查询
|
||||
if isActionAvailableInDB(ctx, ownerName, repoName) {
|
||||
availableRefs = append(availableRefs, ref)
|
||||
log.Info("Action available at line %d: %s (repo: %s/%s found in database)", ref.LineNumber, ref.ShortRef, ownerName, repoName)
|
||||
} else {
|
||||
log.Warn("Action unavailable at line %d: %s (repo: %s/%s not found in database, skipping)", ref.LineNumber, ref.ShortRef, ownerName, repoName)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Filtered actions: %d available out of %d total", len(availableRefs), len(shortRefs))
|
||||
return availableRefs
|
||||
}
|
||||
|
||||
func isActionAvailableInDB(ctx context.Context, ownerName, repoName string) bool {
|
||||
log.Info("Checking repository %s/%s in database...", ownerName, repoName)
|
||||
start := time.Now()
|
||||
|
||||
// 从数据库查询仓库是否存在
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
log.Info("Repository %s/%s not found in database (elapsed: %v): %v", ownerName, repoName, elapsed, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查仓库是否被删除或私有(可选)
|
||||
if repo == nil {
|
||||
log.Info("Repository %s/%s is nil (elapsed: %v)", ownerName, repoName, elapsed)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("Repository %s/%s found in database (elapsed: %v)", ownerName, repoName, elapsed)
|
||||
return true
|
||||
}
|
||||
|
||||
func ReplaceShortFormActions(shortRefs []ActionReference, payload string, baseURL string) (string, error) {
|
||||
if len(shortRefs) == 0 {
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// 移除 baseURL 末尾的斜杠(如果有)
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
lines := strings.Split(payload, "\n")
|
||||
|
||||
for _, ref := range shortRefs {
|
||||
if ref.LineNumber <= len(lines) {
|
||||
fullURL := fmt.Sprintf("%s/%s", baseURL, ref.ShortRef)
|
||||
|
||||
lines[ref.LineNumber-1] = strings.Replace(lines[ref.LineNumber-1], ref.ShortRef, fullURL, 1)
|
||||
log.Info("Converted line %d: '%s' -> '%s'", ref.LineNumber, ref.ShortRef, fullURL)
|
||||
}
|
||||
}
|
||||
|
||||
modifiedPayload := strings.Join(lines, "\n")
|
||||
return modifiedPayload, nil
|
||||
}
|
||||
|
||||
func ModifyWorkflowPayload(ctx context.Context, payload []byte) ([]byte, error) {
|
||||
log.Info("Modifying workflow payload")
|
||||
|
||||
payloadStr := string(payload)
|
||||
|
||||
// 先识别所有短格式引用
|
||||
shortRefs, err := IdentifyShortFormActions(payloadStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to identify short-form actions: %w", err)
|
||||
}
|
||||
|
||||
if len(shortRefs) == 0 {
|
||||
log.Info("No short-form actions to convert")
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// 过滤可用的 action 引用
|
||||
availableRefs := FilterAvailableActions(ctx, shortRefs, setting.AppURL)
|
||||
|
||||
if len(availableRefs) == 0 {
|
||||
log.Info("No available actions to convert")
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// 使用 setting.AppURL 执行替换
|
||||
modifiedPayload, err := ReplaceShortFormActions(availableRefs, payloadStr, setting.AppURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to replace short-form actions: %w", err)
|
||||
}
|
||||
|
||||
return []byte(modifiedPayload), nil
|
||||
}
|
||||
@@ -79,9 +79,14 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
||||
return fmt.Errorf("generateTaskContext: %w", err)
|
||||
}
|
||||
|
||||
modifiedPayload, err := ModifyWorkflowPayload(ctx, t.Job.WorkflowPayload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ModifyWorkflowPayload: %w", err)
|
||||
}
|
||||
|
||||
task = &runnerv1.Task{
|
||||
Id: t.ID,
|
||||
WorkflowPayload: t.Job.WorkflowPayload,
|
||||
WorkflowPayload: modifiedPayload,
|
||||
Context: taskContext,
|
||||
Secrets: secrets,
|
||||
Vars: vars,
|
||||
|
||||
Reference in New Issue
Block a user