7 Commits

Author SHA1 Message Date
f58a6daa6c [汉化] 汉化测试用例、修复部分龙架构测试用例失效的问题
Some checks failed
checks / check and test (push) Has been cancelled
release-nightly / goreleaser (push) Has been cancelled
release-nightly / release-image (push) Has been cancelled
2025-05-09 15:17:16 +08:00
54308690d0 [冲突修复] 修复和上游的冲突文件
Some checks are pending
release-nightly / goreleaser (push) Waiting to run
release-nightly / release-image (push) Waiting to run
checks / check and test (push) Waiting to run
2025-05-09 11:05:26 +08:00
4b90fa4325 [汉化] 更新main 2025-05-09 10:46:03 +08:00
5302c25feb Add environment variables for OIDC token service (#674)
Some checks failed
checks / check and test (pull_request) Has been cancelled
Resurrecting [this PR](https://gitea.com/gitea/act_runner/pulls/272) (a dependency of [this one](https://github.com/go-gitea/gitea/pull/33945)) after the original author [lost motivation](https://github.com/go-gitea/gitea/pull/25664#issuecomment-2737099259) - though, to be clear, all credit belongs to them, and all blame for mistakes or misunderstandings to me.

Co-authored-by: Søren L. Hansen <sorenisanerd@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/674
Reviewed-by: ChristopherHX <christopherhx@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jack Jackson <scubbojj@gmail.com>
Co-committed-by: Jack Jackson <scubbojj@gmail.com>
2025-05-08 01:58:31 +00:00
a616ed1a10 feat: register interactive with values from cli (#682)
I used to be able to do something like `./act_runner register --instance https://gitea.com --token testdcff --name test` on GitHub Actions Runners, but act_runner always asked me to enter instance, token etc. again and requiring me to use `--no-interactive` including passing everything per cli.

My idea was to extract the preset input of some stages to skip the prompt for this value if it is a non empty string. Labels is the only question that has been asked more than once if validation failed, in this case the error path have to unset the values of the input structure to not end in a non-interactive loop.

_I have written this initially for my own gitea runner, might be useful to everyone using the official runner as well_

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/682
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-08 01:57:53 +00:00
f0b5aff3bb fix: invalid label NoInteractive exit code (#683)
* add test
* return validation error not nil from function

Closes #665

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/683
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-07 17:17:26 +00:00
44b4736703 feat: docker env vars for ephemeral and once (#685)
* GITEA_RUNNER_EPHEMERAL=1
* GITEA_RUNNER_ONCE=1

Related https://gitea.com/gitea/act_runner/issues/684

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/685
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-07 15:43:05 +00:00
13 changed files with 242 additions and 157 deletions

View File

@ -103,7 +103,7 @@ fmt-check:
fi; fi;
test: fmt-check test: fmt-check
@$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1 @$(GO) test -v -cover -coverprofile coverage.txt ./... && echo -e "\n===> \e[32mOk\e[m\n" || exit 1
.PHONY: vet .PHONY: vet
vet: vet:

View File

@ -52,7 +52,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string)
} }
} else { } else {
go func() { go func() {
if err := registerInteractive(ctx, *configFile); err != nil { if err := registerInteractive(ctx, *configFile, regArgs); err != nil {
log.Fatal(err) log.Fatal(err)
return return
} }
@ -127,6 +127,22 @@ func validateLabels(ls []string) error {
return nil return nil
} }
func (r *registerInputs) stageValue(stage registerStage) string {
switch stage {
case StageInputInstance:
return r.InstanceAddr
case StageInputToken:
return r.Token
case StageInputRunnerName:
return r.RunnerName
case StageInputLabels:
if len(r.Labels) > 0 {
return strings.Join(r.Labels, ",")
}
}
return ""
}
func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage { func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage {
// 必须设置实例地址和令牌。 // 必须设置实例地址和令牌。
// 如果为空,保持当前阶段。 // 如果为空,保持当前阶段。
@ -181,6 +197,7 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
if validateLabels(r.Labels) != nil { if validateLabels(r.Labels) != nil {
log.Infoln("无效的标签, 请重新输入, 留空以使用默认标签 (例如, debian-latest:lcr.loongnix.cn/library/debian:latest) ") log.Infoln("无效的标签, 请重新输入, 留空以使用默认标签 (例如, debian-latest:lcr.loongnix.cn/library/debian:latest) ")
r.Labels = nil
return StageInputLabels return StageInputLabels
} }
return StageWaitingForRegistration return StageWaitingForRegistration
@ -188,11 +205,25 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
return StageUnknown return StageUnknown
} }
func registerInteractive(ctx context.Context, configFile string) error { func initInputs(regArgs *registerArgs) *registerInputs {
inputs := &registerInputs{
InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token,
RunnerName: regArgs.RunnerName,
Ephemeral: regArgs.Ephemeral,
}
regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// command line flag.
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
return inputs
}
func registerInteractive(ctx context.Context, configFile string, regArgs *registerArgs) error {
var ( var (
reader = bufio.NewReader(os.Stdin) reader = bufio.NewReader(os.Stdin)
stage = StageInputInstance stage = StageInputInstance
inputs = new(registerInputs)
) )
cfg, err := config.LoadDefault(configFile) cfg, err := config.LoadDefault(configFile)
@ -202,13 +233,17 @@ func registerInteractive(ctx context.Context, configFile string) error {
if f, err := os.Stat(cfg.Runner.File); err == nil && !f.IsDir() { if f, err := os.Stat(cfg.Runner.File); err == nil && !f.IsDir() {
stage = StageOverwriteLocalConfig stage = StageOverwriteLocalConfig
} }
inputs := initInputs(regArgs)
for { for {
printStageHelp(stage) cmdString := inputs.stageValue(stage)
if cmdString == "" {
cmdString, err := reader.ReadString('\n') printStageHelp(stage)
if err != nil { var err error
return err cmdString, err = reader.ReadString('\n')
if err != nil {
return err
}
} }
stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString), cfg) stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString), cfg)
@ -226,7 +261,7 @@ func registerInteractive(ctx context.Context, configFile string) error {
} }
if stage <= StageUnknown { if stage <= StageUnknown {
log.Errorf("无效输入,请重新运行 act 命令。") log.Errorf("无效输入,请重新运行命令。")
return nil return nil
} }
} }
@ -255,18 +290,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
if err != nil { if err != nil {
return err return err
} }
inputs := &registerInputs{ inputs := initInputs(regArgs)
InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token,
RunnerName: regArgs.RunnerName,
Labels: defaultLabels,
Ephemeral: regArgs.Ephemeral,
}
regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// 命令行标志。
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
// 配置文件中指定的标签。 // 配置文件中指定的标签。
if len(cfg.Runner.Labels) > 0 { if len(cfg.Runner.Labels) > 0 {
if regArgs.Labels != "" { if regArgs.Labels != "" {
@ -274,14 +298,17 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
} }
inputs.Labels = cfg.Runner.Labels inputs.Labels = cfg.Runner.Labels
} }
if len(inputs.Labels) == 0 {
inputs.Labels = defaultLabels
}
if inputs.RunnerName == "" { if inputs.RunnerName == "" {
inputs.RunnerName, _ = os.Hostname() inputs.RunnerName, _ = os.Hostname()
log.Infof("运行器名称为空,使用主机名 '%s'。", inputs.RunnerName) log.Infof("运行器名称为空,使用主机名 '%s'。", inputs.RunnerName)
} }
if err := inputs.validate(); err != nil { if err := inputs.validate(); err != nil {
log.WithError(err).Errorf("无效输入,请重新运行 act 命令。") log.WithError(err).Errorf("无效输入,请重新运行命令。")
return nil return err
} }
if err := doRegister(ctx, cfg, inputs); err != nil { if err := doRegister(ctx, cfg, inputs); err != nil {
return fmt.Errorf("注册运行器失败: %w", err) return fmt.Errorf("注册运行器失败: %w", err)

View File

@ -0,0 +1,19 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"gotest.tools/v3/assert"
)
func TestRegisterNonInteractiveReturnsLabelValidationError(t *testing.T) {
err := registerNoInteractive(t.Context(), "", &registerArgs{
Labels: "标签:无效",
Token: "token",
InstanceAddr: "http://localhost:3000",
})
assert.Error(t, err, "不支持的标签: 无效")
}

View File

@ -30,16 +30,16 @@ import (
// Runner 运行流水线 // Runner 运行流水线
type Runner struct { type Runner struct {
name string // Runner 名称 name string // Runner 名称
cfg *config.Config // 配置信息 cfg *config.Config // 配置信息
client client.Client // Gitea 客户端 client client.Client // Gitea 客户端
labels labels.Labels // 标签集合 labels labels.Labels // 标签集合
envs map[string]string // 环境变量 envs map[string]string // 环境变量
// 正在运行的任务 // 正在运行的任务
runningTasks sync.Map // 使用 sync.Map 来存储正在运行的任务 ID runningTasks sync.Map // 使用 sync.Map 来存储正在运行的任务 ID
} }
// NewRunner 使用提供的配置、注册信息和客户端创建一个新的 Runner 实例 // NewRunner 使用提供的配置、注册信息和客户端创建一个新的 Runner 实例
@ -173,6 +173,12 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
preset.Token = t preset.Token = t
} }
if actionsIdTokenRequestUrl := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIdTokenRequestUrl != "" {
r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = actionsIdTokenRequestUrl
r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue()
task.Secrets["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
}
giteaRuntimeToken := taskContext["gitea_runtime_token"].GetStringValue() giteaRuntimeToken := taskContext["gitea_runtime_token"].GetStringValue()
if giteaRuntimeToken == "" { if giteaRuntimeToken == "" {
// 兼容旧版本 Gitea Server // 兼容旧版本 Gitea Server

View File

@ -12,7 +12,7 @@ import (
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )
func Test_generateWorkflow(t *testing.T) { func Test_生成工作流(t *testing.T) {
type args struct { type args struct {
task *runnerv1.Task task *runnerv1.Task
} }
@ -24,32 +24,32 @@ func Test_generateWorkflow(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{ {
name: "has needs", name: "有需求",
args: args{ args: args{
task: &runnerv1.Task{ task: &runnerv1.Task{
WorkflowPayload: []byte(` WorkflowPayload: []byte(`
name: Build and deploy name: 构建部署测试
on: push on: push
jobs: jobs:
job9: job9:
needs: build needs: build
runs-on: ubuntu-latest runs-on: linux-loong64
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: ./deploy --build ${{ needs.job1.outputs.output1 }} - run: ./deploy --build ${{ needs.job1.outputs.output1 }}
- run: ./deploy --build ${{ needs.job2.outputs.output2 }} - run: ./deploy --build ${{ needs.job2.outputs.output2 }}
`), `),
Needs: map[string]*runnerv1.TaskNeed{ Needs: map[string]*runnerv1.TaskNeed{
"job1": { "job1": {
Outputs: map[string]string{ Outputs: map[string]string{
"output1": "output1 value", "output1": "输出1值",
}, },
Result: runnerv1.Result_RESULT_SUCCESS, Result: runnerv1.Result_RESULT_SUCCESS,
}, },
"job2": { "job2": {
Outputs: map[string]string{ Outputs: map[string]string{
"output2": "output2 value", "output2": "输出2值",
}, },
Result: runnerv1.Result_RESULT_SUCCESS, Result: runnerv1.Result_RESULT_SUCCESS,
}, },

View File

@ -6,6 +6,6 @@ package client
const ( const (
UUIDHeader = "x-runner-uuid" UUIDHeader = "x-runner-uuid"
TokenHeader = "x-runner-token" TokenHeader = "x-runner-token"
// Deprecated: could be removed after Gitea 1.20 released // 已弃用: 可以在Gitea 1.20发布后删除
VersionHeader = "x-runner-version" VersionHeader = "x-runner-version"
) )

View File

@ -27,7 +27,7 @@ func getHTTPClient(endpoint string, insecure bool) *http.Client {
return http.DefaultClient return http.DefaultClient
} }
// New returns a new runner client. // New返回一个新的runner客户端。
func New(endpoint string, insecure bool, uuid, token, version string, opts ...connect.ClientOption) *HTTPClient { func New(endpoint string, insecure bool, uuid, token, version string, opts ...connect.ClientOption) *HTTPClient {
baseURL := strings.TrimRight(endpoint, "/") + "/api/actions" baseURL := strings.TrimRight(endpoint, "/") + "/api/actions"
@ -39,7 +39,7 @@ func New(endpoint string, insecure bool, uuid, token, version string, opts ...co
if token != "" { if token != "" {
req.Header().Set(TokenHeader, token) req.Header().Set(TokenHeader, token)
} }
// TODO: version will be removed from request header after Gitea 1.20 released. // TODOversion将在Gitea 1.20发布后从请求标头中删除。
if version != "" { if version != "" {
req.Header().Set(VersionHeader, version) req.Header().Set(VersionHeader, version)
} }
@ -73,7 +73,7 @@ func (c *HTTPClient) Insecure() bool {
var _ Client = (*HTTPClient)(nil) var _ Client = (*HTTPClient)(nil)
// An HTTPClient manages communication with the runner API. // HTTPClient管理与runner API的通信。
type HTTPClient struct { type HTTPClient struct {
pingv1connect.PingServiceClient pingv1connect.PingServiceClient
runnerv1connect.RunnerServiceClient runnerv1connect.RunnerServiceClient

View File

@ -36,7 +36,7 @@ func Parse(str string) (*Label, error) {
label.Arg = splits[2] label.Arg = splits[2]
} }
if label.Schema != SchemeHost && label.Schema != SchemeDocker { if label.Schema != SchemeHost && label.Schema != SchemeDocker {
return nil, fmt.Errorf("不支持的 schema: %s", label.Schema) return nil, fmt.Errorf("不支持的标签: %s", label.Schema)
} }
return label, nil return label, nil
} }

View File

@ -10,7 +10,7 @@ import (
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )
func TestParse(t *testing.T) { func Test解析(t *testing.T) {
tests := []struct { tests := []struct {
args string args string
want *Label want *Label

View File

@ -23,23 +23,23 @@ import (
) )
type Reporter struct { type Reporter struct {
ctx context.Context // 上下文,用于控制生命周期 ctx context.Context // 上下文,用于控制生命周期
cancel context.CancelFunc // 取消函数 cancel context.CancelFunc // 取消函数
closed bool // 是否已关闭 closed bool // 是否已关闭
client client.Client // Gitea 客户端 client client.Client // Gitea 客户端
clientM sync.Mutex // 客户端访问互斥锁 clientM sync.Mutex // 客户端访问互斥锁
logOffset int // 日志偏移量 logOffset int // 日志偏移量
logRows []*runnerv1.LogRow // 日志行缓存 logRows []*runnerv1.LogRow // 日志行缓存
logReplacer *strings.Replacer // 日志内容替换器 logReplacer *strings.Replacer // 日志内容替换器
oldnew []string // 需要替换的敏感信息 oldnew []string // 需要替换的敏感信息
state *runnerv1.TaskState // 任务状态 state *runnerv1.TaskState // 任务状态
stateMu sync.RWMutex // 状态访问读写锁 stateMu sync.RWMutex // 状态访问读写锁
outputs sync.Map // 输出参数存储 outputs sync.Map // 输出参数存储
debugOutputEnabled bool // 是否启用调试输出 debugOutputEnabled bool // 是否启用调试输出
stopCommandEndToken string // 停止命令结束标记 stopCommandEndToken string // 停止命令结束标记
} }
// NewReporter 构造函数 初始化日志脱敏规则、状态等信息 // NewReporter 构造函数 初始化日志脱敏规则、状态等信息

View File

@ -19,122 +19,133 @@ import (
"git.whlug.cn/LAA/loong_runner/internal/pkg/client/mocks" "git.whlug.cn/LAA/loong_runner/internal/pkg/client/mocks"
) )
func TestReporter_parseLogRow(t *testing.T) { func Test记录器_解析输出日志(t *testing.T) {
tests := []struct { tests := []struct {
name string name string // 测试用例名称
debugOutputEnabled bool debugOutputEnabled bool // 是否启用调试输出
args []string args []string // 输入的日志行
want []string want []string // 期望的输出结果
}{ }{
{ {
"No command", false, name: "无命令",
[]string{"Hello, world!"}, debugOutputEnabled: false,
[]string{"Hello, world!"}, args: []string{"你好,世界!"},
want: []string{"你好,世界!"},
}, },
{ {
"Add-mask", false, name: "添加掩码",
[]string{ debugOutputEnabled: false,
"foo mysecret bar", args: []string{
"::add-mask::mysecret", "foo 我的密钥 bar", // 输入日志:普通日志行
"foo mysecret bar", "::add-mask::我的密钥", // 输入命令:添加掩码
"foo 我的密钥 bar", // 输入日志:再次普通日志行
}, },
[]string{ want: []string{
"foo mysecret bar", "foo 我的密钥 bar", // 原始日志直接输出
"<nil>", "<nil>", // 添加掩码命令处理结果(无输出内容)
"foo *** bar", "foo *** bar", // 掩码替换后的日志行
}, },
}, },
{ {
"Debug enabled", true, name: "启用调试",
[]string{ debugOutputEnabled: true,
"::debug::GitHub Actions runtime token access controls", args: []string{
"::debug::GitHub Actions 运行时令牌访问控制",
}, },
[]string{ want: []string{
"GitHub Actions runtime token access controls", "GitHub Actions 运行时令牌访问控制", // 调试信息直接输出
}, },
}, },
{ {
"Debug not enabled", false, name: "禁用调试",
[]string{ debugOutputEnabled: false,
"::debug::GitHub Actions runtime token access controls", args: []string{
"::debug::GitHub Actions 运行时令牌访问控制",
}, },
[]string{ want: []string{
"<nil>", "<nil>", // 调试信息被忽略
}, },
}, },
{ {
"notice", false, name: "通知",
[]string{ debugOutputEnabled: false,
"::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", args: []string{
"::notice file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通",
}, },
[]string{ want: []string{
"::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", "::notice file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通", // 通知日志原样输出
}, },
}, },
{ {
"warning", false, name: "警告",
[]string{ debugOutputEnabled: false,
"::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", args: []string{
"::warning file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通",
}, },
[]string{ want: []string{
"::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", "::warning file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通", // 警告日志原样输出
}, },
}, },
{ {
"error", false, name: "错误",
[]string{ debugOutputEnabled: false,
"::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", args: []string{
"::error file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通",
}, },
[]string{ want: []string{
"::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", "::error file=文件.name,line=42,endLine=48,title=酷标题::天啊,这行不通", // 错误日志原样输出
}, },
}, },
{ {
"group", false, name: "分组",
[]string{ debugOutputEnabled: false,
"::group::", args: []string{
"::endgroup::", "::group::", // 开始分组
"::endgroup::", // 结束分组
}, },
[]string{ want: []string{
"::group::", "::group::", // 分组开始标记原样输出
"::endgroup::", "::endgroup::", // 分组结束标记原样输出
}, },
}, },
{ {
"stop-commands", false, name: "停止命令",
[]string{ debugOutputEnabled: false,
"::add-mask::foo", args: []string{
"::stop-commands::myverycoolstoptoken", "::add-mask::foo", // 添加掩码命令
"::add-mask::bar", "::stop-commands::我的停止令牌", // 停止命令标记
"::debug::Stuff", "::add-mask::bar", // 被忽略的添加掩码命令
"myverycoolstoptoken", "::debug::调试信息", // 被忽略的调试信息
"::add-mask::baz", "我的停止令牌", // 停止命令标记结束
"::myverycoolstoptoken::", "::add-mask::baz", // 恢复处理的添加掩码命令
"::add-mask::wibble", "::我的停止令牌::", // 另一种停止命令标记
"foo bar baz wibble", "::add-mask::wibble", // 被忽略的添加掩码命令
"foo bar baz wibble", // 普通日志行
}, },
[]string{ want: []string{
"<nil>", "<nil>", // 第一个添加掩码命令处理结果
"<nil>", "<nil>", // 停止命令标记处理结果
"::add-mask::bar", "::add-mask::bar", // 被忽略的命令原样输出
"::debug::Stuff", "::debug::调试信息", // 被忽略的调试信息原样输出
"myverycoolstoptoken", "我的停止令牌", // 停止标记结束原样输出
"::add-mask::baz", "::add-mask::baz", // 恢复处理的添加掩码命令
"<nil>", "<nil>", // 无效停止命令标记处理结果
"<nil>", "<nil>", // 被忽略的添加掩码命令处理结果
"*** bar baz ***", "*** bar baz ***", // 掩码替换后的日志行
}, },
}, },
{ {
"unknown command", false, name: "未知命令",
[]string{ debugOutputEnabled: false,
"::set-mask::foo", args: []string{
"::set-mask::foo", // 未知命令
}, },
[]string{ want: []string{
"::set-mask::foo", "::set-mask::foo", // 未知命令原样输出
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Reporter{ r := &Reporter{
@ -155,43 +166,58 @@ func TestReporter_parseLogRow(t *testing.T) {
} }
} }
func TestReporter_Fire(t *testing.T) { // 测试 ReporterFire 方法(验证命令行忽略逻辑)
t.Run("ignore command lines", func(t *testing.T) { func Test记录器_触发(t *testing.T) {
t.Run("忽略命令行", func(t *testing.T) { // 测试场景:验证是否正确处理需要忽略的命令行日志
// 创建模拟客户端
client := mocks.NewClient(t) client := mocks.NewClient(t)
// 模拟 UpdateLog 接口调用,记录请求内容并返回响应
client.On("UpdateLog", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateLogRequest]) (*connect_go.Response[runnerv1.UpdateLogResponse], error) { client.On("UpdateLog", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateLogRequest]) (*connect_go.Response[runnerv1.UpdateLogResponse], error) {
t.Logf("Received UpdateLog: %s", req.Msg.String()) t.Logf("收到 UpdateLog 请求:%s", req.Msg.String()) // 记录日志请求内容
return connect_go.NewResponse(&runnerv1.UpdateLogResponse{ return connect_go.NewResponse(&runnerv1.UpdateLogResponse{
AckIndex: req.Msg.Index + int64(len(req.Msg.Rows)), AckIndex: req.Msg.Index + int64(len(req.Msg.Rows)), // 计算确认索引
}), nil }), nil
}) })
// 模拟 UpdateTask 接口调用
client.On("UpdateTask", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateTaskRequest]) (*connect_go.Response[runnerv1.UpdateTaskResponse], error) { client.On("UpdateTask", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateTaskRequest]) (*connect_go.Response[runnerv1.UpdateTaskResponse], error) {
t.Logf("Received UpdateTask: %s", req.Msg.String()) t.Logf("收到 UpdateTask 请求:%s", req.Msg.String()) // 记录任务更新请求
return connect_go.NewResponse(&runnerv1.UpdateTaskResponse{}), nil return connect_go.NewResponse(&runnerv1.UpdateTaskResponse{}), nil
}) })
// 初始化上下文和任务
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
taskCtx, err := structpb.NewStruct(map[string]interface{}{}) taskCtx, err := structpb.NewStruct(map[string]interface{}{}) // 创建空任务上下文
require.NoError(t, err) require.NoError(t, err)
// 创建 Reporter 实例
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{ reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{
Context: taskCtx, Context: taskCtx, // 注入任务上下文
}) })
defer func() { defer func() {
assert.NoError(t, reporter.Close("")) assert.NoError(t, reporter.Close("")) // 测试结束关闭 Reporter
}() }()
reporter.ResetSteps(5)
reporter.ResetSteps(5) // 初始化5个步骤的日志存储
// 定义步骤0的日志元数据
dataStep0 := map[string]interface{}{ dataStep0 := map[string]interface{}{
"stage": "Main", "stage": "Main", // 阶段名称
"stepNumber": 0, "stepNumber": 0, // 步骤编号
"raw_output": true, "raw_output": true, // 启用原始输出模式
} }
assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) // 发送混合类型的日志条目 ---------------------------------------------------
assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) // 预期:普通日志被记录,调试日志被忽略
assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) assert.NoError(t, reporter.Fire(&log.Entry{Message: "普通日志行", Data: dataStep0}))
assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::调试日志行", Data: dataStep0})) // 应被忽略
assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) assert.NoError(t, reporter.Fire(&log.Entry{Message: "普通日志行", Data: dataStep0}))
assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::调试日志行", Data: dataStep0})) // 应被忽略
assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::调试日志行", Data: dataStep0})) // 应被忽略
assert.NoError(t, reporter.Fire(&log.Entry{Message: "普通日志行", Data: dataStep0}))
assert.Equal(t, int64(3), reporter.state.Steps[0].LogLength) // 验证结果:步骤0应只有3条普通日志(调试日志被过滤)
assert.Equal(t, int64(3), reporter.state.Steps[0].LogLength, "普通日志数量不符预期")
}) })
} }

View File

@ -14,6 +14,6 @@ import (
func main() { func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop() defer stop()
// run the command // 运行命令
cmd.Execute(ctx) cmd.Execute(ctx)
} }

View File

@ -20,6 +20,13 @@ EXTRA_ARGS=""
if [[ ! -z "${GITEA_RUNNER_LABELS}" ]]; then if [[ ! -z "${GITEA_RUNNER_LABELS}" ]]; then
EXTRA_ARGS="${EXTRA_ARGS} --labels ${GITEA_RUNNER_LABELS}" EXTRA_ARGS="${EXTRA_ARGS} --labels ${GITEA_RUNNER_LABELS}"
fi fi
if [[ ! -z "${GITEA_RUNNER_EPHEMERAL}" ]]; then
EXTRA_ARGS="${EXTRA_ARGS} --ephemeral"
fi
RUN_ARGS=""
if [[ ! -z "${GITEA_RUNNER_ONCE}" ]]; then
RUN_ARGS="${RUN_ARGS} --once"
fi
# 如果没有设置令牌,可以从文件中读取令牌,例如从 Docker Secret # 如果没有设置令牌,可以从文件中读取令牌,例如从 Docker Secret
if [[ -z "${GITEA_RUNNER_REGISTRATION_TOKEN}" ]] && [[ -f "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE}" ]]; then if [[ -z "${GITEA_RUNNER_REGISTRATION_TOKEN}" ]] && [[ -f "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE}" ]]; then
@ -60,4 +67,4 @@ unset GITEA_RUNNER_REGISTRATION_TOKEN
unset GITEA_RUNNER_REGISTRATION_TOKEN_FILE unset GITEA_RUNNER_REGISTRATION_TOKEN_FILE
# 启动 act_runner 守护进程 # 启动 act_runner 守护进程
exec act_runner daemon ${CONFIG_ARG} exec act_runner daemon ${CONFIG_ARG} ${RUN_ARGS}