优先使用本平台仓库里面的actions,来加速actions的拉取 (#4)
Some checks failed
DevStar Studio Auto Test Pipeline / unit-backend-test (push) Has been cancelled
DevStar Studio Auto Test Pipeline / unit-frontend-test (push) Has been cancelled
DevStar Studio CI/CD Pipeline / build-and-push-x86-64-docker-image (push) Failing after 10m16s

修改内容:
在发送给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:
2025-12-04 06:25:00 +00:00
committed by 孟宁
parent 8cc8e911e8
commit ea22437ae8
2 changed files with 182 additions and 1 deletions

View 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
}

View File

@@ -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,