81 Commits
v0.2.4 ... main

Author SHA1 Message Date
f58a6daa6c [汉化] 汉化测试用例、修复部分龙架构测试用例失效的问题 2025-05-09 15:17:16 +08:00
54308690d0 [冲突修复] 修复和上游的冲突文件 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)
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
e9c297600c [汉化] 添加一些运行时候调用函数的注释, 帮助理解运行流程 2025-05-07 18:15:42 +08:00
19bcf7e525 [汉化] 新增汉化两个文件修复poller中的汉化导致的一些异 2025-05-07 09:43:17 +08:00
e5259a8fb7 [汉化] 汉化项目的所有输出和各个注释方便阅读, 并且按照上游格式化进行格式化 2025-04-21 09:53:42 +08:00
9bed659875 [汉化] 翻译实例文档和修改名称 2025-04-20 15:50:01 +08:00
b1ae30dda8 ephemeral act runner (#649)
Works for both interactive and non-interactive registration mode.

A further enhancement would be jitconfig support of the daemon command, because after some changes in Gitea Actions the registration token became reusable.

removing runner and fail seems not possible at the current api level

Part of https://github.com/go-gitea/gitea/pull/33570

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/649
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
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-03-13 21:57:44 +00:00
0d687268c7 act_runner requires go 1.24 now 2025-03-02 05:36:24 +00:00
425a570261 use new docker image URLs (#661)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/661
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-03-01 20:21:52 +00:00
4c8179ee12 upgrade to go1.24, act to 0.261.4 and actions-proto-go to 0.4.1 (#662)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/662
Reviewed-by: ChristopherHX <christopherhx@noreply.gitea.com>
2025-03-01 20:18:36 +00:00
5ae13f0bd7 Update xgo version to 1.24 (#651)
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/651
Co-authored-by: Pablo Carranza <pcarranza@gmail.com>
Co-committed-by: Pablo Carranza <pcarranza@gmail.com>
2025-02-15 16:07:18 +00:00
3510152e36 Fix Makefile make docker (#641)
Fix #640

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/641
2025-01-29 03:27:47 +00:00
8dfb805c62 Update examples/kubernetes/dind-docker.yaml to reflect recent changes to Dockerfile (#633)
With the changes made two months ago for the Dockerfile /opt/act/run.sh no longer exists in the docker container, this caused this example to fail, updating the example so that it correctly references run.sh now located in /usr/local/bin

I have used this to deploy on my own cluster and it is now working swimmingly

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/633
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: armistace <armistace@noreply.gitea.com>
Co-committed-by: armistace <armistace@noreply.gitea.com>
2025-01-26 02:10:17 +00:00
a7080f5457 Update examples for GITEA_RUNNER_REGISTRATION_TOKEN env (#630)
For https://github.com/go-gitea/gitea/pull/32946

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/630
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2025-01-05 22:25:32 +00:00
8b72d1c7ae add s390x and riscv64 as an arch for binaries 2024-12-09 18:49:38 +00:00
8bc0275e74 feat: add once flag to daemon command (#19) (#598)
Once flag polls and completes one job then exits.

I use this with Windows Sandbox (and creating users with local brew install on Mac) to create a fresh environment every time.

Co-authored-by: Garet Halliday <garet@pit.dev>
Co-authored-by: Jason Song <wolfogre@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/598
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: Jason Song <wolfogre@noreply.gitea.com>
Co-authored-by: garet90 <garet90@noreply.gitea.com>
Co-committed-by: garet90 <garet90@noreply.gitea.com>
2024-11-06 17:16:08 +00:00
0348aaac59 Wait for the Docker daemon to be ready before starting act runner (#620)
Follow #619.

Wait for the Docker daemon to be ready before starting act runner.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/620
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-11-06 07:38:31 +00:00
9712481bed Support basic, dind and dind-rootless as multiple kinds of images (#619)
- `basic`: Only the runner process in the container; users need to mount the Docker socket to it.
- `dind`: A Docker daemon will be started in the container with the root user.
- `dind-rootless`: A Docker daemon will be started in the container with a rootless user.

Use s6 instead of supervisord to start processes.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/619
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-11-06 03:15:51 +00:00
b5f901b2d9 Upgrade act from v0.261.2 -> v0.261.3 (#607)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/607
Reviewed-by: Jason Song <wolfogre@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-10-18 04:33:57 +00:00
0e2a3e00f5 examples/vm/rootless-docker.md aktualisiert (#487)
Depending on the VM's existing users the id can vary

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/487
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: frank-dspeed <frank-dspeed@noreply.gitea.com>
Co-committed-by: frank-dspeed <frank-dspeed@noreply.gitea.com>
2024-09-30 01:55:04 +00:00
b282356e9e update example for docker-compose to allow fix 502 errors in case Gitea not yet ready on runner startup (#605)
Minimalistic approach: Only adds what is needed to fix #600
Context: https://blog.schallbert.de/en/fix-gitea-runner/

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/605
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Co-authored-by: Schallbert <schallbert@mailbox.org>
Co-committed-by: Schallbert <schallbert@mailbox.org>
2024-09-26 05:54:54 +00:00
b075e3a1d5 Bump goreleaser-action to use v2 by default (#604)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/604
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-09-24 08:46:21 +00:00
e27189ea32 Fix releasing (#603)
```
[command]/opt/hostedtoolcache/goreleaser-action/2.3.2-pro/x64/goreleaser release --nightly
  • by using this software you agree with its EULA, available at https://goreleaser.com/eula
  • running goreleaser v2.3.2-pro
  • only configurations files on  version: 2  are supported, yours is  version: 0 , please update your configuration
  ⨯ release failed after 0s                  error=only configurations files on  version: 2  are supported, yours is  version: 0 , please update your configuration
::error::The process '/opt/hostedtoolcache/goreleaser-action/2.3.2-pro/x64/goreleaser' failed with exit code 1
```

```
#20 [linux/arm64 builder 2/5] RUN apk add --no-cache make git
#20 CANCELED
------
 > [linux/amd64 builder 5/5] RUN make clean && make build:
0.058 go clean -x -i ./...
0.061 go: go.mod requires go >= 1.22 (running go 1.21.10; GOTOOLCHAIN=local)
0.061 make: *** [Makefile:176: clean] Error 1
------
 1 warning found (use docker --debug to expand):
 - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1)
Dockerfile:8
--------------------
   6 |     WORKDIR /opt/src/act_runner
   7 |
   8 | >>> RUN make clean && make build
   9 |
  10 |     FROM alpine:3.18
--------------------
ERROR: failed to solve: process "/bin/sh -c make clean && make build" did not complete successfully: exit code: 2
::error::buildx failed with: ERROR: failed to solve: process "/bin/sh -c make clean && make build" did not complete successfully: exit code: 2
```

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/603
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-09-24 08:22:53 +00:00
59e478464e Bump act to v0.261.2 (#602)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/602
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-09-24 07:04:47 +00:00
e1c7b20898 Bump Go to 1.22 and upgrade dependencies (#580)
Co-authored-by: Chongyi Zheng <git@zcy.dev>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/580
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: harryzcy <harryzcy@noreply.gitea.com>
Co-committed-by: harryzcy <harryzcy@noreply.gitea.com>
2024-07-26 16:00:33 +00:00
2f78411c3d ci: standardize code style and update version extraction (#566)
- Change single quotes to double quotes in YAML files
- Update REPO_VERSION extraction method to use GITHUB_REF_NAME without the 'v' prefix

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/566
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-06-28 07:48:10 +00:00
d1d3cad4b0 feat: allow graceful shutdowns (#546)
Add a `Shutdown(context.Context) error` method to the Poller. Calling this method will first shutdown all active polling, preventing any new jobs from spawning. It will then wait for either all jobs to finish, or for the context to be cancelled. If the context is cancelled, it will then force all jobs to end, and then exit.

Fixes https://gitea.com/gitea/act_runner/issues/107

Co-authored-by: Rowan Bohde <rowan.bohde@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/546
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: rowan-allspice <rowan-allspice@noreply.gitea.com>
Co-committed-by: rowan-allspice <rowan-allspice@noreply.gitea.com>
2024-05-27 07:38:55 +00:00
1735b26e66 Don't log job output when debug logging is not enabled (#543)
We wanted the ability to disable outputting the logs from the individual job to the console. This changes the logging so that job logs are only output to the console whenever debug logging is enabled in `act_runner`, while still allowing the `Reporter` to receive these logs and forward them to Gitea when debug logging is not enabled.

Co-authored-by: Rowan Bohde <rowan.bohde@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/543
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: rowan-allspice <rowan-allspice@noreply.gitea.com>
Co-committed-by: rowan-allspice <rowan-allspice@noreply.gitea.com>
2024-05-07 05:58:33 +00:00
65ed62d2f5 Upgrade dependencies (#537)
Patially fix #513

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/537
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-11 04:36:00 +00:00
ec03f19650 Bump act to v0.261.1 (#535)
Related to https://gitea.com/gitea/act/compare/v0.261.0...v0.261.1

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/535
2024-04-10 07:07:56 +00:00
8567324a19 Release nightly versions for tags (#536)
Or the latest nightly version could be "v0.2.8-4-gbe2df36" even though there's "v0.2.9", although they are for the same commit.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/536
2024-04-10 07:06:08 +00:00
be2df361ef Ensure declare to use new labels (#530)
Ensure "declare" is supported, to use new labels, see https://gitea.com/gitea/act_runner/pulls/529

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/530
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-02 07:39:40 +00:00
a5085dde0c Use gitea/runner-images by default (#529)
See https://gitea.com/gitea/runner-images

Also give up ubuntu-18.04 since it's too old. And enable force_pull by default to check new versions of images.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/529
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-02 07:38:14 +00:00
cef86d1140 Bump act to v0.260.0 (#528)
Related to:

- https://gitea.com/gitea/act/pulls/104
- https://gitea.com/gitea/act/issues/102

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/528
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-04-02 06:25:33 +00:00
94c45acf6b Use node 20 by default (#526)
Because Octokit requires Node 18 or higher now.

See https://github.com/octokit/octokit.js/#fetch-missing .

However, for existing runners, users should manually modify `labels` in the `.runner` file or `runner.labels` in the `config.yaml` file to update. Don't forget to restart.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/526
Reviewed-by: sillyguodong <sillyguodong@noreply.gitea.com>
2024-03-29 03:03:26 +00:00
1760899d27 Set gitea token to release (#524)
Follow a7eca813ea/.gitea/workflows/release-tag.yml (L39)

It works before unexpectedly because of https://github.com/nektos/act/pull/2089

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/524
2024-03-27 10:51:45 +00:00
a7eca813ea Bump act to v0.260.2 (#523)
Related to

- https://gitea.com/gitea/act/pulls/86
- https://gitea.com/gitea/act/pulls/103

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/523
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2024-03-27 10:25:23 +00:00
23ec12b8cf Bump act to v0.260.0 (#522)
Related to https://gitea.com/gitea/act/issues/99.

Also update other main dependencies.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/522
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
2024-03-27 03:17:04 +00:00
a1fc2b3ca7 Improve the explanation of workdir_parent config (#519)
Fix #512

act_runner adds a '/' before the path (see 5977042b86/internal/app/run/runner.go (L186)) . So `workdir_parent` doesn't need to have the prefix '/'.

If `workdir_parent` has the prefix '/', errors will occur when reading files from the job container.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/519
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2024-03-22 02:30:31 +00:00
5977042b86 Bump act to v0.259.2 (#515)
Related to https://gitea.com/gitea/act/pulls/97

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/515
Reviewed-by: silverwind <silverwind@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-03-14 18:58:43 +00:00
6bc19cbc33 Improve workflows (#516)
Starting from setup-go v4, it will cache build dependencies by default, see https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs.

Also bump some versions.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/516
Reviewed-by: silverwind <silverwind@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-03-14 13:27:47 +00:00
75006a59cc Support cloning remote actions from insecure Gitea instances (#508)
Follow https://gitea.com/gitea/act/pulls/92

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/508
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2024-03-06 06:10:37 +00:00
4da97f53de Bump act to v0.259.0 (#491)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/491
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: silverwind <silverwind@noreply.gitea.com>
Co-authored-by: SupremeVoid <supremevoid@t-online.de>
Co-committed-by: SupremeVoid <supremevoid@t-online.de>
2024-03-03 16:34:37 +00:00
45270656df Set the status of steps to skipped if job is skipped (#500)
If a job is detected as skipped, its steps should also be `skipped`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/500
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2024-03-01 08:33:32 +00:00
e14f42c40a Add ACTIONS_RESULTS_URL to env (#473)
actions/upload-artifact@v4 and actions/download-artifact@v4 depend on this variable

BaseUrl in a url are ignored by the nodejs code of the new actions, so this change doesn't append the path of the older `ACTIONS_RUNTIME_URL`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/473
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>
2024-02-19 02:30:54 +00:00
e6630e2e36 Use artifacts v4 jwt if available (#471)
Needs https://github.com/go-gitea/gitea/pull/28885 to provide jwt if sent by server

Could fix #459, but that has not been verified.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/471
Reviewed-by: delvh <dev.lh@web.de>
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>
2024-02-08 02:43:39 +00:00
f1f9142a3c fix: Exit docker container on any process exit (#462)
## Description
Issue described in #460

## Changes

- Edited `supervisord.conf` to exit if it detects any of the supervisored processes exiting.
- minor text fix

## Notes

Without this change (or something similar), if act_runner fails, then the container will stay up as a zombie container - it does nothing and does not restart. After this change, if act_runner fails (e.g. due to Gitea instance being down), then supervisord will exit and the container will be restarted.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/462
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: davidfrickert <david.frickert@protonmail.com>
Co-committed-by: davidfrickert <david.frickert@protonmail.com>
2024-01-30 13:47:42 +00:00
f17cad1bbe Update the docker image rebuild flag for config file and example gitea/act_runner#390 (#391)
this is a PR for the issue gitea/act_runner#390
It adding configuration for dockerfile container image to rebuild base on the configuration flag force_rebuild in config.yaml

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/391
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Alex Lau(AvengerMoJo) <avengermojo@gmail.com>
Co-committed-by: Alex Lau(AvengerMoJo) <avengermojo@gmail.com>
2023-12-20 07:13:33 +00:00
daf52d0e62 Sanitize UFT-8 content in logs (#453)
I accidently closed my previous PR #384

This PR replaces invalid UTF-8 character in a stream with `?` character. On Windows Server 2019 other characters are replaced by `?` as well so it's consistent.

fixes #452

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/453
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: hakito <hakito@noreply.gitea.com>
Co-committed-by: hakito <hakito@noreply.gitea.com>
2023-12-20 07:06:46 +00:00
8c8a8ce401 fix(deps): update module github.com/avast/retry-go/v4 to v4.5.1 (#411)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-28 05:46:05 +00:00
08c681be0c fix(deps): update module golang.org/x/time to v0.5.0 (#429)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-28 05:45:28 +00:00
91bfe4c186 fix(deps): update module golang.org/x/time to v0.4.0 (#424)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-26 00:01:51 +00:00
825c6f97b7 fix(deps): update module github.com/mattn/go-isatty to v0.0.20 (#414)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 08:33:37 +00:00
2f3e5c7125 fix(deps): update module github.com/docker/docker to v24.0.7+incompatible (#413)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 08:27:38 +00:00
4d9de6ca8c fix(deps): update module github.com/spf13/cobra to v1.8.0 (#416)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 08:27:07 +00:00
feb39666cc fix(deps): update module code.gitea.io/gitea-vet to v0.2.3 (#410)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 08:26:24 +00:00
61cd71c9f8 bump to actions/checkout v4 2023-11-24 03:06:21 +00:00
0adfc1c7cc chore(deps): update actions/setup-go action to v4 (#418)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 03:05:33 +00:00
e3c68668fa chore(deps): update docker/build-push-action action to v5 (#419)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 03:05:28 +00:00
f1b27d5274 chore(deps): update docker/login-action action to v3 (#420)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 03:05:22 +00:00
655a39fd61 chore(deps): update docker/setup-buildx-action action to v3 (#421)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 03:05:07 +00:00
cca7d54117 chore(deps): update docker/setup-qemu-action action to v3 (#422)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-24 03:05:00 +00:00
934471813a Fix #404: nil map error when reading env file (#405)
Co-authored-by: Mark Glines <mark@glines.org>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/405
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: infinoid <infinoid@noreply.gitea.com>
Co-committed-by: infinoid <infinoid@noreply.gitea.com>
2023-11-24 01:56:27 +00:00
1e940f028b Add renovate config (#408)
+ write access required for `renovate-bot` user

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/408
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: pat-s <patrick.schratz@gmail.com>
Co-committed-by: pat-s <patrick.schratz@gmail.com>
2023-11-23 20:41:10 +00:00
2020ce79bf Remove ACTIONS_RUNTIME_TOKEN workaround (#396)
The bug has been fixed for a long time in the GitHub version.
The fix commit is d8823bfaed and released in the 4.0.0 and after. The issue also mentions that https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/396
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: lng2020 <nanguanlin6@gmail.com>
Co-committed-by: lng2020 <nanguanlin6@gmail.com>
2023-11-07 05:15:14 +00:00
00e9b3d62b Bump act (#394)
Bump act to follow https://gitea.com/gitea/act/pulls/81

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/394
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-10-31 15:03:07 +00:00
130b9f1877 Added environment variable for run.sh to specify runner state file. (#377)
Added environment variable `RUNNER_STATE_FILE` to let users specify
where `run.sh` looks for the runner JSON file. Defaults to ``.runner``
to preserve the original behavior.

Addresses issue #368. It's not my preferred solution but it's the least invasive one I can think of.

I'm happy to make any changes you want.

I didn't see an appropriate place to reference the change in the documentation. I will add documentation wherever you think is appropriate.

To test this I did the following:
* Built an image with the `make docker` command and pushed it to my private registry.
* Added that private image as the image in my existing docker stack that was exhibiting the behavior described in #368.
* Added the RUNNER_STATE_FILE environment variable pointing to the runner JSON file set in my `config.yml` (``/data/runner.json`` in this case).
* Configured a new runner in gitea and added the token as an environment variable in the stack config.
* Deployed the stack and verified the new runner was recognized (in the idle state).
* Force updated the runner service to restart the container.
* Verified the same runner was still recognized in gitea (in the idle state) once it was back up and running.

Here is the relevant config. It's stored as a template so I've left the things that would normally be redacted as they are in the template.

```
    runner:
     image: hub.hax.in.net/haxwithaxe/act_runner:dev1
      networks:
        - swarm-net
      environment:
       RUNNER_STATE_FILE: /data/runner.json
        CONFIG_FILE: /data/config.yml
        GITEA_INSTANCE_URL: "https://git_gitea"
        GITEA_RUNNER_REGISTRATION_TOKEN: "{{ git_runner_reg_token }}"
        GITEA_RUNNER_NAME: "git_runner"
      volumes:
        - runner_data:/data
        - /var/run/docker.sock:/var/run/docker.sock
```

`runner_data` is a glusterfs subvolume

Thanks for creating this gitea specific fork. Apart from the issue this pull request addresses it works so well I don't have to think about it once it's set up.

Co-authored-by: haxwithaxe <spam@haxwithaxe.net>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/377
Co-authored-by: haxwithaxe <haxwithaxe@noreply.gitea.com>
Co-committed-by: haxwithaxe <haxwithaxe@noreply.gitea.com>
2023-10-15 23:21:53 +00:00
4c35288175 Add DIND docker-compose example (#365)
Co-authored-by: Nikita Vilunov <nikita@vilunov.me>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/365
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: vilunov <vilunov@noreply.gitea.com>
Co-committed-by: vilunov <vilunov@noreply.gitea.com>
2023-10-02 15:12:31 +00:00
990db1bfc0 Fix k8s rootless Docker filesystem permissions (#366)
Without this the deployment fails because the runner can't write to
/data/

Credit to @ccureau for identifying the fix

Relates to: gitea/act_runner#264

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/366
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Andrew Imeson <andrew@andrewimeson.com>
Co-committed-by: Andrew Imeson <andrew@andrewimeson.com>
2023-10-02 15:12:14 +00:00
d07fbfc8c3 bump act library to v0.2.51 (#360)
brings in node20 support

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/360
Reviewed-by: delvh <dev.lh@web.de>
2023-09-24 19:20:40 +00:00
m90
10dc6fb60d Allow reading registration token from file (#350)
When deploying the runner in a Docker Swarm setup, it can be useful to
read the potentially sensitive token from a secret instead of having to
pass it from an environment variable.

Co-authored-by: Frederik Ring <frederik.ring@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/350
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: m90 <m90@noreply.gitea.com>
Co-committed-by: m90 <m90@noreply.gitea.com>
2023-09-04 04:12:07 +00:00
ed35b09b8f change podman socket path (#341)
port of https://github.com/nektos/act/pull/1961
closes gitea/act_runner#274

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/341
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2023-08-21 04:01:12 +00:00
03f0829d09 Add ForcePull option (#339)
Close #271

What it does: instead of forcing the value of `ForcePull` to false, the user can now configure it on the runner yaml

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/339
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Gianni Angelozzi <gianni@sistema3.it>
Co-committed-by: Gianni Angelozzi <gianni@sistema3.it>
2023-08-17 06:51:57 +00:00
7fc1b91ba6 Update to go1.21 in Dockerfile.rootless (#332)
Follow #330

Fix https://gitea.com/gitea/act_runner/pulls/330#issuecomment-747099

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/332
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2023-08-12 00:37:08 +00:00
82c3c2df1a Upgrade Go to 1.21 and bump other dependencies (#330)
Co-authored-by: harryzcy <harry@harryzheng.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/330
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: harryzcy <harryzcy@noreply.gitea.com>
Co-committed-by: harryzcy <harryzcy@noreply.gitea.com>
2023-08-10 01:45:25 +00:00
9fc823e4b1 Fix "http: invalid Host header" issue (#319)
This issue  is caused by the addition of validation logic for `Host` in `http.Request` in golang 1.20.6 (see https://go-review.googlesource.com/c/go/+/507357/6/src/net/http/request.go)

In `act`, when execute `ContainerExecAttach()`(see 22d91e3ac3/pkg/container/docker_run.go (L594)), the actual value of `request.Host` is `"/var/run/docker.sock"`. This does not conform to the specification described in `validhostHeader`.
 <details> <summary>ValidHostHeader()</summary>

![image](/attachments/57fb13ba-1c74-47f6-ac48-231a72a1947e)
</details>
So this PR follow upstream: https://github.com/nektos/act/pull/1917 and revert https://gitea.com/gitea/act_runner/pulls/295

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/319
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-08-02 04:34:36 +00:00
55 changed files with 1558 additions and 1092 deletions

View File

@ -2,42 +2,25 @@ name: release-nightly
on: on:
push: push:
branches: [ main ] branches: [main]
tags:
env: - "*"
GOPATH: /go_path
GOCACHE: /go_cache
jobs: jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.20.1' go-version-file: "go.mod"
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |
go.mod
go.sum
- name: cache go
id: cache-go
uses: https://github.com/actions/cache@v3
with:
path: |
/go_path
/go_cache
key: go_path-${{ steps.hash-go.outputs.hash }}
- name: goreleaser - name: goreleaser
uses: https://github.com/goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest args: release --nightly
args: release --nightly
env: env:
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }} AWS_REGION: ${{ secrets.AWS_REGION }}
@ -45,6 +28,8 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_REGION: ${{ secrets.AWS_REGION }} S3_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.AWS_BUCKET }} S3_BUCKET: ${{ secrets.AWS_BUCKET }}
GORELEASER_FORCE_TOKEN: "gitea"
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-image: release-image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
@ -54,18 +39,18 @@ jobs:
DOCKER_LATEST: nightly DOCKER_LATEST: nightly
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX - name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
@ -74,15 +59,14 @@ jobs:
id: meta id: meta
run: | run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
target: basic
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm64
@ -90,13 +74,25 @@ jobs:
tags: | tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }} ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
- name: Build and push dind-rootless - name: Build and push dind
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with: with:
context: . context: .
file: ./Dockerfile.rootless file: ./Dockerfile
target: dind
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
- name: Build and push dind-rootless
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
target: dind-rootless
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm64

View File

@ -3,49 +3,30 @@ name: release-tag
on: on:
push: push:
tags: tags:
- '*' - "*"
env:
GOPATH: /go_path
GOCACHE: /go_cache
jobs: jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.20.1' go-version-file: "go.mod"
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |
go.mod
go.sum
- name: cache go
id: cache-go
uses: https://github.com/actions/cache@v3
with:
path: |
/go_path
/go_cache
key: go_path-${{ steps.hash-go.outputs.hash }}
- name: Import GPG key - name: Import GPG key
id: import_gpg id: import_gpg
uses: https://github.com/crazy-max/ghaction-import-gpg@v5 uses: crazy-max/ghaction-import-gpg@v6
with: with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }} passphrase: ${{ secrets.PASSPHRASE }}
fingerprint: CC64B1DB67ABBEECAB24B6455FC346329753F4B0 fingerprint: CC64B1DB67ABBEECAB24B6455FC346329753F4B0
- name: goreleaser - name: goreleaser
uses: https://github.com/goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest args: release
args: release
env: env:
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }} AWS_REGION: ${{ secrets.AWS_REGION }}
@ -53,7 +34,7 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_REGION: ${{ secrets.AWS_REGION }} S3_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.AWS_BUCKET }} S3_BUCKET: ${{ secrets.AWS_BUCKET }}
GORELEASER_FORCE_TOKEN: 'gitea' GORELEASER_FORCE_TOKEN: "gitea"
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
release-image: release-image:
@ -65,18 +46,18 @@ jobs:
DOCKER_LATEST: latest DOCKER_LATEST: latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX - name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
@ -85,15 +66,14 @@ jobs:
id: meta id: meta
run: | run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
target: basic
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm64
@ -102,13 +82,26 @@ jobs:
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }} ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }} ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
- name: Build and push dind-rootless - name: Build and push dind
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with: with:
context: . context: .
file: ./Dockerfile.rootless file: ./Dockerfile
target: dind
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
- name: Build and push dind-rootless
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
target: dind-rootless
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm64

View File

@ -3,33 +3,15 @@ on:
- push - push
- pull_request - pull_request
env:
GOPATH: /go_path
GOCACHE: /go_cache
jobs: jobs:
lint: lint:
name: check and test name: check and test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: '>=1.20.1' go-version-file: 'go.mod'
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |
go.mod
go.sum
- name: cache go
id: cache-go
uses: https://github.com/actions/cache@v3
with:
path: |
/go_path
/go_cache
key: go_path-${{ steps.hash-go.outputs.hash }}
- name: vet checks - name: vet checks
run: make vet run: make vet
- name: build - name: build

7
.gitignore vendored
View File

@ -1,12 +1,17 @@
act_runner /act_runner
/loong_runner
.env .env
.runner .runner
coverage.txt coverage.txt
/gitea-vet /gitea-vet
/config.yaml /config.yaml
# Jetbrains
.idea
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin __debug_bin
# gorelease binary folder # gorelease binary folder
dist dist
# vim
*.sw*

View File

@ -1,3 +1,5 @@
version: 2
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
@ -14,6 +16,8 @@ builds:
- amd64 - amd64
- arm - arm
- arm64 - arm64
- s390x
- riscv64
goarm: goarm:
- "5" - "5"
- "6" - "6"
@ -58,7 +62,7 @@ builds:
flags: flags:
- -trimpath - -trimpath
ldflags: ldflags:
- -s -w -X gitea.com/gitea/act_runner/internal/pkg/ver.version={{ .Summary }} - -s -w -X git.whlug.cn/LAA/loong_runner/internal/pkg/ver.version={{ .Summary }}
binary: >- binary: >-
{{ .ProjectName }}- {{ .ProjectName }}-
{{- .Version }}- {{- .Version }}-
@ -81,7 +85,7 @@ blobs:
provider: s3 provider: s3
bucket: "{{ .Env.S3_BUCKET }}" bucket: "{{ .Env.S3_BUCKET }}"
region: "{{ .Env.S3_REGION }}" region: "{{ .Env.S3_REGION }}"
folder: "act_runner/{{.Version}}" directory: "act_runner/{{.Version}}"
extra_files: extra_files:
- glob: ./**.xz - glob: ./**.xz
- glob: ./**.sha256 - glob: ./**.sha256
@ -97,10 +101,10 @@ checksum:
- glob: ./**.xz - glob: ./**.xz
snapshot: snapshot:
name_template: "{{ .Branch }}-devel" version_template: "{{ .Branch }}-devel"
nightly: nightly:
name_template: "nightly" version_template: "nightly"
gitea_urls: gitea_urls:
api: https://gitea.com/api/v1 api: https://gitea.com/api/v1

View File

@ -1,16 +1,54 @@
FROM golang:1.20.5-alpine3.18 as builder FROM golang:1.24-alpine AS builder
# Do not remove `git` here, it is required for getting runner version when executing `make build` # Do not remove `git` here, it is required for getting runner version when executing `make build`
RUN apk add --no-cache make git RUN apk add --no-cache make git
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-}
COPY . /opt/src/act_runner COPY . /opt/src/act_runner
WORKDIR /opt/src/act_runner WORKDIR /opt/src/act_runner
RUN make clean && make build RUN make clean && make build
FROM alpine:3.18 FROM docker:dind AS dind
RUN apk add --no-cache git bash tini
RUN apk add --no-cache s6 bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /opt/act/run.sh COPY scripts/run.sh /usr/local/bin/run.sh
COPY scripts/s6 /etc/s6
ENTRYPOINT ["/sbin/tini","--","/opt/act/run.sh"] VOLUME /data
ENTRYPOINT ["s6-svscan","/etc/s6"]
FROM docker:dind-rootless AS dind-rootless
USER root
RUN apk add --no-cache s6 bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /usr/local/bin/run.sh
COPY scripts/s6 /etc/s6
VOLUME /data
RUN mkdir -p /data && chown -R rootless:rootless /etc/s6 /data
ENV DOCKER_HOST=unix:///run/user/1000/docker.sock
USER rootless
ENTRYPOINT ["s6-svscan","/etc/s6"]
FROM alpine AS basic
RUN apk add --no-cache tini bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /usr/local/bin/run.sh
VOLUME /var/run/docker.sock
VOLUME /data
ENTRYPOINT ["/sbin/tini","--","run.sh"]

View File

@ -1,24 +0,0 @@
FROM golang:1.20.5-alpine3.18 as builder
# Do not remove `git` here, it is required for getting runner version when executing `make build`
RUN apk add --no-cache make git
COPY . /opt/src/act_runner
WORKDIR /opt/src/act_runner
RUN make clean && make build
FROM docker:dind-rootless
USER root
RUN apk add --no-cache \
git bash supervisor
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY /scripts/supervisord.conf /etc/supervisord.conf
COPY /scripts/run.sh /opt/act/run.sh
COPY /scripts/rootless.sh /opt/act/rootless.sh
RUN mkdir /data \
&& chown rootless:rootless /data
USER rootless
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

View File

@ -1,5 +1,5 @@
DIST := dist DIST := dist
EXECUTABLE := act_runner EXECUTABLE := loong_runner
GOFMT ?= gofumpt -l GOFMT ?= gofumpt -l
DIST := dist DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release DIST_DIRS := $(DIST)/binaries $(DIST)/release
@ -7,7 +7,7 @@ GO ?= go
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
XGO_VERSION := go-1.18.x XGO_VERSION := go-1.24.x
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
LINUX_ARCHS ?= linux/amd64,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/arm64
@ -16,7 +16,7 @@ WINDOWS_ARCHS ?= windows/amd64
GO_FMT_FILES := $(shell find . -type f -name "*.go" ! -name "generated.*") GO_FMT_FILES := $(shell find . -type f -name "*.go" ! -name "generated.*")
GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "generated.*") GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "generated.*")
DOCKER_IMAGE ?= gitea/act_runner DOCKER_IMAGE ?= gitea/loong_runner
DOCKER_TAG ?= nightly DOCKER_TAG ?= nightly
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
@ -66,11 +66,11 @@ else
endif endif
endif endif
GO_PACKAGES_TO_VET ?= $(filter-out gitea.com/gitea/act_runner/internal/pkg/client/mocks,$(shell $(GO) list ./...)) GO_PACKAGES_TO_VET ?= $(filter-out git.whlug.cn/LAA/loong_runner/internal/pkg/client/mocks,$(shell $(GO) list ./...))
TAGS ?= TAGS ?=
LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=v$(RELASE_VERSION)" LDFLAGS ?= -X "git.whlug.cn/LAA/loong_runner/internal/pkg/ver.version=v$(RELASE_VERSION)"
all: build all: build
@ -86,7 +86,7 @@ go-check:
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' '))) $(eval MIN_GO_VERSION := $(shell printf "%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
$(eval GO_VERSION := $(shell printf "%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9]+' | tr '.' ' ');)) $(eval GO_VERSION := $(shell printf "%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9]+' | tr '.' ' ');))
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \ @if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
echo "Act Runner requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \ echo "Act Runner 需要 Go $(MIN_GO_VERSION_STR) 或更高版本才能构建。您可以从 https://go.dev/dl/ 获取。"; \
exit 1; \ exit 1; \
fi fi
@ -97,17 +97,17 @@ fmt-check:
fi fi
@diff=$$($(GOFMT) -d $(GO_FMT_FILES)); \ @diff=$$($(GOFMT) -d $(GO_FMT_FILES)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \ echo "请运行'make fmt'并提交结果"; \
echo "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
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:
@echo "Running go vet..." @echo "运行 go vet..."
@$(GO) build code.gitea.io/gitea-vet @$(GO) build code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES_TO_VET) @$(GO) vet -vettool=gitea-vet $(GO_PACKAGES_TO_VET)
@ -170,7 +170,6 @@ docker:
ARG_DISABLE_CONTENT_TRUST=--disable-content-trust=false; \ ARG_DISABLE_CONTENT_TRUST=--disable-content-trust=false; \
fi; \ fi; \
docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_REF) . docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_REF) .
docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_ROOTLESS_REF) -f Dockerfile.rootless .
clean: clean:
$(GO) clean -x -i ./... $(GO) clean -x -i ./...

View File

@ -1,106 +1,111 @@
# act runner # Loong Runner
Act runner is a runner for Gitea based on [Gitea fork](https://gitea.com/gitea/act) of [act](https://github.com/nektos/act). Loong Runner 是基于 [Gitea派生(fock)的act](https://gitea.com/gitea/act) [二次派生](https://git.whlug.cn/LoongArchActions/loong_runner) 主要适用于当前龙架构的运行时。
## Installation > 上游的Gitea派生是可以直接在龙架构上编译使用的, 此派生仓库主要是解决上游的Docker是基于乌班图系统制作的, 目前龙架构暂无乌班图系统
> * 当前计划使用`Debian`来代替乌班图用于构建容器镜像
### Prerequisites
Docker Engine Community version is required for docker mode. To install Docker CE, follow the official [install instructions](https://docs.docker.com/engine/install/). ## 安装
### Download pre-built binary ### 前提条件
Visit [here](https://dl.gitea.com/act_runner/) and download the right version for your platform. 在 Docker 模式下需要 `Docker Engine Community` 版本。要安装 `Docker CE`,请遵循官方 [安装说明](https://docs.docker.com/engine/install/)。
### Build from source ### 下载预构建的二进制文件
访问 [这里](https://git.whlug.cn/LoongArchActions/loong_runner) 仅提供龙架构版本。
### 从源码构建
```bash ```bash
make build make build
``` ```
### Build a docker image ### 构建 Docker 容器镜像
```bash ```bash
make docker make docker
``` ```
## Quickstart ## 快速入门
默认情况下,操作是禁用的,因此您需要在 Gitea 实例的配置文件中添加以下内容以启用它:
Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:
```ini ```ini
[actions] [actions]
ENABLED=true ENABLED=true
``` ```
### Register ### 注册
```bash ```bash
./act_runner register ./loong_runner register
``` ```
And you will be asked to input: 系统将提示您输入:
1. Gitea instance URL, like `http://192.168.8.8:3000/`. You should use your gitea instance ROOT_URL as the instance argument 1. Gitea 实例 URL,例如 `http://192.168.8.8:3000/`。您应该使用您的 Gitea 实例的 ROOT_URL 作为实例参数,并且不应使用 `localhost``127.0.0.1` 作为实例 IP;
and you should not use `localhost` or `127.0.0.1` as instance IP; 2. 运行器令牌,您可以从 `http://192.168.8.8:3000/admin/actions/runners` 获取;
2. Runner token, you can get it from `http://192.168.8.8:3000/admin/actions/runners`; 3. 运行器名称,您可以留空;
3. Runner name, you can just leave it blank; 4. 运行器标签,您可以留空。
4. Runner labels, you can just leave it blank.
The process looks like: 过程如下:
```text ```text
INFO Registering runner, arch=amd64, os=darwin, version=0.1.5. INFO 注册运行器,架构=loong64,操作系统=linux,版本=0.1.5
WARN Runner in user-mode. WARN 运行器处于用户模式。
INFO Enter the Gitea instance URL (for example, https://gitea.com/): INFO 输入 Gitea 实例 URL(例如,https://gitea.com/):
http://192.168.8.8:3000/ http://192.168.8.8:3000/
INFO Enter the runner token: INFO 输入运行器令牌:
fe884e8027dc292970d4e0303fe82b14xxxxxxxx fe884e8027dc292970d4e0303fe82b14xxxxxxxx
INFO Enter the runner name (if set empty, use hostname: Test.local): INFO 输入运行器名称(如果设置为空,则使用主机名:Test.local):
INFO Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host): INFO 输入运行器标签,留空以使用默认标签(逗号分隔,例如,debian-latest:docker://docker.gitea.com/runner-images:latest)
INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://node:16-bullseye ubuntu-22.04:docker://node:16-bullseye ubuntu-20.04:docker://node:16-bullseye ubuntu-18.04:docker://node:16-buster]. INFO 注册运行器,名称=Test.local,实例=http://192.168.8.8:3000/,标签=[debian-latest:docker://docker.gitea.com/runner-images:latest debian-sid:docker://docker.gitea.com/runner-images:debian-sid anolisos-latest:docker://lcr.loongnix.cn/library/anolisos:latest]。
DEBU Successfully pinged the Gitea instance server DEBU 成功 ping 到 Gitea 实例服务器
INFO Runner registered successfully. INFO 运行器注册成功。
``` ```
You can also register with command line arguments. 您也可以使用命令行参数进行注册。
```bash ```bash
./act_runner register --instance http://192.168.8.8:3000 --token <my_runner_token> --no-interactive ./loong_runner register --instance http://192.168.8.8:3000 --token <my_runner_token> --no-interactive
``` ```
If the registry succeed, it will run immediately. Next time, you could run the runner directly. 如果注册成功,它将立即运行。下次,您可以直接运行运行器。
### Run ### 运行
```bash ```bash
./act_runner daemon ./loong_runner daemon
``` ```
### Run with docker ### 使用 Docker 运行
```bash ```bash
docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/act_runner:nightly docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/loong_runner:nightly
``` ```
### Configuration ### 配置
You can also configure the runner with a configuration file. 您还可以使用配置文件配置运行器。
The configuration file is a YAML file, you can generate a sample configuration file with `./act_runner generate-config`. 配置文件是一个 YAML 文件,您可以使用 `./loong_runner generate-config` 生成一个示例配置文件。
```bash ```bash
./act_runner generate-config > config.yaml ./loong_runner generate-config > config.yaml
``` ```
You can specify the configuration file path with `-c`/`--config` argument. 您可以使用 `-c`/`--config` 参数指定配置文件路径。
```bash ```bash
./act_runner -c config.yaml register # register with config file ./loong_runner -c config.yaml register # 使用配置文件注册
./act_runner -c config.yaml daemon # run with config file ./loong_runner -c config.yaml daemon # 使用配置文件运行
``` ```
### Example Deployments 您可以在 [config.example.yaml](internal/pkg/config/config.example.yaml) 上在线查看配置文件的最新版本。
Check out the [examples](examples) directory for sample deployment types. ### 示例部署
查看 [examples](examples) 目录中的示例部署类型。

View File

@ -1,12 +1,12 @@
# Usage Examples for `act_runner` # `loong_runner` 使用示例
Welcome to our collection of usage and deployment examples specifically designed for Gitea setups. Whether you're a beginner or an experienced user, you'll find practical resources here that you can directly apply to enhance your Gitea experience. We encourage you to contribute your own insights and knowledge to make this collection even more comprehensive and valuable. 欢迎来到我们专为 Gitea 设置设计的使用和部署示例集合。无论您是初学者还是经验丰富的用户,您都会在这里找到可以直接应用以增强 Gitea 体验的实用资源。我们鼓励您贡献自己的见解和知识,使这个集合更加全面和有价值。
| Section | Description | | 部分 | 描述 |
|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----|------|
| [`docker`](docker) | This section provides you with scripts and instructions tailored for running containers on a workstation or server where Docker is installed. It simplifies the process of setting up and managing your Gitea deployment using Docker. | | [`docker`](docker) | 本部分为您提供适用于在工作站或安装了 Docker 的服务器上运行容器的脚本和说明。它简化了使用 Docker 设置和管理 Gitea 部署的过程。 |
| [`docker-compose`](docker-compose) | In this section, you'll discover examples demonstrating how to utilize docker-compose to efficiently handle your Gitea deployments. It offers a straightforward approach to managing multiple containerized components of your Gitea setup. | | [`docker-compose`](docker-compose) | 在本部分中,您将发现如何利用 `docker-compose` 来高效处理 Gitea 部署的示例。它提供了一个直接的方法来管理 Gitea 设置的多个容器化组件。 |
| [`kubernetes`](kubernetes) | If you're utilizing Kubernetes clusters for your infrastructure, this section is specifically designed for you. It presents examples and guidelines for configuring Gitea deployments within Kubernetes clusters, enabling you to leverage the scalability and flexibility of Kubernetes. | | [`kubernetes`](kubernetes) | 如果您正在使用 Kubernetes 集群作为基础设施,本部分专门为您设计。它展示了在 Kubernetes 集群内配置 Gitea 部署的示例和指南,使您能够利用 Kubernetes 的可扩展性和灵活性。 |
| [`vm`](vm) | This section is dedicated to examples that assist you in setting up Gitea on virtual or physical servers. Whether you're working with virtual machines or physical hardware, you'll find helpful resources to guide you through the deployment process. | | [`vm`](vm) | 本部分致力于协助您在虚拟或物理服务器上设置 Gitea 的示例。无论您是在使用虚拟机还是物理硬件,您都会找到有用的资源来指导您完成部署过程。 |
We hope these resources provide you with valuable insights and solutions for your Gitea setup. Feel free to explore, contribute, and adapt these examples to suit your specific requirements. 我们希望这些资源为您的 Gitea 设置提供有价值的见解和解决方案。请随意探索、贡献和调整这些示例以满足您的具体需求。

View File

@ -1,20 +1,59 @@
### Running `act_runner` using `docker-compose` ### 使用`docker-compose`运行`loong_runner`
```yml ```yml
... ...
gitea: gitea:
image: gitea/gitea image: gitea/gitea
... ...
healthcheck:
# 使用curl检查仓库前端是否可用
test: ["CMD", "curl", "-f", "<instance_url>"]
interval: 10s
retries: 3
start_period: 30s
timeout: 10s
environment:
# GITEA_RUNNER_REGISTRATION_TOKEN可用于设置全局运行器注册令牌。
# Gitea版本必须为v1.23或更高版本。
# 也可以使用 GITEA_RUNNER_REGISTRATION_TOKEN_FILE 来传递位置。
# - GITEA_RUNNER_REGISTRATION_TOKEN=<用户定义的注册令牌>
runner: runner:
image: gitea/act_runner image: gitea/loong_runner
restart: always restart: always
depends_on: depends_on:
- gitea gitea:
# 需要(下述配置),以便运行器能够连接到仓库,请参阅“健康检查(healthcheck)”
condition: service_healthy
restart: true
volumes: volumes:
- ./data/act_runner:/data - ./data/loong_runner:/data
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
- GITEA_INSTANCE_URL=<instance url> - GITEA_INSTANCE_URL=<instance url>
# 当使用Docker Secrets时
# 也可以使用 GITEA_RUNNER_REGISTRATION_TOKEN_FILE 来传递位置。
# 环境变量优先, 仅在首次启动时需要。
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration token>
```
### 使用 Docker-in-Docker (DIND) 运行 `loong_runner`
```yml
...
runner:
image: gitea/loong_runner:latest-dind-rootless
restart: always
privileged: true
depends_on:
- gitea
volumes:
- ./data/loong_runner:/data
environment:
- GITEA_INSTANCE_URL=<instance url>
- DOCKER_HOST=unix:///var/run/user/1000/docker.sock
# 使用Docker Secrets时
# 也可以使用 GITEA_RUNNER_REGISTRATION_TOKEN_FILE 来传递位置。
# 环境变量优先, 仅在首次启动时需要。
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration token> - GITEA_RUNNER_REGISTRATION_TOKEN=<registration token>
``` ```

View File

@ -1,8 +1,8 @@
### Run `act_runner` in a Docker Container ### 在Docker容器中运行`loong_runner`
```sh ```sh
docker run -e GITEA_INSTANCE_URL=http://192.168.8.18:3000 -e GITEA_RUNNER_REGISTRATION_TOKEN=<runner_token> -v /var/run/docker.sock:/var/run/docker.sock -v $PWD/data:/data --name my_runner gitea/act_runner:nightly docker run -e GITEA_INSTANCE_URL=http://192.168.8.18:3000 -e GITEA_RUNNER_REGISTRATION_TOKEN=<runner_token> -v /var/run/docker.sock:/var/run/docker.sock -v $PWD/data:/data --name my_runner gitea/loong_runner:nightly
``` ```
The `/data` directory inside the docker container contains the runner API keys after registration. docker 容器内的 `/data` 目录在注册后包含运行器 API 密钥。
It must be persisted, otherwise the runner would try to register again, using the same, now defunct registration token. 必须对其进行持久化存储,否则运行器将尝试再次注册,并使用相同的(现已失效的)注册令牌。

View File

@ -1,11 +1,19 @@
## Kubernetes Docker in Docker Deployment with `act_runner` ## Kubernetes 中使用 `act_runner` 部署 Docker-in-Docker(DinD)
NOTE: Docker in Docker (dind) requires elevated privileges on Kubernetes. The current way to achieve this is to set the pod `SecurityContext` to `privileged`. Keep in mind that this is a potential security issue that has the potential for a malicious application to break out of the container context. 注意:Docker-in-Docker(DinD)在 Kubernetes 中需要提升权限。目前的实现方式是将 Pod 的 `SecurityContext`(安全上下文)设置为 `privileged`(特权模式)。请注意这存在潜在安全风险,恶意应用可能突破容器隔离上下文。
Files in this directory: 本目录包含文件:
- [`dind-docker.yaml`](dind-docker.yaml) - [`dind-docker.yaml`](dind-docker.yaml)
How to create a Deployment and Persistent Volume for Kubernetes to act as a runner. The Docker credentials are re-generated each time the pod connects and does not need to be persisted. 用于在 Kubernetes 中创建作为运行器的 Deployment(部署)和 Persistent Volume(持久卷)。Docker 凭证会在每次 Pod 连接时重新生成,无需持久化存储。
- [`rootless-docker.yaml`](rootless-docker.yaml) - [`rootless-docker.yaml`](rootless-docker.yaml)
How to create a rootless Deployment and Persistent Volume for Kubernetes to act as a runner. The Docker credentials are re-generated each time the pod connects and does not need to be persisted. 用于在 Kubernetes 中创建 rootless(非特权)模式的 Deployment Persistent Volume 作为运行器。Docker 凭证会在每次 Pod 连接时重新生成,无需持久化存储。
关键术语说明:
1. privileged 模式:容器获得与宿主机 root 用户几乎相同的权限
2. SecurityContext:Kubernetes 中定义 Pod/容器权限和安全配置的对象
3. rootless 模式:以非特权用户身份运行 Docker 守护进程,安全性更高
4. act_runner:Gitea 的 CI/CD 运行器组件
安全建议:在生产环境中,建议优先考虑 rootless 模式,若必须使用 DinD,建议通过 Pod 安全策略、网络策略和审计日志加强安全监控。

View File

@ -12,6 +12,9 @@ spec:
--- ---
apiVersion: v1 apiVersion: v1
data: data:
# 注册令牌可从Web UI、API或命令行获取。
# 可以通过`GITEA_RUNNER_REGISTRATION_TOKEN`/`GITEA_RUNNER_REGISTRATION_TOKEN_FILE`环境变量
# 为Gitea实例设置预定义的全局运行器注册令牌。
token: << base64 encoded registration token >> token: << base64 encoded registration token >>
kind: Secret kind: Secret
metadata: metadata:
@ -46,7 +49,7 @@ spec:
containers: containers:
- name: runner - name: runner
image: gitea/act_runner:nightly image: gitea/act_runner:nightly
command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"] command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo '等待docker守护进程..'; sleep 5; done; /sbin/tini -- run.sh"]
env: env:
- name: DOCKER_HOST - name: DOCKER_HOST
value: tcp://localhost:2376 value: tcp://localhost:2376

View File

@ -12,7 +12,10 @@ spec:
--- ---
apiVersion: v1 apiVersion: v1
data: data:
token: << runner registration token goes here >> # 注册令牌可从Web UI、API或命令行获取。
# 可以通过`GITEA_RUNNER_REGISTRATION_TOKEN`/`GITEA_RUNNER_REGISTRATION_TOKEN_FILE`环境变量
# 为Gitea实例设置预定义的全局运行器注册令牌。
token: << base64 encoded registration token >>
kind: Secret kind: Secret
metadata: metadata:
name: runner-secret name: runner-secret
@ -41,11 +44,13 @@ spec:
- name: runner-data - name: runner-data
persistentVolumeClaim: persistentVolumeClaim:
claimName: act-runner-vol claimName: act-runner-vol
securityContext:
fsGroup: 1000
containers: containers:
- name: runner - name: runner
image: gitea/act_runner:nightly-dind-rootless image: gitea/act_runner:nightly-dind-rootless
imagePullPolicy: Always imagePullPolicy: Always
# command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"] # command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo '等待docker守护进程..'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"]
env: env:
- name: DOCKER_HOST - name: DOCKER_HOST
value: tcp://localhost:2376 value: tcp://localhost:2376

View File

@ -1,6 +1,6 @@
## `act_runner` on Virtual or Physical Servers # 在虚拟或物理服务器上使用 `loong_runner`
Files in this directory: 此目录中的文件:
- [`rootless-docker.md`](rootless-docker.md) - [`rootless-docker.md`](rootless-docker.md)
How to set up a rootless docker implementation of the runner. 在非Root用户下使用Docker运行`loong_runner`

View File

@ -1,65 +1,73 @@
## Using Rootless Docker with`act_runner` # 在非特权模式下使用Docker
Here is a simple example of how to set up `act_runner` with rootless Docker. It has been created with Debian, but other Linux should work the same way. 以下是如何在在非特权模式下使用 Docker 中设置 `loong_runner` 的简单示例。实例是基于 Debian 编写的,其他 Linux 发行版不会有太大区别。
Note: This procedure needs a real login shell -- using `sudo su` or other method of accessing the account will fail some of the steps below. 注意:此过程需要一个真实的登录 shell -- 使用 `sudo su` 或其他访问账户的方法将无法完成下面的一些步骤。
As `root`: 使用 `root` 用户:
- Create a user to run both `docker` and `act_runner`. In this example, we use a non-privileged account called `rootless`. - 创建一个用户来运行 `docker``loong_runner`。在这个例子中,我们使用了一个名为 `rootless` 的非特权账户。
```bash ```bash
useradd -m rootless useradd -m rootless
passwd rootless passwd rootless
apt-get install -y uidmap # 对 docker 非Root用户使用是必需的。
``` ```
- Install [`docker-ce`](https://docs.docker.com/engine/install/) - 安装 [`docker-ce`](https://docs.docker.com/engine/install/)
- (Recommended) Disable the system-wide Docker daemon - (推荐)禁用系统范围的 Docker 守护进程
``systemctl disable --now docker.service docker.socket`` ``systemctl disable --now docker.service docker.socket``
As the `rootless` user: 作为 `rootless` 用户:
- Follow the instructions for [enabling rootless mode](https://docs.docker.com/engine/security/rootless/) - 按照 [启用非特权模式](https://docs.docker.com/engine/security/rootless/) 的说明进行操作
- Add the following lines to the `/home/rootless/.bashrc`: - 将以下行添加到 `/home/rootless/.bashrc`
```bash ```bash
export XDG_RUNTIME_DIR=/home/rootless/.docker/run for f in ./.bashrc.d/*.bash; do echo "处理 $f 文件..."; . "$f"; done
export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///run/user/1001/docker.sock
``` ```
- Reboot. Ensure that the Docker process is working. - 创建 `.bashrc.d` 目录 `mkdir ~/.bashrc.d`
- Create a directory for saving `act_runner` data between restarts - 将以下行添加到 `/home/rootless/.bashrc.d/rootless-docker.bash`:
`mkdir /home/rootless/act_runner`
- Register the runner from the data directory
```bash ```bash
cd /home/rootless/act_runner export XDG_RUNTIME_DIR=/home/rootless/.docker/run
act_runner register export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
``` ```
- Generate a `act_runner` configuration file in the data directory. Edit the file to adjust for the system. - 重启。确保 Docker 进程正在工作。
- 为保存 `loong_runner` 数据创建一个目录
`mkdir /home/rootless/loong_runner`
- 从数据目录注册 runner
```bash ```bash
act_runner generate-config >/home/rootless/act_runner/config cd /home/rootless/loong_runner
loong_runner register
``` ```
- Create a new user-level`systemd` unit file as `/home/rootless/.config/systemd/user/act_runner.service` with the following contents: - 在数据目录中生成 `loong_runner` 配置文件。编辑文件以适应系统。
```bash ```bash
Description=Gitea Actions runner loong_runner generate-config >/home/rootless/loong_runner/config
Documentation=https://gitea.com/gitea/act_runner ```
- 创建一个新的用户级 `systemd` 单元文件 `/home/rootless/.config/systemd/user/loong_runner.service`,内容如下:
```bash
Description=龙架构代码仓库运行时
Documentation=https://git.whlug.cn/LoongArchActions/loong_runner
After=docker.service After=docker.service
[Service] [Service]
Environment=PATH=/home/rootless/bin:/sbin:/usr/sbin:/home/rootless/bin:/home/rootless/bin:/home/rootless/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games Environment=PATH=/home/rootless/bin:/sbin:/usr/sbin:/home/rootless/bin:/home/rootless/bin:/home/rootless/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Environment=DOCKER_HOST=unix:///run/user/1001/docker.sock Environment=DOCKER_HOST=unix:///run/user/1001/docker.sock
ExecStart=/usr/bin/act_runner daemon -c /home/rootless/act_runner/config ExecStart=/usr/bin/loong_runner daemon -c /home/rootless/loong_runner/config
ExecReload=/bin/kill -s HUP $MAINPID ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/home/rootless/act_runner WorkingDirectory=/home/rootless/loong_runner
TimeoutSec=0 TimeoutSec=0
RestartSec=2 RestartSec=2
Restart=always Restart=always
@ -78,10 +86,11 @@ As the `rootless` user:
WantedBy=default.target WantedBy=default.target
``` ```
- Reboot - 重启
After the system restarts, check that the`act_runner` is working and that the runner is connected to Gitea. 系统重启后,检查 `loong_runner` 是否正常工作,并且 运行时 是否已连接到 Gitea
````bash ````bash
systemctl --user status act_runner systemctl --user status loong_runner
journalctl --user -xeu act_runner journalctl --user -xeu loong_runner
```

117
go.mod
View File

@ -1,92 +1,101 @@
module gitea.com/gitea/act_runner module git.whlug.cn/LAA/loong_runner
go 1.20 go 1.24
require ( require (
code.gitea.io/actions-proto-go v0.3.1 code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 code.gitea.io/gitea-vet v0.2.3
github.com/avast/retry-go/v4 v4.3.1 connectrpc.com/connect v1.16.2
github.com/bufbuild/connect-go v1.3.1 github.com/avast/retry-go/v4 v4.6.0
github.com/docker/docker v23.0.6+incompatible github.com/docker/docker v25.0.5+incompatible
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-isatty v0.0.20
github.com/nektos/act v0.0.0 github.com/nektos/act v0.0.0 // will be replaced
github.com/sirupsen/logrus v1.9.2 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.9.0
golang.org/x/term v0.8.0 golang.org/x/term v0.22.0
golang.org/x/time v0.1.0 golang.org/x/time v0.5.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.4.0 gotest.tools/v3 v3.5.1
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.13 // indirect
github.com/containerd/containerd v1.6.20 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/creack/pty v1.1.18 // indirect github.com/creack/pty v1.1.21 // indirect
github.com/cyphar/filepath-securejoin v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v24.0.1+incompatible // indirect github.com/distribution/reference v0.5.0 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/cli v25.0.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.17.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.7.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/imdario/mergo v0.3.15 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.15.12 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/buildkit v0.11.6 // indirect github.com/moby/buildkit v0.12.5 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rhysd/actionlint v1.6.24 // indirect github.com/rhysd/actionlint v1.7.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.1.1 // indirect github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a // indirect github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.etcd.io/bbolt v1.3.7 // indirect go.etcd.io/bbolt v1.3.10 // indirect
golang.org/x/crypto v0.9.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
golang.org/x/net v0.10.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect
golang.org/x/sync v0.1.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect
golang.org/x/sys v0.8.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/tools v0.8.0 // indirect golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/tools v0.23.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
replace github.com/nektos/act => gitea.com/gitea/act v0.246.2-0.20230717034634-cdc6d4bc6a38 replace github.com/nektos/act => gitea.com/gitea/act v0.261.4

334
go.sum
View File

@ -1,91 +1,103 @@
code.gitea.io/actions-proto-go v0.3.1 h1:PMyiQtBKb8dNnpEO2R5rcZdXSis+UQZVo/SciMtR1aU= code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls=
code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 h1:daBEK2GQeqGikJESctP5Cu1i33z5ztAD4kyQWiw185M= code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
gitea.com/gitea/act v0.246.2-0.20230717034634-cdc6d4bc6a38 h1:whUEO/qPkYfpbL1he9TuIIzz2P4v6xEwb2lT6E/4F7A= connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
gitea.com/gitea/act v0.246.2-0.20230717034634-cdc6d4bc6a38/go.mod h1:oU/5klyP5O+J2psPS3t50t09+SNVg+fZ/jN4lDZAq1U= connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/hcsshim v0.9.8 h1:lf7xxK2+Ikbj9sVf2QZsouGjRjEp2STj1yDHgoVtU5k= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/avast/retry-go/v4 v4.3.1 h1:Mtg11F9PdAIMkMiio2RKcYauoVHjl2aB3zQJJlzD4cE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/avast/retry-go/v4 v4.3.1/go.mod h1:rg6XFaiuFYII0Xu3RDbZQkxCofFwruZKW8oEF1jpWiU= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
github.com/bufbuild/connect-go v1.3.1 h1:doJP6Q8Ypg6haUT2IAZJPWHUN9rAUp+F9MfK7yhu1zs= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
github.com/bufbuild/connect-go v1.3.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/containerd/containerd v1.6.20 h1:+itjwpdqXpzHB/QAiWc/BZCjjVfcNgw69w/oIeF4Oy0= github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/containerd/containerd v1.6.20/go.mod h1:apei1/i5Ux2FzrK6+DM/suEsGuK/MeVOfy8tR2q7Wnw= github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.3.0 h1:tXpmbiaeBrS/K2US8nhgwdKYnfAOnVfkcLPKFgFHeA0=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.3.0/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v24.0.1+incompatible h1:uVl5Xv/39kZJpDo9VaktTOYBc702sdYYF33FqwUG/dM= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/docker/cli v24.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v23.0.6+incompatible h1:aBD4np894vatVX99UTx/GyOUOK4uEcROwA3+bQhEcoU= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/docker v23.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -100,46 +112,42 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.11.6 h1:VYNdoKk5TVxN7k4RvZgdeM4GOyRvIi4Z8MXOY7xvyUs= github.com/moby/buildkit v0.12.5 h1:RNHH1l3HDhYyZafr5EgstEu8aGNCwyfvMtrQDtjH9T0=
github.com/moby/buildkit v0.11.6/go.mod h1:GCqKfHhz+pddzfgaR7WmHVEE3nKKZMMDPpK8mh3ZLv4= github.com/moby/buildkit v0.12.5/go.mod h1:YGwjA2loqyiYfZeEo8FtI7z4x5XponAaIWsWcSjWwso=
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
@ -148,35 +156,32 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rhysd/actionlint v1.6.24 h1:5f61cF5ssP2pzG0jws5bEsfZBNhbBcO9nl7vTzVKjzs= github.com/rhysd/actionlint v1.7.1 h1:WJaDzyT1StBWVKGSsZPYnbV0HF9Y9/vD6KFdZQL42qE=
github.com/rhysd/actionlint v1.6.24/go.mod h1:gQmz9r2wlcpLy+VdbzK0GINJQnAK5/sNH3BpwW4Mt5I= github.com/rhysd/actionlint v1.7.1/go.mod h1:lNjNNlZY0BdBl8l837Z9ZiBpu8v+5lzfoJQFdSk4xss=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -184,18 +189,15 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 h1:zjNCuOOhh1TKRU0Ru3PPPJt80z7eReswCao91gBLk00=
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0= github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928/go.mod h1:PCFYfAEfKT+Nd6zWvUpsXduMR1bXFLf0uGSlEF05MCI=
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
@ -204,112 +206,132 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@ -9,7 +9,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
"github.com/nektos/act/pkg/artifactcache" "github.com/nektos/act/pkg/artifactcache"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -17,16 +17,16 @@ import (
) )
type cacheServerArgs struct { type cacheServerArgs struct {
Dir string Dir string // 缓存目录
Host string Host string // 主机地址
Port uint16 Port uint16 // 端口号
} }
func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error { func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadDefault(*configFile) cfg, err := config.LoadDefault(*configFile)
if err != nil { if err != nil {
return fmt.Errorf("invalid configuration: %w", err) return fmt.Errorf("配置无效: %w", err)
} }
initLogging(cfg) initLogging(cfg)
@ -37,7 +37,7 @@ func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheSer
port = cfg.Cache.Port port = cfg.Cache.Port
) )
// cacheArgs has higher priority // cacheArgs 优先级更高
if cacheArgs.Dir != "" { if cacheArgs.Dir != "" {
dir = cacheArgs.Dir dir = cacheArgs.Dir
} }
@ -58,7 +58,7 @@ func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheSer
return err return err
} }
log.Infof("cache server is listening on %v", cacheHandler.ExternalURL()) log.Infof("缓存服务器正在监听 %v", cacheHandler.ExternalURL())
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)

View File

@ -10,44 +10,47 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/ver" "git.whlug.cn/LAA/loong_runner/internal/pkg/ver"
) )
func Execute(ctx context.Context) { func Execute(ctx context.Context) {
// ./act_runner // ./act_runner
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"", Use: "act_runner [运行事件名称]\n如果没有传递事件名称, 默认为 \"on: push\"",
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.", Short: "通过指定事件名称(例如 `push`)或直接指定操作名称来本地运行 GitHub Actions",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
Version: ver.Version(), Version: ver.Version(),
SilenceUsage: true, SilenceUsage: true,
} }
configFile := "" configFile := ""
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Config file path") rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "配置文件路径")
// ./act_runner register // ./act_runner register
var regArgs registerArgs var regArgs registerArgs
registerCmd := &cobra.Command{ registerCmd := &cobra.Command{
Use: "register", Use: "register",
Short: "Register a runner to the server", Short: "将运行器注册到服务器",
Args: cobra.MaximumNArgs(0), Args: cobra.MaximumNArgs(0),
RunE: runRegister(ctx, &regArgs, &configFile), // must use a pointer to regArgs RunE: runRegister(ctx, &regArgs, &configFile), // 必须使用 regArgs 的指针
} }
registerCmd.Flags().BoolVar(&regArgs.NoInteractive, "no-interactive", false, "Disable interactive mode") registerCmd.Flags().BoolVar(&regArgs.NoInteractive, "no-interactive", false, "禁用交互模式")
registerCmd.Flags().StringVar(&regArgs.InstanceAddr, "instance", "", "Gitea instance address") registerCmd.Flags().StringVar(&regArgs.InstanceAddr, "instance", "", "Gitea 实例地址")
registerCmd.Flags().StringVar(&regArgs.Token, "token", "", "Runner token") registerCmd.Flags().StringVar(&regArgs.Token, "token", "", "运行器令牌")
registerCmd.Flags().StringVar(&regArgs.RunnerName, "name", "", "Runner name") registerCmd.Flags().StringVar(&regArgs.RunnerName, "name", "", "运行器名称")
registerCmd.Flags().StringVar(&regArgs.Labels, "labels", "", "Runner tags, comma separated") registerCmd.Flags().StringVar(&regArgs.Labels, "labels", "", "运行器标签,逗号分隔")
registerCmd.Flags().BoolVar(&regArgs.Ephemeral, "ephemeral", false, "配置运行器为临时运行器,只能选择单个作业(比 --once 更严格)")
rootCmd.AddCommand(registerCmd) rootCmd.AddCommand(registerCmd)
// ./act_runner daemon // ./act_runner daemon
var daemArgs daemonArgs
daemonCmd := &cobra.Command{ daemonCmd := &cobra.Command{
Use: "daemon", Use: "daemon",
Short: "Run as a runner daemon", Short: "作为运行器守护进程运行",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(0),
RunE: runDaemon(ctx, &configFile), RunE: runDaemon(ctx, &daemArgs, &configFile),
} }
daemonCmd.Flags().BoolVar(&daemArgs.Once, "once", false, "运行一个作业然后退出")
rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(daemonCmd)
// ./act_runner exec // ./act_runner exec
@ -56,7 +59,7 @@ func Execute(ctx context.Context) {
// ./act_runner config // ./act_runner config
rootCmd.AddCommand(&cobra.Command{ rootCmd.AddCommand(&cobra.Command{
Use: "generate-config", Use: "generate-config",
Short: "Generate an example config file", Short: "生成示例配置文件",
Args: cobra.MaximumNArgs(0), Args: cobra.MaximumNArgs(0),
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
fmt.Printf("%s", config.Example) fmt.Printf("%s", config.Example)
@ -67,16 +70,16 @@ func Execute(ctx context.Context) {
var cacheArgs cacheServerArgs var cacheArgs cacheServerArgs
cacheCmd := &cobra.Command{ cacheCmd := &cobra.Command{
Use: "cache-server", Use: "cache-server",
Short: "Start a cache server for the cache action", Short: "启动缓存服务器用于缓存操作",
Args: cobra.MaximumNArgs(0), Args: cobra.MaximumNArgs(0),
RunE: runCacheServer(ctx, &configFile, &cacheArgs), RunE: runCacheServer(ctx, &configFile, &cacheArgs),
} }
cacheCmd.Flags().StringVarP(&cacheArgs.Dir, "dir", "d", "", "Cache directory") cacheCmd.Flags().StringVarP(&cacheArgs.Dir, "dir", "d", "", "缓存目录")
cacheCmd.Flags().StringVarP(&cacheArgs.Host, "host", "s", "", "Host of the cache server") cacheCmd.Flags().StringVarP(&cacheArgs.Host, "host", "s", "", "缓存服务器主机")
cacheCmd.Flags().Uint16VarP(&cacheArgs.Port, "port", "p", 0, "Port of the cache server") cacheCmd.Flags().Uint16VarP(&cacheArgs.Port, "port", "p", 0, "缓存服务器端口")
rootCmd.AddCommand(cacheCmd) rootCmd.AddCommand(cacheCmd)
// hide completion command // 隐藏自动补全命令
rootCmd.CompletionOptions.HiddenDefaultCmd = true rootCmd.CompletionOptions.HiddenDefaultCmd = true
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {

View File

@ -10,39 +10,40 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"slices"
"strconv" "strconv"
"strings" "strings"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.com/gitea/act_runner/internal/app/poll" "git.whlug.cn/LAA/loong_runner/internal/app/poll"
"gitea.com/gitea/act_runner/internal/app/run" "git.whlug.cn/LAA/loong_runner/internal/app/run"
"gitea.com/gitea/act_runner/internal/pkg/client" "git.whlug.cn/LAA/loong_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/envcheck" "git.whlug.cn/LAA/loong_runner/internal/pkg/envcheck"
"gitea.com/gitea/act_runner/internal/pkg/labels" "git.whlug.cn/LAA/loong_runner/internal/pkg/labels"
"gitea.com/gitea/act_runner/internal/pkg/ver" "git.whlug.cn/LAA/loong_runner/internal/pkg/ver"
) )
func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error { func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadDefault(*configFile) cfg, err := config.LoadDefault(*configFile)
if err != nil { if err != nil {
return fmt.Errorf("invalid configuration: %w", err) return fmt.Errorf("无效的配置: %w", err)
} }
initLogging(cfg) initLogging(cfg)
log.Infoln("Starting runner daemon") log.Infoln("启动运行器守护进程")
reg, err := config.LoadRegistration(cfg.Runner.File) reg, err := config.LoadRegistration(cfg.Runner.File)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Error("registration file not found, please register the runner first") log.Error("未找到注册文件,请先注册运行器")
return err return err
} else if err != nil { } else if err != nil {
return fmt.Errorf("failed to load registration file: %w", err) return fmt.Errorf("加载注册文件失败: %w", err)
} }
lbls := reg.Labels lbls := reg.Labels
@ -54,13 +55,13 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
for _, l := range lbls { for _, l := range lbls {
label, err := labels.Parse(l) label, err := labels.Parse(l)
if err != nil { if err != nil {
log.WithError(err).Warnf("ignored invalid label %q", l) log.WithError(err).Warnf("忽略无效标签 %q", l)
continue continue
} }
ls = append(ls, label) ls = append(ls, label)
} }
if len(ls) == 0 { if len(ls) == 0 {
log.Warn("no labels configured, runner may not be able to pick up jobs") log.Warn("未配置任何标签,运行器可能无法接取作业")
} }
if ls.RequireDocker() { if ls.RequireDocker() {
@ -71,15 +72,15 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
if err := envcheck.CheckIfDockerRunning(ctx, dockerSocketPath); err != nil { if err := envcheck.CheckIfDockerRunning(ctx, dockerSocketPath); err != nil {
return err return err
} }
// if dockerSocketPath passes the check, override DOCKER_HOST with dockerSocketPath // 如果 dockerSocketPath 通过检查,用 dockerSocketPath 覆盖 DOCKER_HOST
os.Setenv("DOCKER_HOST", dockerSocketPath) os.Setenv("DOCKER_HOST", dockerSocketPath)
// empty cfg.Container.DockerHost means act_runner need to find an available docker host automatically // 如果 cfg.Container.DockerHost 为空,意味着 act_runner 需要自动查找可用的 docker 主机
// and assign the path to cfg.Container.DockerHost // 并将路径分配给 cfg.Container.DockerHost
if cfg.Container.DockerHost == "" { if cfg.Container.DockerHost == "" {
cfg.Container.DockerHost = dockerSocketPath cfg.Container.DockerHost = dockerSocketPath
} }
// check the scheme, if the scheme is not npipe or unix // 检查方案,如果方案不是 npipe unix
// set cfg.Container.DockerHost to "-" because it can't be mounted to the job container // cfg.Container.DockerHost 设置为 "-",因为它不能挂载到作业容器
if protoIndex := strings.Index(cfg.Container.DockerHost, "://"); protoIndex != -1 { if protoIndex := strings.Index(cfg.Container.DockerHost, "://"); protoIndex != -1 {
scheme := cfg.Container.DockerHost[:protoIndex] scheme := cfg.Container.DockerHost[:protoIndex]
if !strings.EqualFold(scheme, "npipe") && !strings.EqualFold(scheme, "unix") { if !strings.EqualFold(scheme, "npipe") && !strings.EqualFold(scheme, "unix") {
@ -88,6 +89,14 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
} }
} }
if !slices.Equal(reg.Labels, ls.ToStrings()) {
reg.Labels = ls.ToStrings()
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
return fmt.Errorf("保存运行器配置失败: %w", err)
}
log.Infof("标签更新为: %v", reg.Labels)
}
cli := client.New( cli := client.New(
reg.Address, reg.Address,
cfg.Runner.Insecure, cfg.Runner.Insecure,
@ -97,33 +106,59 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
) )
runner := run.NewRunner(cfg, reg, cli) runner := run.NewRunner(cfg, reg, cli)
// declare the labels of the runner before fetching tasks
// 在获取任务之前声明运行器的标签
resp, err := runner.Declare(ctx, ls.Names()) resp, err := runner.Declare(ctx, ls.Names())
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented { if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
// Gitea instance is older version. skip declare step. log.Errorf("您的 Gitea 版本太旧,不支持运行器声明,请升级到 v1.21 或更高版本")
log.Warn("Because the Gitea instance is an old version, skip declare labels and version.") return err
} else if err != nil { } else if err != nil {
log.WithError(err).Error("fail to invoke Declare") log.WithError(err).Error("调用 Declare 失败")
return err return err
} else { } else {
log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully", log.Infof("运行器: %s, 版本: %s, 标签: %v, 声明成功",
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels) resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
// if declare successfully, override the labels in the.runner file with valid labels in the config file (if specified)
reg.Labels = ls.ToStrings()
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
return fmt.Errorf("failed to save runner config: %w", err)
}
} }
poller := poll.New(cfg, cli, runner) poller := poll.New(cfg, cli, runner)
poller.Poll(ctx) if daemArgs.Once || reg.Ephemeral {
done := make(chan struct{})
go func() {
defer close(done)
poller.PollOnce()
}()
// 当完成一个作业或请求取消时关闭
select {
case <-ctx.Done():
case <-done:
}
} else {
go poller.Poll()
<-ctx.Done()
}
log.Infof("运行器: %s 关闭开始,等待 %s 让正在运行的作业完成后再关闭", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout)
ctx, cancel := context.WithTimeout(context.Background(), cfg.Runner.ShutdownTimeout)
defer cancel()
err = poller.Shutdown(ctx)
if err != nil {
log.Warnf("运行器: %s 在关闭期间取消了正在进行的作业", resp.Msg.Runner.Name)
}
return nil return nil
} }
} }
// initLogging setup the global logrus logger. type daemonArgs struct {
Once bool // 是否只运行一次
}
// initLogging 设置全局 logrus 日志记录器。
func initLogging(cfg *config.Config) { func initLogging(cfg *config.Config) {
isTerm := isatty.IsTerminal(os.Stdout.Fd()) isTerm := isatty.IsTerminal(os.Stdout.Fd())
format := &log.TextFormatter{ format := &log.TextFormatter{
@ -136,17 +171,17 @@ func initLogging(cfg *config.Config) {
level, err := log.ParseLevel(l) level, err := log.ParseLevel(l)
if err != nil { if err != nil {
log.WithError(err). log.WithError(err).
Errorf("invalid log level: %q", l) Errorf("无效的日志级别: %q", l)
} }
// debug level // 调试级别
if level == log.DebugLevel { if level == log.DebugLevel {
log.SetReportCaller(true) log.SetReportCaller(true)
format.CallerPrettyfier = func(f *runtime.Frame) (string, string) { format.CallerPrettyfier = func(f *runtime.Frame) (string, string) {
// get function name // 获取函数名
s := strings.Split(f.Function, ".") s := strings.Split(f.Function, ".")
funcname := "[" + s[len(s)-1] + "]" funcname := "[" + s[len(s)-1] + "]"
// get file name and line number // 获取文件名和行号
_, filename := path.Split(f.File) _, filename := path.Split(f.File)
filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]" filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]"
return funcname, filename return funcname, filename
@ -155,7 +190,7 @@ func initLogging(cfg *config.Config) {
} }
if log.GetLevel() != level { if log.GetLevel() != level {
log.Infof("log level changed to %v", level) log.Infof("日志级别更改为 %v", level)
log.SetLevel(level) log.SetLevel(level)
} }
} }
@ -163,15 +198,16 @@ func initLogging(cfg *config.Config) {
var commonSocketPaths = []string{ var commonSocketPaths = []string{
"/var/run/docker.sock", "/var/run/docker.sock",
"/var/run/podman/podman.sock", "/run/podman/podman.sock",
"$HOME/.colima/docker.sock", "$HOME/.colima/docker.sock",
"$XDG_RUNTIME_DIR/docker.sock", "$XDG_RUNTIME_DIR/docker.sock",
"$XDG_RUNTIME_DIR/podman/podman.sock",
`\\.\pipe\docker_engine`, `\\.\pipe\docker_engine`,
"$HOME/.docker/run/docker.sock", "$HOME/.docker/run/docker.sock",
} }
func getDockerSocketPath(configDockerHost string) (string, error) { func getDockerSocketPath(configDockerHost string) (string, error) {
// a `-` means don't mount the docker socket to job containers // `-` 表示不要将 docker 套接字挂载到作业容器
if configDockerHost != "" && configDockerHost != "-" { if configDockerHost != "" && configDockerHost != "-" {
return configDockerHost, nil return configDockerHost, nil
} }
@ -190,5 +226,5 @@ func getDockerSocketPath(configDockerHost string) (string, error) {
} }
} }
return "", fmt.Errorf("daemon Docker Engine socket not found and docker_host config was invalid") return "", fmt.Errorf("守护进程 Docker 引擎套接字未找到且 docker_host 配置无效")
} }

View File

@ -26,47 +26,47 @@ import (
) )
type executeArgs struct { type executeArgs struct {
runList bool runList bool // 是否列出工作流
job string job string // 特定作业 ID
event string event string // 事件名称
workdir string workdir string // 工作目录
workflowsPath string workflowsPath string // 工作流文件路径
noWorkflowRecurse bool noWorkflowRecurse bool // 是否禁止递归运行子目录中的工作流
autodetectEvent bool autodetectEvent bool // 自动检测事件
forcePull bool forcePull bool // 即使存在也拉取 Docker 镜像
forceRebuild bool forceRebuild bool // 即使存在也重建本地动作 Docker 镜像
jsonLogger bool jsonLogger bool // 以 JSON 格式输出日志
envs []string envs []string // 环境变量
envfile string envfile string // 环境文件
secrets []string secrets []string // 机密
defaultActionsURL string defaultActionsURL string // 默认动作 URL
insecureSecrets bool insecureSecrets bool // 不建议!打印日志时不隐藏机密
privileged bool privileged bool // 使用特权模式
usernsMode string usernsMode string // 用户命名空间
containerArchitecture string containerArchitecture string // 容器架构
containerDaemonSocket string containerDaemonSocket string // 容器守护进程套接字
useGitIgnore bool useGitIgnore bool // 控制是否将 .gitignore 中指定的路径复制到容器中
containerCapAdd []string containerCapAdd []string // 添加到工作流容器的内核能力
containerCapDrop []string containerCapDrop []string // 从工作流容器中删除的内核能力
containerOptions string containerOptions string // 容器选项
artifactServerPath string artifactServerPath string // 存储上传和检索下载的路径
artifactServerAddr string artifactServerAddr string // 监听地址
artifactServerPort string artifactServerPort string // 监听端口
noSkipCheckout bool noSkipCheckout bool // 不跳过 actions/checkout
debug bool debug bool // 启用调试日志
dryrun bool dryrun bool // dryrun 模式
image string image string // 使用的 Docker 镜像
cacheHandler *artifactcache.Handler cacheHandler *artifactcache.Handler // 缓存处理器
network string network string // 容器连接的网络
githubInstance string githubInstance string // 使用的 Gitea 实例
} }
// WorkflowsPath returns path to workflow file(s) // WorkflowsPath 返回工作流文件的路径
func (i *executeArgs) WorkflowsPath() string { func (i *executeArgs) WorkflowsPath() string {
return i.resolve(i.workflowsPath) return i.resolve(i.workflowsPath)
} }
// Envfile returns path to .env // Envfile 返回 .env 文件的路径
func (i *executeArgs) Envfile() string { func (i *executeArgs) Envfile() string {
return i.resolve(i.envfile) return i.resolve(i.envfile)
} }
@ -77,18 +77,18 @@ func (i *executeArgs) LoadSecrets() map[string]string {
secretPairParts := strings.SplitN(secretPair, "=", 2) secretPairParts := strings.SplitN(secretPair, "=", 2)
secretPairParts[0] = strings.ToUpper(secretPairParts[0]) secretPairParts[0] = strings.ToUpper(secretPairParts[0])
if strings.ToUpper(s[secretPairParts[0]]) == secretPairParts[0] { if strings.ToUpper(s[secretPairParts[0]]) == secretPairParts[0] {
log.Errorf("Secret %s is already defined (secrets are case insensitive)", secretPairParts[0]) log.Errorf("机密 %s 已经定义(机密不区分大小写)", secretPairParts[0])
} }
if len(secretPairParts) == 2 { if len(secretPairParts) == 2 {
s[secretPairParts[0]] = secretPairParts[1] s[secretPairParts[0]] = secretPairParts[1]
} else if env, ok := os.LookupEnv(secretPairParts[0]); ok && env != "" { } else if env, ok := os.LookupEnv(secretPairParts[0]); ok && env != "" {
s[secretPairParts[0]] = env s[secretPairParts[0]] = env
} else { } else {
fmt.Printf("Provide value for '%s': ", secretPairParts[0]) fmt.Printf(" '%s' 提供值: ", secretPairParts[0])
val, err := term.ReadPassword(int(os.Stdin.Fd())) val, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println() fmt.Println()
if err != nil { if err != nil {
log.Errorf("failed to read input: %v", err) log.Errorf("读取输入失败: %v", err)
os.Exit(1) os.Exit(1)
} }
s[secretPairParts[0]] = string(val) s[secretPairParts[0]] = string(val)
@ -101,7 +101,7 @@ func readEnvs(path string, envs map[string]string) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
env, err := godotenv.Read(path) env, err := godotenv.Read(path)
if err != nil { if err != nil {
log.Fatalf("Error loading from %s: %v", path, err) log.Fatalf("从 %s 加载失败: %v", path, err)
} }
for k, v := range env { for k, v := range env {
envs[k] = v envs[k] = v
@ -130,7 +130,7 @@ func (i *executeArgs) LoadEnvs() map[string]string {
return envs return envs
} }
// Workdir returns path to workdir // Workdir 返回工作目录的路径
func (i *executeArgs) Workdir() string { func (i *executeArgs) Workdir() string {
return i.resolve(".") return i.resolve(".")
} }
@ -151,22 +151,22 @@ func (i *executeArgs) resolve(path string) string {
func printList(plan *model.Plan) error { func printList(plan *model.Plan) error {
type lineInfoDef struct { type lineInfoDef struct {
jobID string jobID string // 作业 ID
jobName string jobName string // 作业名称
stage string stage string // 阶段
wfName string wfName string // 工作流名称
wfFile string wfFile string // 工作流文件
events string events string // 事件
} }
lineInfos := []lineInfoDef{} lineInfos := []lineInfoDef{}
header := lineInfoDef{ header := lineInfoDef{
jobID: "Job ID", jobID: "作业 ID",
jobName: "Job name", jobName: "作业名称",
stage: "Stage", stage: "阶段",
wfName: "Workflow name", wfName: "工作流名称",
wfFile: "Workflow file", wfFile: "工作流文件",
events: "Events", events: "事件",
} }
jobs := map[string]bool{} jobs := map[string]bool{}
@ -221,7 +221,6 @@ func printList(plan *model.Plan) error {
jobNameMaxWidth += 2 jobNameMaxWidth += 2
stageMaxWidth += 2 stageMaxWidth += 2
wfNameMaxWidth += 2 wfNameMaxWidth += 2
wfFileMaxWidth += 2
fmt.Printf("%*s%*s%*s%*s%*s%*s\n", fmt.Printf("%*s%*s%*s%*s%*s%*s\n",
-stageMaxWidth, header.stage, -stageMaxWidth, header.stage,
@ -242,47 +241,47 @@ func printList(plan *model.Plan) error {
) )
} }
if duplicateJobIDs { if duplicateJobIDs {
fmt.Print("\nDetected multiple jobs with the same job name, use `-W` to specify the path to the specific workflow.\n") fmt.Print("\n检测到多个作业具有相同的作业名称,请使用 `-W` 指定特定工作流的路径。\n")
} }
return nil return nil
} }
func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *executeArgs) error { func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *executeArgs) error {
// plan with filtered jobs - to be used for filtering only // 计划带有过滤的作业 - 仅用于过滤
var filterPlan *model.Plan var filterPlan *model.Plan
// Determine the event name to be filtered // 确定要过滤的事件名称
var filterEventName string var filterEventName string
if len(execArgs.event) > 0 { if len(execArgs.event) > 0 {
log.Infof("Using chosed event for filtering: %s", execArgs.event) log.Infof("使用选择的事件进行过滤: %s", execArgs.event)
filterEventName = execArgs.event filterEventName = execArgs.event
} else if execArgs.autodetectEvent { } else if execArgs.autodetectEvent {
// collect all events from loaded workflows // 收集所有加载的工作流中的事件
events := planner.GetEvents() events := planner.GetEvents()
// set default event type to first event from many available // 将默认事件类型设置为第一个可用的事件
// this way user dont have to specify the event. // 这样用户就不必指定事件。
log.Infof("Using first detected workflow event for filtering: %s", events[0]) log.Infof("使用检测到的第一个工作流事件进行过滤: %s", events[0])
filterEventName = events[0] filterEventName = events[0]
} }
var err error var err error
if execArgs.job != "" { if execArgs.job != "" {
log.Infof("Preparing plan with a job: %s", execArgs.job) log.Infof("准备带有作业的计划: %s", execArgs.job)
filterPlan, err = planner.PlanJob(execArgs.job) filterPlan, err = planner.PlanJob(execArgs.job)
if err != nil { if err != nil {
return err return err
} }
} else if filterEventName != "" { } else if filterEventName != "" {
log.Infof("Preparing plan for a event: %s", filterEventName) log.Infof("准备事件的计划: %s", filterEventName)
filterPlan, err = planner.PlanEvent(filterEventName) filterPlan, err = planner.PlanEvent(filterEventName)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
log.Infof("Preparing plan with all jobs") log.Infof("准备所有作业的计划")
filterPlan, err = planner.PlanAll() filterPlan, err = planner.PlanAll()
if err != nil { if err != nil {
return err return err
@ -305,40 +304,40 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
return runExecList(ctx, planner, execArgs) return runExecList(ctx, planner, execArgs)
} }
// plan with triggered jobs // 计划触发作业
var plan *model.Plan var plan *model.Plan
// Determine the event name to be triggered // 确定要触发的事件名称
var eventName string var eventName string
// collect all events from loaded workflows // 收集所有加载的工作流中的事件
events := planner.GetEvents() events := planner.GetEvents()
if len(execArgs.event) > 0 { if len(execArgs.event) > 0 {
log.Infof("Using chosed event for filtering: %s", execArgs.event) log.Infof("使用选择的事件进行过滤: %s", execArgs.event)
eventName = execArgs.event eventName = execArgs.event
} else if len(events) == 1 && len(events[0]) > 0 { } else if len(events) == 1 && len(events[0]) > 0 {
log.Infof("Using the only detected workflow event: %s", events[0]) log.Infof("使用唯一检测到的工作流事件: %s", events[0])
eventName = events[0] eventName = events[0]
} else if execArgs.autodetectEvent && len(events) > 0 && len(events[0]) > 0 { } else if execArgs.autodetectEvent && len(events) > 0 && len(events[0]) > 0 {
// set default event type to first event from many available // 将默认事件类型设置为第一个可用的事件
// this way user dont have to specify the event. // 这样用户就不必指定事件。
log.Infof("Using first detected workflow event: %s", events[0]) log.Infof("使用检测到的第一个工作流事件: %s", events[0])
eventName = events[0] eventName = events[0]
} else { } else {
log.Infof("Using default workflow event: push") log.Infof("使用默认工作流事件: push")
eventName = "push" eventName = "push"
} }
// build the plan for this run // 为此运行构建计划
if execArgs.job != "" { if execArgs.job != "" {
log.Infof("Planning job: %s", execArgs.job) log.Infof("规划作业: %s", execArgs.job)
plan, err = planner.PlanJob(execArgs.job) plan, err = planner.PlanJob(execArgs.job)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
log.Infof("Planning jobs for event: %s", eventName) log.Infof("规划事件的作业: %s", eventName)
plan, err = planner.PlanEvent(eventName) plan, err = planner.PlanEvent(eventName)
if err != nil { if err != nil {
return err return err
@ -350,18 +349,18 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
maxLifetime = time.Until(deadline) maxLifetime = time.Until(deadline)
} }
// init a cache server // 初始化缓存服务器
handler, err := artifactcache.StartHandler("", "", 0, log.StandardLogger().WithField("module", "cache_request")) handler, err := artifactcache.StartHandler("", "", 0, log.StandardLogger().WithField("module", "cache_request"))
if err != nil { if err != nil {
return err return err
} }
log.Infof("cache handler listens on: %v", handler.ExternalURL()) log.Infof("缓存处理器监听于: %v", handler.ExternalURL())
execArgs.cacheHandler = handler execArgs.cacheHandler = handler
if len(execArgs.artifactServerAddr) == 0 { if len(execArgs.artifactServerAddr) == 0 {
ip := common.GetOutboundIP() ip := common.GetOutboundIP()
if ip == nil { if ip == nil {
return fmt.Errorf("unable to determine outbound IP address") return fmt.Errorf("无法确定出站 IP 地址")
} }
execArgs.artifactServerAddr = ip.String() execArgs.artifactServerAddr = ip.String()
} }
@ -376,7 +375,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
execArgs.artifactServerPath = tempDir execArgs.artifactServerPath = tempDir
} }
// run the plan // 运行计划
config := &runner.Config{ config := &runner.Config{
Workdir: execArgs.Workdir(), Workdir: execArgs.Workdir(),
BindWorkdir: false, BindWorkdir: false,
@ -411,7 +410,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
PlatformPicker: func(_ []string) string { PlatformPicker: func(_ []string) string {
return execArgs.image return execArgs.image
}, },
ValidVolumes: []string{"**"}, // All volumes are allowed for `exec` command ValidVolumes: []string{"**"}, // 所有挂载的卷(volumes)都允许被 exec 命令访问
} }
config.Env["ACT_EXEC"] = "true" config.Env["ACT_EXEC"] = "true"
@ -433,7 +432,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
} }
artifactCancel := artifacts.Serve(ctx, execArgs.artifactServerPath, execArgs.artifactServerAddr, execArgs.artifactServerPort) artifactCancel := artifacts.Serve(ctx, execArgs.artifactServerPath, execArgs.artifactServerAddr, execArgs.artifactServerPort)
log.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort) log.Debugf("artifacts 服务器启动于 %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort)
ctx = common.WithDryrun(ctx, execArgs.dryrun) ctx = common.WithDryrun(ctx, execArgs.dryrun)
executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error { executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error {
@ -450,43 +449,43 @@ func loadExecCmd(ctx context.Context) *cobra.Command {
execCmd := &cobra.Command{ execCmd := &cobra.Command{
Use: "exec", Use: "exec",
Short: "Run workflow locally.", Short: "本地运行工作流",
Args: cobra.MaximumNArgs(20), Args: cobra.MaximumNArgs(20),
RunE: runExec(ctx, &execArg), RunE: runExec(ctx, &execArg),
} }
execCmd.Flags().BoolVarP(&execArg.runList, "list", "l", false, "list workflows") execCmd.Flags().BoolVarP(&execArg.runList, "list", "l", false, "本地运行工作流")
execCmd.Flags().StringVarP(&execArg.job, "job", "j", "", "run a specific job ID") execCmd.Flags().StringVarP(&execArg.job, "job", "j", "", "运行特定作业 ID")
execCmd.Flags().StringVarP(&execArg.event, "event", "E", "", "run a event name") execCmd.Flags().StringVarP(&execArg.event, "event", "E", "", "运行事件名称")
execCmd.PersistentFlags().StringVarP(&execArg.workflowsPath, "workflows", "W", "./.gitea/workflows/", "path to workflow file(s)") execCmd.PersistentFlags().StringVarP(&execArg.workflowsPath, "workflows", "W", "./.gitea/workflows/", "工作流文件路径")
execCmd.PersistentFlags().StringVarP(&execArg.workdir, "directory", "C", ".", "working directory") execCmd.PersistentFlags().StringVarP(&execArg.workdir, "directory", "C", ".", "工作目录")
execCmd.PersistentFlags().BoolVarP(&execArg.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag") execCmd.PersistentFlags().BoolVarP(&execArg.noWorkflowRecurse, "no-recurse", "", false, "禁用运行指定路径的子目录中的工作流")
execCmd.Flags().BoolVarP(&execArg.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow") execCmd.Flags().BoolVarP(&execArg.autodetectEvent, "detect-event", "", false, "使用工作流中的第一个事件类型作为触发工作流的事件")
execCmd.Flags().BoolVarP(&execArg.forcePull, "pull", "p", false, "pull docker image(s) even if already present") execCmd.Flags().BoolVarP(&execArg.forcePull, "pull", "p", false, "即使已经存在也拉取 Docker 镜像")
execCmd.Flags().BoolVarP(&execArg.forceRebuild, "rebuild", "", false, "rebuild local action docker image(s) even if already present") execCmd.Flags().BoolVarP(&execArg.forceRebuild, "rebuild", "", false, "即使已经存在也重建本地动作 Docker 镜像")
execCmd.PersistentFlags().BoolVar(&execArg.jsonLogger, "json", false, "Output logs in json format") execCmd.PersistentFlags().BoolVar(&execArg.jsonLogger, "json", false, "以 JSON 格式输出日志")
execCmd.Flags().StringArrayVarP(&execArg.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)") execCmd.Flags().StringArrayVarP(&execArg.envs, "env", "", []string{}, "使环境变量对动作可用,可选值(例如 --env myenv=foo --env myenv)")
execCmd.PersistentFlags().StringVarP(&execArg.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers") execCmd.PersistentFlags().StringVarP(&execArg.envfile, "env-file", "", ".env", "读取并用作容器中的环境的环境文件")
execCmd.Flags().StringArrayVarP(&execArg.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)") execCmd.Flags().StringArrayVarP(&execArg.secrets, "secret", "s", []string{}, "为 Action 提供密钥,可带可选值(例如 -s mysecret=foo -s mysecret)")
execCmd.PersistentFlags().BoolVarP(&execArg.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.") execCmd.PersistentFlags().BoolVarP(&execArg.insecureSecrets, "insecure-secrets", "", false, "不推荐!打印日志时不会隐藏密钥信息")
execCmd.Flags().BoolVar(&execArg.privileged, "privileged", false, "use privileged mode") execCmd.Flags().BoolVar(&execArg.privileged, "privileged", false, "使用特权模式")
execCmd.Flags().StringVar(&execArg.usernsMode, "userns", "", "user namespace to use") execCmd.Flags().StringVar(&execArg.usernsMode, "userns", "", "要使用的用户命名空间")
execCmd.PersistentFlags().StringVarP(&execArg.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.") execCmd.PersistentFlags().StringVarP(&execArg.containerArchitecture, "container-architecture", "", "", "运行容器使用的架构(如 linux/loong64)。未指定时使用宿主机默认架构。需要 Docker 服务端 API 版本 1.41+,更低版本的 Docker 平台会忽略此参数")
execCmd.PersistentFlags().StringVarP(&execArg.containerDaemonSocket, "container-daemon-socket", "", "/var/run/docker.sock", "Path to Docker daemon socket which will be mounted to containers") execCmd.PersistentFlags().StringVarP(&execArg.containerDaemonSocket, "container-daemon-socket", "", "/var/run/docker.sock", "挂载到容器的 Docker 守护进程 socket 路径")
execCmd.Flags().BoolVar(&execArg.useGitIgnore, "use-gitignore", true, "Controls whether paths specified in .gitignore should be copied into container") execCmd.Flags().BoolVar(&execArg.useGitIgnore, "use-gitignore", true, "控制是否将 .gitignore 中指定的路径复制到容器中")
execCmd.Flags().StringArrayVarP(&execArg.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)") execCmd.Flags().StringArrayVarP(&execArg.containerCapAdd, "container-cap-add", "", []string{}, "为工作流容器添加的内核能力(例如 --container-cap-add SYS_PTRACE)")
execCmd.Flags().StringArrayVarP(&execArg.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)") execCmd.Flags().StringArrayVarP(&execArg.containerCapDrop, "container-cap-drop", "", []string{}, "从工作流容器移除的内核能力(例如 --container-cap-drop SYS_PTRACE)")
execCmd.Flags().StringVarP(&execArg.containerOptions, "container-opts", "", "", "container options") execCmd.Flags().StringVarP(&execArg.containerOptions, "container-opts", "", "", "容器选项")
execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPath, "artifact-server-path", "", ".", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPath, "artifact-server-path", "", ".", "定义构建物服务器存储上传和下载的路径。未指定时构建物服务器不会启动")
execCmd.PersistentFlags().StringVarP(&execArg.artifactServerAddr, "artifact-server-addr", "", "", "Defines the address where the artifact server listens") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerAddr, "artifact-server-addr", "", "", "定义构建物服务器的监听地址")
execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens (will only bind to localhost).") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPort, "artifact-server-port", "", "34567", "定义构建物服务器的监听端口(例如 --container-cap-drop SYS_PTRACE)")
execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsURL, "default-actions-url", "", "https://github.com", "Defines the default url of action instance.") execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsURL, "default-actions-url", "", "https://github.com", "定义 Action 实例的默认 URL")
execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout") execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "不跳过 actions/checkout")
execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "enable debug log") execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "启用调试日志")
execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun mode") execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun 模式")
execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "node:16-bullseye", "docker image to use") execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "lcr.loongnix.cn/library/debian:latest", "使用的 Docker 镜像。使用 \"-self-hosted\" 直接在主机上运行")
execCmd.PersistentFlags().StringVarP(&execArg.network, "network", "", "", "Specify the network to which the container will connect") execCmd.PersistentFlags().StringVarP(&execArg.network, "network", "", "", "容器连接的网络")
execCmd.PersistentFlags().StringVarP(&execArg.githubInstance, "gitea-instance", "", "", "Gitea instance to use.") execCmd.PersistentFlags().StringVarP(&execArg.githubInstance, "gitea-instance", "", "", "使用的 Gitea 实例")
return execCmd return execCmd
} }

View File

@ -15,18 +15,18 @@ import (
pingv1 "code.gitea.io/actions-proto-go/ping/v1" pingv1 "code.gitea.io/actions-proto-go/ping/v1"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.com/gitea/act_runner/internal/pkg/client" "git.whlug.cn/LAA/loong_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/labels" "git.whlug.cn/LAA/loong_runner/internal/pkg/labels"
"gitea.com/gitea/act_runner/internal/pkg/ver" "git.whlug.cn/LAA/loong_runner/internal/pkg/ver"
) )
// runRegister registers a runner to the server // runRegister 将运行器注册到服务器
func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) func(*cobra.Command, []string) error { func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
log.SetReportCaller(false) log.SetReportCaller(false)
@ -37,13 +37,13 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string)
}) })
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.Infof("Registering runner, arch=%s, os=%s, version=%s.", log.Infof("注册运行器,架构=%s,操作系统=%s,版本=%s",
goruntime.GOARCH, goruntime.GOOS, ver.Version()) goruntime.GOARCH, goruntime.GOOS, ver.Version())
// runner always needs root permission // 运行器始终需要 root 权限
if os.Getuid() != 0 { if os.Getuid() != 0 {
// TODO: use a better way to check root permission // TODO: 使用更好的方法检查 root 权限
log.Warnf("Runner in user-mode.") log.Warnf("运行器处于用户模式。")
} }
if regArgs.NoInteractive { if regArgs.NoInteractive {
@ -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
} }
@ -68,13 +68,14 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string)
} }
} }
// registerArgs represents the arguments for register command // registerArgs 代表 register 命令的参数
type registerArgs struct { type registerArgs struct {
NoInteractive bool NoInteractive bool // 是否非交互模式
InstanceAddr string InstanceAddr string // 实例地址
Token string Token string // 令牌
RunnerName string RunnerName string // 运行器名称
Labels string Labels string // 标签
Ephemeral bool // 是否临时
} }
type registerStage int8 type registerStage int8
@ -91,10 +92,9 @@ const (
) )
var defaultLabels = []string{ var defaultLabels = []string{
"ubuntu-latest:docker://node:16-bullseye", "debian-latest:docker://lcr.loongnix.cn/library/debian:latest",
"ubuntu-22.04:docker://node:16-bullseye", // There's no node:16-bookworm yet "anolisos-latest:docker://lcr.loongnix.cn/library/anolisos:latest",
"ubuntu-20.04:docker://node:16-bullseye", "anolisos-23.2:docker://lcr.loongnix.cn/library/anolisos:23.2",
"ubuntu-18.04:docker://node:16-buster",
} }
type registerInputs struct { type registerInputs struct {
@ -102,14 +102,15 @@ type registerInputs struct {
Token string Token string
RunnerName string RunnerName string
Labels []string Labels []string
Ephemeral bool
} }
func (r *registerInputs) validate() error { func (r *registerInputs) validate() error {
if r.InstanceAddr == "" { if r.InstanceAddr == "" {
return fmt.Errorf("instance address is empty") return fmt.Errorf("实例地址为空")
} }
if r.Token == "" { if r.Token == "" {
return fmt.Errorf("token is empty") return fmt.Errorf("令牌为空")
} }
if len(r.Labels) > 0 { if len(r.Labels) > 0 {
return validateLabels(r.Labels) return validateLabels(r.Labels)
@ -126,16 +127,32 @@ 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 {
// must set instance address and token. // 必须设置实例地址和令牌。
// if empty, keep current stage. // 如果为空,保持当前阶段。
if stage == StageInputInstance || stage == StageInputToken { if stage == StageInputInstance || stage == StageInputToken {
if value == "" { if value == "" {
return stage return stage
} }
} }
// set hostname for runner name if empty // 如果运行器名称为空,设置为主机名
if stage == StageInputRunnerName && value == "" { if stage == StageInputRunnerName && value == "" {
value, _ = os.Hostname() value, _ = os.Hostname()
} }
@ -154,19 +171,19 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
return StageInputRunnerName return StageInputRunnerName
case StageInputRunnerName: case StageInputRunnerName:
r.RunnerName = value r.RunnerName = value
// if there are some labels configured in config file, skip input labels stage // 如果配置文件中有标签配置,跳过输入标签阶段
if len(cfg.Runner.Labels) > 0 { if len(cfg.Runner.Labels) > 0 {
ls := make([]string, 0, len(cfg.Runner.Labels)) ls := make([]string, 0, len(cfg.Runner.Labels))
for _, l := range cfg.Runner.Labels { for _, l := range cfg.Runner.Labels {
_, err := labels.Parse(l) _, err := labels.Parse(l)
if err != nil { if err != nil {
log.WithError(err).Warnf("ignored invalid label %q", l) log.WithError(err).Warnf("忽略无效标签 %q", l)
continue continue
} }
ls = append(ls, l) ls = append(ls, l)
} }
if len(ls) == 0 { if len(ls) == 0 {
log.Warn("no valid labels configured in config file, runner may not be able to pick up jobs") log.Warn("配置文件中没有有效的标签配置,运行器可能无法接取作业")
} }
r.Labels = ls r.Labels = ls
return StageWaitingForRegistration return StageWaitingForRegistration
@ -179,7 +196,8 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
} }
if validateLabels(r.Labels) != nil { if validateLabels(r.Labels) != nil {
log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host)") log.Infoln("无效的标签, 请重新输入, 留空以使用默认标签 (例如, debian-latest:lcr.loongnix.cn/library/debian:latest) ")
r.Labels = nil
return StageInputLabels return StageInputLabels
} }
return StageWaitingForRegistration return StageWaitingForRegistration
@ -187,36 +205,54 @@ 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)
if err != nil { if err != nil {
return fmt.Errorf("failed to load config: %v", err) return fmt.Errorf("加载配置失败: %v", err)
} }
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)
if stage == StageWaitingForRegistration { if stage == StageWaitingForRegistration {
log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels) log.Infof("注册运行器,名称=%s, 实例=%s, 标签=%v", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels)
if err := doRegister(ctx, cfg, inputs); err != nil { if err := doRegister(ctx, cfg, inputs); err != nil {
return fmt.Errorf("Failed to register runner: %w", err) return fmt.Errorf("注册运行器失败: %w", err)
} }
log.Infof("Runner registered successfully.") log.Infof("运行器注册成功。")
return nil return nil
} }
@ -225,7 +261,7 @@ func registerInteractive(ctx context.Context, configFile string) error {
} }
if stage <= StageUnknown { if stage <= StageUnknown {
log.Errorf("Invalid input, please re-run act command.") log.Errorf("无效输入,请重新运行命令。")
return nil return nil
} }
} }
@ -234,18 +270,18 @@ func registerInteractive(ctx context.Context, configFile string) error {
func printStageHelp(stage registerStage) { func printStageHelp(stage registerStage) {
switch stage { switch stage {
case StageOverwriteLocalConfig: case StageOverwriteLocalConfig:
log.Infoln("Runner is already registered, overwrite local config? [y/N]") log.Infoln("运行器已注册,是否覆盖本地配置?[y/N]")
case StageInputInstance: case StageInputInstance:
log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):") log.Infoln("请输入 Gitea 实例 URL(例如, https://gitea.com/):")
case StageInputToken: case StageInputToken:
log.Infoln("Enter the runner token:") log.Infoln("请输入运行器令牌:")
case StageInputRunnerName: case StageInputRunnerName:
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
log.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname) log.Infof("请输入运行器名称(如果留空,使用主机名:%s):\n", hostname)
case StageInputLabels: case StageInputLabels:
log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host):") log.Infoln("请输入运行器标签, 留空以使用默认标签(逗号分隔, 例如, debian-latest:docker://lcr.loongnix.cn/library/debian:latest):")
case StageWaitingForRegistration: case StageWaitingForRegistration:
log.Infoln("Waiting for registration...") log.Infoln("等待注册...")
} }
} }
@ -254,42 +290,35 @@ 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,
}
regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// command line flag.
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
// specify labels in config file.
if len(cfg.Runner.Labels) > 0 { if len(cfg.Runner.Labels) > 0 {
if regArgs.Labels != "" { if regArgs.Labels != "" {
log.Warn("Labels from command will be ignored, use labels defined in config file.") log.Warn("命令行中的标签将被忽略,使用配置文件中定义的标签。")
} }
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("Runner name is empty, use hostname '%s'.", inputs.RunnerName) log.Infof("运行器名称为空,使用主机名 '%s'", inputs.RunnerName)
} }
if err := inputs.validate(); err != nil { if err := inputs.validate(); err != nil {
log.WithError(err).Errorf("Invalid input, please re-run act command.") 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("Failed to register runner: %w", err) return fmt.Errorf("注册运行器失败: %w", err)
} }
log.Infof("Runner registered successfully.") log.Infof("运行器注册成功。")
return nil return nil
} }
func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) error { func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) error {
// initial http client // 初始化 http 客户端
cli := client.New( cli := client.New(
inputs.InstanceAddr, inputs.InstanceAddr,
cfg.Runner.Insecure, cfg.Runner.Insecure,
@ -312,20 +341,21 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
} }
if err != nil { if err != nil {
log.WithError(err). log.WithError(err).
Errorln("Cannot ping the Gitea instance server") Errorln("无法 ping Gitea 实例服务器")
// TODO: if ping failed, retry or exit // TODO: 如果 ping 失败,重试或退出
time.Sleep(time.Second) time.Sleep(time.Second)
} else { } else {
log.Debugln("Successfully pinged the Gitea instance server") log.Debugln("成功 ping 到 Gitea 实例服务器")
break break
} }
} }
reg := &config.Registration{ reg := &config.Registration{
Name: inputs.RunnerName, Name: inputs.RunnerName,
Token: inputs.Token, Token: inputs.Token,
Address: inputs.InstanceAddr, Address: inputs.InstanceAddr,
Labels: inputs.Labels, Labels: inputs.Labels,
Ephemeral: inputs.Ephemeral,
} }
ls := make([]string, len(reg.Labels)) ls := make([]string, len(reg.Labels))
@ -333,16 +363,17 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
l, _ := labels.Parse(v) l, _ := labels.Parse(v)
ls[i] = l.Name ls[i] = l.Name
} }
// register new runner. // 注册新的运行器。
resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{ resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
Name: reg.Name, Name: reg.Name,
Token: reg.Token, Token: reg.Token,
Version: ver.Version(), Version: ver.Version(),
AgentLabels: ls, // Could be removed after Gitea 1.20 AgentLabels: ls, // Gitea 1.20 之后可能会被移除
Labels: ls, Labels: ls,
Ephemeral: reg.Ephemeral,
})) }))
if err != nil { if err != nil {
log.WithError(err).Error("poller: cannot register new runner") log.WithError(err).Error("poller: 无法注册新运行器")
return err return err
} }
@ -351,8 +382,13 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
reg.Name = resp.Msg.Runner.Name reg.Name = resp.Msg.Runner.Name
reg.Token = resp.Msg.Runner.Token reg.Token = resp.Msg.Runner.Token
if inputs.Ephemeral != resp.Msg.Runner.Ephemeral {
// TODO 我们不能通过 runner api 移除配置,如果在这里返回错误,我们只是填充数据库
log.Error("poller: 无法注册新的临时运行器,升级 Gitea 以获得安全性,自动使用 run-once")
}
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil { if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
return fmt.Errorf("failed to save runner config: %w", err) return fmt.Errorf("保存运行器配置失败: %w", err)
} }
return nil return nil
} }

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

@ -11,75 +11,172 @@ import (
"sync/atomic" "sync/atomic"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"gitea.com/gitea/act_runner/internal/app/run" "git.whlug.cn/LAA/loong_runner/internal/app/run"
"gitea.com/gitea/act_runner/internal/pkg/client" "git.whlug.cn/LAA/loong_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
) )
type Poller struct { type Poller struct {
client client.Client client client.Client // Gitea 客户端,用于与服务器通信
runner *run.Runner runner *run.Runner // 任务执行器
cfg *config.Config cfg *config.Config // 配置信息
tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea.
tasksVersion atomic.Int64 // 任务版本号,用于增量同步
pollingCtx context.Context // 轮询上下文
shutdownPolling context.CancelFunc // 轮询关闭函数
jobsCtx context.Context // 任务执行上下文
shutdownJobs context.CancelFunc // 任务执行关闭函数
done chan struct{} // 完成信号通道
} }
// New 构造函数,创建并初始化 Poller 实例
// 接受配置、客户端和服务运行器作为参数,并初始化必要的上下文和通道以便于后续操作。
func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller { func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller {
pollingCtx, shutdownPolling := context.WithCancel(context.Background())
jobsCtx, shutdownJobs := context.WithCancel(context.Background())
done := make(chan struct{})
return &Poller{ return &Poller{
client: client, client: client,
runner: runner, runner: runner,
cfg: cfg, cfg: cfg,
pollingCtx: pollingCtx,
shutdownPolling: shutdownPolling,
jobsCtx: jobsCtx,
shutdownJobs: shutdownJobs,
done: done,
} }
} }
func (p *Poller) Poll(ctx context.Context) { // Poll 持续轮询模式
// 启动多个 goroutine 来并发地轮询任务。每个 goroutine 调用 poll 方法进行实际的工作。在所有工作完成之后,通过关闭 done 通道发出信号。
func (p *Poller) Poll() {
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1) limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for i := 0; i < p.cfg.Runner.Capacity; i++ { for i := 0; i < p.cfg.Runner.Capacity; i++ {
wg.Add(1) wg.Add(1)
go p.poll(ctx, wg, limiter) go p.poll(wg, limiter)
} }
wg.Wait() wg.Wait()
// 发出我们正在关闭的信号
close(p.done)
} }
func (p *Poller) poll(ctx context.Context, wg *sync.WaitGroup, limiter *rate.Limiter) { // PollOnce 单次轮询模式
// 类似于 Poll,但是只执行一次任务轮询。同样会在完成后关闭 done 通道。
func (p *Poller) PollOnce() {
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
p.pollOnce(limiter)
// 发出我们已经完成的信号
close(p.done)
}
// Shutdown 关闭
// 关闭轮询过程。首先取消轮询上下文,然后等待所有任务完成。如果超时发生,则强制关闭所有正在运行的任务并确保状态报告给 Gitea。
func (p *Poller) Shutdown(ctx context.Context) error {
p.shutdownPolling()
select {
// 优雅地完成关闭
case <-p.done:
return nil
// 关闭超时
case <-ctx.Done():
// 当超时和优雅关闭同时发生时,
// 这个分支可能会被触发。这里进行非阻塞检查,
// 避免在不必要的情况下发送错误。
_, ok := <-p.done
if !ok {
return nil
}
// 强制关闭所有运行中的任务
p.shutdownJobs()
// 等待运行中的任务向Gitea报告其状态
_, _ = <-p.done
return ctx.Err()
}
}
// poll 轮询循环
// 这是实际执行轮询逻辑的地方。它会持续调用 pollOnce 方法来获取并处理任务,直到上下文被取消。
func (p *Poller) poll(wg *sync.WaitGroup, limiter *rate.Limiter) {
defer wg.Done() defer wg.Done()
for { for {
if err := limiter.Wait(ctx); err != nil { p.pollOnce(limiter)
if ctx.Err() != nil {
log.WithError(err).Debug("limiter wait failed") select {
case <-p.pollingCtx.Done():
return
default:
continue
}
}
}
// pollOnce 单次轮询执行
// 负责单次任务轮询。它首先等待速率限制器允许继续,然后尝试获取一个新任务。一旦获取到任务,就调用 runTaskWithRecover 方法来执行任务。
func (p *Poller) pollOnce(limiter *rate.Limiter) {
for {
if err := limiter.Wait(p.pollingCtx); err != nil {
if p.pollingCtx.Err() != nil {
log.WithError(err).Debug("速率限制等待失败")
} }
return return
} }
task, ok := p.fetchTask(ctx) task, ok := p.fetchTask(p.pollingCtx)
if !ok { if !ok {
continue continue
} }
p.runTaskWithRecover(ctx, task)
p.runTaskWithRecover(p.jobsCtx, task)
return
} }
} }
// runTaskWithRecover 安全执行任务
// 执行给定的任务,并提供 panic 恢复机制以防止意外崩溃。如果任务执行过程中出现错误或 panic,都会记录相应的日志信息。
func (p *Poller) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) { func (p *Poller) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) {
// 建立错误恢复机制
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
err := fmt.Errorf("panic: %v", r) err := fmt.Errorf("panic: %v", r)
log.WithError(err).Error("panic in runTaskWithRecover") log.WithError(err).Error("runTaskWithRecover中发生panic")
} }
}() }()
if err := p.runner.Run(ctx, task); err != nil { if err := p.runner.Run(ctx, task); err != nil {
log.WithError(err).Error("failed to run task") log.WithError(err).Error("运行任务失败")
} }
} }
// fetchTask 获取任务
// 尝试从 Gitea 获取一个新任务。如果成功获取到任务且该任务的版本号比本地缓存的大,则更新本地缓存的版本号。
// 如果获取失败或没有可用的任务,返回 false;否则返回 true 和任务数据。
func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) { func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout) reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout)
defer cancel() defer cancel()
// Load the version value that was in the cache when the request was sent. // 加载发送请求时缓存中的版本值
v := p.tasksVersion.Load() v := p.tasksVersion.Load()
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{ resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{
TasksVersion: v, TasksVersion: v,
@ -88,7 +185,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
err = nil err = nil
} }
if err != nil { if err != nil {
log.WithError(err).Error("failed to fetch task") log.WithError(err).Error("获取任务失败")
return nil, false return nil, false
} }
@ -104,7 +201,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
return nil, false return nil, false
} }
// got a task, set `tasksVersion` to zero to focre query db in next request. // 收到一个任务,将`tasksVersion`设置为零,以便在下一个请求中创建查询数据库。
p.tasksVersion.CompareAndSwap(resp.Msg.TasksVersion, 0) p.tasksVersion.CompareAndSwap(resp.Msg.TasksVersion, 0)
return resp.Msg.Task, true return resp.Msg.Task, true

View File

@ -0,0 +1,23 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package run
import (
"io"
log "github.com/sirupsen/logrus"
)
// NullLogger用于创建一个新的JobLogger以丢弃日志。
// 这将防止这些日志被记录到标准输出,但会通过其钩子将它们转发给Reporter。
type NullLogger struct{}
// WithJobLogger 创建一个新的 logrus.Logger,它将丢弃所有日志。
func (n NullLogger) WithJobLogger() *log.Logger {
logger := log.New()
logger.SetOutput(io.Discard)
logger.SetLevel(log.TraceLevel)
return logger
}

View File

@ -13,7 +13,7 @@ import (
"time" "time"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/nektos/act/pkg/artifactcache" "github.com/nektos/act/pkg/artifactcache"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
@ -21,26 +21,28 @@ import (
"github.com/nektos/act/pkg/runner" "github.com/nektos/act/pkg/runner"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gitea.com/gitea/act_runner/internal/pkg/client" "git.whlug.cn/LAA/loong_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config" "git.whlug.cn/LAA/loong_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/labels" "git.whlug.cn/LAA/loong_runner/internal/pkg/labels"
"gitea.com/gitea/act_runner/internal/pkg/report" "git.whlug.cn/LAA/loong_runner/internal/pkg/report"
"gitea.com/gitea/act_runner/internal/pkg/ver" "git.whlug.cn/LAA/loong_runner/internal/pkg/ver"
) )
// Runner runs the pipeline. // Runner 运行流水线
type Runner struct { type Runner struct {
name string name string // Runner 名称
cfg *config.Config cfg *config.Config // 配置信息
client client.Client client client.Client // Gitea 客户端
labels labels.Labels labels labels.Labels // 标签集合
envs map[string]string envs map[string]string // 环境变量
runningTasks sync.Map // 正在运行的任务
runningTasks sync.Map // 使用 sync.Map 来存储正在运行的任务 ID
} }
// NewRunner 使用提供的配置、注册信息和客户端创建一个新的 Runner 实例
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner { func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
ls := labels.Labels{} ls := labels.Labels{}
for _, v := range reg.Labels { for _, v := range reg.Labels {
@ -52,6 +54,7 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
for k, v := range cfg.Runner.Envs { for k, v := range cfg.Runner.Envs {
envs[k] = v envs[k] = v
} }
// 初始化缓存环境变量
if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled { if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled {
if cfg.Cache.ExternalServer != "" { if cfg.Cache.ExternalServer != "" {
envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer
@ -63,19 +66,20 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
log.StandardLogger().WithField("module", "cache_request"), log.StandardLogger().WithField("module", "cache_request"),
) )
if err != nil { if err != nil {
log.Errorf("cannot init cache server, it will be disabled: %v", err) log.Errorf("无法初始化缓存服务器,将禁用它:%v", err)
// go on // 继续执行
} else { } else {
envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/" envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/"
} }
} }
} }
// set artifact gitea api // 设置 artifact gitea api
artifactGiteaAPI := strings.TrimSuffix(cli.Address(), "/") + "/api/actions_pipeline/" artifactGiteaAPI := strings.TrimSuffix(cli.Address(), "/") + "/api/actions_pipeline/"
envs["ACTIONS_RUNTIME_URL"] = artifactGiteaAPI envs["ACTIONS_RUNTIME_URL"] = artifactGiteaAPI
envs["ACTIONS_RESULTS_URL"] = strings.TrimSuffix(cli.Address(), "/")
// Set specific environments to distinguish between Gitea and GitHub // 设置特定环境变量以区分 Gitea GitHub
envs["GITEA_ACTIONS"] = "true" envs["GITEA_ACTIONS"] = "true"
envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version() envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version()
@ -88,13 +92,16 @@ func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client)
} }
} }
// Run 在给定的上下文中执行任务,确保同一时间仅运行一个任务
func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
// 检查任务是否已在运行
if _, ok := r.runningTasks.Load(task.Id); ok { if _, ok := r.runningTasks.Load(task.Id); ok {
return fmt.Errorf("task %d is already running", task.Id) return fmt.Errorf("任务 %d 已经在运行", task.Id)
} }
r.runningTasks.Store(task.Id, struct{}{}) r.runningTasks.Store(task.Id, struct{}{})
defer r.runningTasks.Delete(task.Id) defer r.runningTasks.Delete(task.Id)
// 创建带超时的子上下文
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout) ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
defer cancel() defer cancel()
reporter := report.NewReporter(ctx, cancel, r.client, task) reporter := report.NewReporter(ctx, cancel, r.client, task)
@ -112,6 +119,7 @@ func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
return nil return nil
} }
// run 执行具体的任务逻辑
func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.Reporter) (err error) { func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.Reporter) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -119,7 +127,8 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
} }
}() }()
reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", r.name, ver.Version(), task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue()) // 记录任务接收日志
reporter.Logf("%s(版本:%s) 收到任务 %v 的作业 %v,由事件触发: %s", r.name, ver.Version(), task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue())
workflow, jobID, err := generateWorkflow(task) workflow, jobID, err := generateWorkflow(task)
if err != nil { if err != nil {
@ -135,10 +144,11 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
taskContext := task.Context.Fields taskContext := task.Context.Fields
log.Infof("task %v repo is %v %v %v", task.Id, taskContext["repository"].GetStringValue(), log.Infof("任务 %v 仓库是 %v %v %v", task.Id, taskContext["repository"].GetStringValue(),
taskContext["gitea_default_actions_url"].GetStringValue(), taskContext["gitea_default_actions_url"].GetStringValue(),
r.client.Address()) r.client.Address())
// 构建预设的 GitHub 上下文环境
preset := &model.GithubContext{ preset := &model.GithubContext{
Event: taskContext["event"].GetStructValue().AsMap(), Event: taskContext["event"].GetStructValue().AsMap(),
RunID: taskContext["run_id"].GetStringValue(), RunID: taskContext["run_id"].GetStringValue(),
@ -156,14 +166,25 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
RepositoryOwner: taskContext["repository_owner"].GetStringValue(), RepositoryOwner: taskContext["repository_owner"].GetStringValue(),
RetentionDays: taskContext["retention_days"].GetStringValue(), RetentionDays: taskContext["retention_days"].GetStringValue(),
} }
// 优先使用 GITEA_TOKEN
if t := task.Secrets["GITEA_TOKEN"]; t != "" { if t := task.Secrets["GITEA_TOKEN"]; t != "" {
preset.Token = t preset.Token = t
} else if t := task.Secrets["GITHUB_TOKEN"]; t != "" { } else if t := task.Secrets["GITHUB_TOKEN"]; t != "" {
preset.Token = t preset.Token = t
} }
// use task token to action api token if actionsIdTokenRequestUrl := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIdTokenRequestUrl != "" {
r.envs["ACTIONS_RUNTIME_TOKEN"] = preset.Token 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()
if giteaRuntimeToken == "" {
// 兼容旧版本 Gitea Server
giteaRuntimeToken = preset.Token
}
r.envs["ACTIONS_RUNTIME_TOKEN"] = giteaRuntimeToken
eventJSON, err := json.Marshal(preset.Event) eventJSON, err := json.Marshal(preset.Event)
if err != nil { if err != nil {
@ -175,16 +196,15 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
maxLifetime = time.Until(deadline) maxLifetime = time.Until(deadline)
} }
// 创建 Runner 配置
runnerConfig := &runner.Config{ runnerConfig := &runner.Config{
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>" Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)),
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", r.cfg.Container.WorkdirParent, preset.Repository)),
BindWorkdir: false, BindWorkdir: false,
ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent), ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent),
ReuseContainers: false, ReuseContainers: false,
ForcePull: false, ForcePull: r.cfg.Container.ForcePull,
ForceRebuild: false, ForceRebuild: r.cfg.Container.ForceRebuild,
LogOutput: true, LogOutput: true,
JSONLogger: false, JSONLogger: false,
Env: r.envs, Env: r.envs,
@ -204,6 +224,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
PlatformPicker: r.labels.PickPlatform, PlatformPicker: r.labels.PickPlatform,
Vars: task.Vars, Vars: task.Vars,
ValidVolumes: r.cfg.Container.ValidVolumes, ValidVolumes: r.cfg.Container.ValidVolumes,
InsecureSkipTLS: r.cfg.Runner.Insecure,
} }
rr, err := runner.New(runnerConfig) rr, err := runner.New(runnerConfig)
@ -212,11 +233,15 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
} }
executor := rr.NewPlanExecutor(plan) executor := rr.NewPlanExecutor(plan)
reporter.Logf("workflow prepared") reporter.Logf("工作流程已准备就绪")
// add logger recorders // 添加日志记录器
ctx = common.WithLoggerHook(ctx, reporter) ctx = common.WithLoggerHook(ctx, reporter)
if !log.IsLevelEnabled(log.DebugLevel) {
ctx = runner.WithJobLoggerFactory(ctx, NullLogger{})
}
execErr := executor(ctx) execErr := executor(ctx)
reporter.SetOutputs(job.Outputs) reporter.SetOutputs(job.Outputs)
return execErr return execErr

View File

@ -22,7 +22,7 @@ func generateWorkflow(task *runnerv1.Task) (*model.Workflow, string, error) {
jobIDs := workflow.GetJobIDs() jobIDs := workflow.GetJobIDs()
if len(jobIDs) != 1 { if len(jobIDs) != 1 {
return nil, "", fmt.Errorf("multiple jobs found: %v", jobIDs) return nil, "", fmt.Errorf("找到多个工作: %v", jobIDs)
} }
jobID := jobIDs[0] jobID := jobIDs[0]

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

@ -11,7 +11,7 @@ import (
"code.gitea.io/actions-proto-go/ping/v1/pingv1connect" "code.gitea.io/actions-proto-go/ping/v1/pingv1connect"
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect" "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
"github.com/bufbuild/connect-go" "connectrpc.com/connect"
) )
func getHTTPClient(endpoint string, insecure bool) *http.Client { func getHTTPClient(endpoint string, insecure bool) *http.Client {
@ -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

@ -1,11 +1,11 @@
// Code generated by mockery v2.26.1. DO NOT EDIT. // Code generated by mockery v2.42.1. DO NOT EDIT.
package mocks package mocks
import ( import (
context "context" context "context"
connect "github.com/bufbuild/connect-go" connect "connectrpc.com/connect"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
@ -23,6 +23,10 @@ type Client struct {
func (_m *Client) Address() string { func (_m *Client) Address() string {
ret := _m.Called() ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Address")
}
var r0 string var r0 string
if rf, ok := ret.Get(0).(func() string); ok { if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf() r0 = rf()
@ -37,6 +41,10 @@ func (_m *Client) Address() string {
func (_m *Client) Declare(_a0 context.Context, _a1 *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) { func (_m *Client) Declare(_a0 context.Context, _a1 *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Declare")
}
var r0 *connect.Response[runnerv1.DeclareResponse] var r0 *connect.Response[runnerv1.DeclareResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error)); ok {
@ -63,6 +71,10 @@ func (_m *Client) Declare(_a0 context.Context, _a1 *connect.Request[runnerv1.Dec
func (_m *Client) FetchTask(_a0 context.Context, _a1 *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) { func (_m *Client) FetchTask(_a0 context.Context, _a1 *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for FetchTask")
}
var r0 *connect.Response[runnerv1.FetchTaskResponse] var r0 *connect.Response[runnerv1.FetchTaskResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error)); ok {
@ -89,6 +101,10 @@ func (_m *Client) FetchTask(_a0 context.Context, _a1 *connect.Request[runnerv1.F
func (_m *Client) Insecure() bool { func (_m *Client) Insecure() bool {
ret := _m.Called() ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Insecure")
}
var r0 bool var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok { if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf() r0 = rf()
@ -103,6 +119,10 @@ func (_m *Client) Insecure() bool {
func (_m *Client) Ping(_a0 context.Context, _a1 *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { func (_m *Client) Ping(_a0 context.Context, _a1 *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Ping")
}
var r0 *connect.Response[pingv1.PingResponse] var r0 *connect.Response[pingv1.PingResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error)); ok {
@ -129,6 +149,10 @@ func (_m *Client) Ping(_a0 context.Context, _a1 *connect.Request[pingv1.PingRequ
func (_m *Client) Register(_a0 context.Context, _a1 *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error) { func (_m *Client) Register(_a0 context.Context, _a1 *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Register")
}
var r0 *connect.Response[runnerv1.RegisterResponse] var r0 *connect.Response[runnerv1.RegisterResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error)); ok {
@ -155,6 +179,10 @@ func (_m *Client) Register(_a0 context.Context, _a1 *connect.Request[runnerv1.Re
func (_m *Client) UpdateLog(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error) { func (_m *Client) UpdateLog(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for UpdateLog")
}
var r0 *connect.Response[runnerv1.UpdateLogResponse] var r0 *connect.Response[runnerv1.UpdateLogResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error)); ok {
@ -181,6 +209,10 @@ func (_m *Client) UpdateLog(_a0 context.Context, _a1 *connect.Request[runnerv1.U
func (_m *Client) UpdateTask(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error) { func (_m *Client) UpdateTask(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for UpdateTask")
}
var r0 *connect.Response[runnerv1.UpdateTaskResponse] var r0 *connect.Response[runnerv1.UpdateTaskResponse]
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error)); ok {
@ -203,13 +235,13 @@ func (_m *Client) UpdateTask(_a0 context.Context, _a1 *connect.Request[runnerv1.
return r0, r1 return r0, r1
} }
type mockConstructorTestingTNewClient interface { // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewClient(t interface {
mock.TestingT mock.TestingT
Cleanup(func()) Cleanup(func())
} },
) *Client {
// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewClient(t mockConstructorTestingTNewClient) *Client {
mock := &Client{} mock := &Client{}
mock.Mock.Test(t) mock.Mock.Test(t)

View File

@ -1,87 +1,101 @@
# Example configuration file, it's safe to copy this as the default config file without any modification. # 示例配置文件,可以安全地将其复制为默认配置文件而无需任何修改。
# You don't have to copy this file to your instance, # 您不必将此文件复制到您的实例,
# just run `./act_runner generate-config > config.yaml` to generate a config file. # 只需运行 `./act_runner generate-config > config.yaml` 来生成配置文件。
log: log:
# The level of logging, can be trace, debug, info, warn, error, fatal # 日志级别,可以是 tracedebuginfowarnerrorfatal
level: info level: info
runner: runner:
# Where to store the registration result. # 存储注册结果的路径。
file: .runner file: .runner
# Execute how many tasks concurrently at the same time. # 同时执行多少个任务。
capacity: 1 capacity: 1
# Extra environment variables to run jobs. # 运行作业的额外环境变量。
envs: envs:
A_TEST_ENV_NAME_1: a_test_env_value_1 A_TEST_ENV_NAME_1: a_test_env_value_1
A_TEST_ENV_NAME_2: a_test_env_value_2 A_TEST_ENV_NAME_2: a_test_env_value_2
# Extra environment variables to run jobs from a file. # 从文件中运行作业的额外环境变量。
# It will be ignored if it's empty or the file doesn't exist. # 如果为空或文件不存在,则会被忽略。
env_file: .env env_file: .env
# The timeout for a job to be finished. # 作业完成的超时时间。
# Please note that the Gitea instance also has a timeout (3h by default) for the job. # 请注意,Gitea 实例也有一个作业超时时间(默认为 3 小时)。
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this. # 如果这个超时时间比 Gitea 实例的超时时间短,作业可能会被 Gitea 实例停止。
timeout: 3h timeout: 3h
# Whether skip verifying the TLS certificate of the Gitea instance. # 运行器在关闭时等待运行作业完成的超时时间。
# 在这个超时时间之后仍未完成的任何运行作业将被取消。
shutdown_timeout: 0s
# 是否跳过验证 Gitea 实例的 TLS 证书。
insecure: false insecure: false
# The timeout for fetching the job from the Gitea instance. # 从 Gitea 实例获取作业的超时时间。
fetch_timeout: 5s fetch_timeout: 5s
# The interval for fetching the job from the Gitea instance. # 从 Gitea 实例获取作业的时间间隔。
fetch_interval: 2s fetch_interval: 2s
# The labels of a runner are used to determine which jobs the runner can run, and how to run them. # 运行器的标签用于确定运行器可以运行哪些作业以及如何运行它们。
# Like: ["macos-arm64:host", "ubuntu-latest:docker://node:16-bullseye", "ubuntu-22.04:docker://node:16-bullseye"] # 例如:"linux-loong64.abi2:host" 或 "debian-latest:docker://lcr.loongnix.cn/library/debian:latest"
# If it's empty when registering, it will ask for inputting labels. # 在 https://gitea.com/docker.gitea.com/runner-images 查找 Gitea 提供的更多镜像。
# If it's empty when execute `deamon`, will use labels in `.runner` file. # 如果在注册时为空,它会要求输入标签。
labels: [] # 如果在执行 `daemon` 时为空,将使用 `.runner` 文件中的标签。
labels:
- "debian-latest:docker://lcr.loongnix.cn/library/debian:latest"
- "anolisos-latest:docker://lcr.loongnix.cn/library/anolisos:latest"
- "anolisos-23.2:docker://lcr.loongnix.cn/library/anolisos:23.2"
cache: cache:
# Enable cache server to use actions/cache. # 启用缓存服务器以使用 actions/cache
enabled: true enabled: true
# The directory to store the cache data. # 存储缓存数据的目录。
# If it's empty, the cache data will be stored in $HOME/.cache/actcache. # 如果为空,缓存数据将存储在 $HOME/.cache/actcache
dir: "" dir: ""
# The host of the cache server. # 缓存服务器的主机。
# It's not for the address to listen, but the address to connect from job containers. # 它不是用于监听的地址,而是用于作业容器连接的地址。
# So 0.0.0.0 is a bad choice, leave it empty to detect automatically. # 所以 0.0.0.0 是一个糟糕的选择,留空以自动检测。
host: "" host: ""
# The port of the cache server. # 缓存服务器的端口。
# 0 means to use a random available port. # 0 表示使用随机可用端口。
port: 0 port: 0
# The external cache server URL. Valid only when enable is true. # 外部缓存服务器 URL。仅在启用时有效。
# If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself. # 如果指定了它,act_runner 将使用此 URL 作为 ACTIONS_CACHE_URL 而不是自己启动一个服务器。
# The URL should generally end with "/". # URL 通常应该以 "/" 结尾。
external_server: "" external_server: ""
container: container:
# Specifies the network to which the container will connect. # 指定容器将连接的网络。
# Could be host, bridge or the name of a custom network. # 可以是 hostbridge 或自定义网络的名称。
# If it's empty, act_runner will create a network automatically. # 如果为空,act_runner 将自动创建一个网络。
network: "" network: ""
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). # 启动任务容器时是否使用特权模式(特权模式对于 Docker-in-Docker 是必需的)。
privileged: false privileged: false
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). # 容器启动时使用的其他选项(例如,--add-host=my.gitea.url:host-gateway)。
options: options:
# The parent directory of a job's working directory. # 作业工作目录的父目录。
# If it's empty, /workspace will be used. # 注意:不需要在路径前添加第一个 '/',因为 act_runner 会自动添加。
# 如果路径以 '/' 开头,'/' 将被修剪。
# 例如,如果父目录是 /path/to/my/dir,workdir_parent 应该是 path/to/my/dir
# 如果为空,将使用 /workspace。
workdir_parent: workdir_parent:
# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob # 可以挂载到容器的卷(包括绑定挂载)。支持 glob 语法,参见 https://github.com/gobwas/glob
# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. # 您可以指定多个卷。如果序列为空,则不能挂载任何卷。
# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: # 例如,如果您只允许容器挂载 `data` 卷和 `/src` 中的所有 json 文件,您应该将配置更改为:
# valid_volumes: # valid_volumes:
# - data # - data
# - /src/*.json # - /src/*.json
# If you want to allow any volume, please use the following configuration: # 如果您想允许任何卷,请使用以下配置:
# valid_volumes: # valid_volumes:
# - '**' # - '**'
valid_volumes: [] valid_volumes: []
# overrides the docker client host with the specified one. # 用指定的主机覆盖 docker 客户端主机。
# If it's empty, act_runner will find an available docker host automatically. # 如果为空,act_runner 将自动查找可用的 docker 主机。
# If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers. # 如果是 "-"act_runner 将自动查找可用的 docker 主机,但 docker 主机不会挂载到作业容器和服务容器。
# If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. # 如果不为空或 "-",将使用指定的 docker 主机。如果不起作用,将返回错误。
docker_host: "" docker_host: ""
# 即使已经存在也拉取 Docker 镜像
force_pull: true
# 即使已经存在也重建 Docker 镜像
force_rebuild: false
host: host:
# The parent directory of a job's working directory. # 作业工作目录的父目录。
# If it's empty, $HOME/.cache/act/ will be used. # 如果为空,将使用 $HOME/.cache/act/
workdir_parent: workdir_parent:

View File

@ -14,69 +14,72 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Log represents the configuration for logging. // Log 代表日志的配置。
type Log struct { type Log struct {
Level string `yaml:"level"` // Level indicates the logging level. Level string `yaml:"level"` // Level 表示日志级别。
} }
// Runner represents the configuration for the runner. // Runner 代表运行器的配置。
type Runner struct { type Runner struct {
File string `yaml:"file"` // File specifies the file path for the runner. File string `yaml:"file"` // File 指定运行器的文件路径。
Capacity int `yaml:"capacity"` // Capacity specifies the capacity of the runner. Capacity int `yaml:"capacity"` // Capacity 指定运行器的容量。
Envs map[string]string `yaml:"envs"` // Envs stores environment variables for the runner. Envs map[string]string `yaml:"envs"` // Envs 存储运行器的环境变量。
EnvFile string `yaml:"env_file"` // EnvFile specifies the path to the file containing environment variables for the runner. EnvFile string `yaml:"env_file"` // EnvFile 指定包含运行器环境变量的文件路径。
Timeout time.Duration `yaml:"timeout"` // Timeout specifies the duration for runner timeout. Timeout time.Duration `yaml:"timeout"` // Timeout 指定运行器超时的持续时间。
Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode. ShutdownTimeout time.Duration `yaml:"shutdown_timeout"` // ShutdownTimeout 指定在运行器关闭期间等待运行作业完成的持续时间。
FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources. Insecure bool `yaml:"insecure"` // Insecure 表示运行器是否在不安全模式下运行。
FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources. FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout 指定获取资源的超时持续时间。
Labels []string `yaml:"labels"` // Labels specifies the labels of the runner. Labels are declared on each startup FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval 指定获取资源的间隔持续时间。
Labels []string `yaml:"labels"` // Labels 指定运行器的标签。每个启动时声明标签。
} }
// Cache represents the configuration for caching. // Cache 代表缓存的配置。
type Cache struct { type Cache struct {
Enabled *bool `yaml:"enabled"` // Enabled indicates whether caching is enabled. It is a pointer to distinguish between false and not set. If not set, it will be true. Enabled *bool `yaml:"enabled"` // Enabled 表示是否启用缓存。它是指针,用于区分 false 和未设置。如果未设置,它将为 true
Dir string `yaml:"dir"` // Dir specifies the directory path for caching. Dir string `yaml:"dir"` // Dir 指定缓存的目录路径。
Host string `yaml:"host"` // Host specifies the caching host. Host string `yaml:"host"` // Host 指定缓存主机。
Port uint16 `yaml:"port"` // Port specifies the caching port. Port uint16 `yaml:"port"` // Port 指定缓存端口。
ExternalServer string `yaml:"external_server"` // ExternalServer specifies the URL of external cache server ExternalServer string `yaml:"external_server"` // ExternalServer 指定外部缓存服务器的 URL。
} }
// Container represents the configuration for the container. // Container 代表容器的配置。
type Container struct { type Container struct {
Network string `yaml:"network"` // Network specifies the network for the container. Network string `yaml:"network"` // Network 指定容器的网络。
NetworkMode string `yaml:"network_mode"` // Deprecated: use Network instead. Could be removed after Gitea 1.20 NetworkMode string `yaml:"network_mode"` // 已弃用:使用 Network 替代。可能在 Gitea 1.20 之后被移除。
Privileged bool `yaml:"privileged"` // Privileged indicates whether the container runs in privileged mode. Privileged bool `yaml:"privileged"` // Privileged 表示容器是否以特权模式运行。
Options string `yaml:"options"` // Options specifies additional options for the container. Options string `yaml:"options"` // Options 指定容器的其他选项。
WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory. WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent 指定容器工作目录的父目录。
ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers. ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes 指定可以挂载到容器的卷(包括绑定挂载)。
DockerHost string `yaml:"docker_host"` // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST. DockerHost string `yaml:"docker_host"` // DockerHost 指定 Docker 主机。它覆盖环境变量 DOCKER_HOST 中指定的值。
ForcePull bool `yaml:"force_pull"` // 即使已经存在也拉取 Docker 镜像。
ForceRebuild bool `yaml:"force_rebuild"` // 即使已经存在也重建 Docker 镜像。
} }
// Host represents the configuration for the host. // Host 代表主机的配置。
type Host struct { type Host struct {
WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the host's working directory. WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent 指定主机工作目录的父目录。
} }
// Config represents the overall configuration. // Config 代表整体配置。
type Config struct { type Config struct {
Log Log `yaml:"log"` // Log represents the configuration for logging. Log Log `yaml:"log"` // Log 代表日志的配置。
Runner Runner `yaml:"runner"` // Runner represents the configuration for the runner. Runner Runner `yaml:"runner"` // Runner 代表运行器的配置。
Cache Cache `yaml:"cache"` // Cache represents the configuration for caching. Cache Cache `yaml:"cache"` // Cache 代表缓存的配置。
Container Container `yaml:"container"` // Container represents the configuration for the container. Container Container `yaml:"container"` // Container 代表容器的配置。
Host Host `yaml:"host"` // Host represents the configuration for the host. Host Host `yaml:"host"` // Host 代表主机的配置。
} }
// LoadDefault returns the default configuration. // LoadDefault 返回默认配置。
// If file is not empty, it will be used to load the configuration. // 如果文件不为空,它将被用来加载配置。
func LoadDefault(file string) (*Config, error) { func LoadDefault(file string) (*Config, error) {
cfg := &Config{} cfg := &Config{}
if file != "" { if file != "" {
content, err := os.ReadFile(file) content, err := os.ReadFile(file)
if err != nil { if err != nil {
return nil, fmt.Errorf("open config file %q: %w", file, err) return nil, fmt.Errorf("打开配置文件 %q: %w", file, err)
} }
if err := yaml.Unmarshal(content, cfg); err != nil { if err := yaml.Unmarshal(content, cfg); err != nil {
return nil, fmt.Errorf("parse config file %q: %w", file, err) return nil, fmt.Errorf("解析配置文件 %q: %w", file, err)
} }
} }
compatibleWithOldEnvs(file != "", cfg) compatibleWithOldEnvs(file != "", cfg)
@ -85,7 +88,10 @@ func LoadDefault(file string) (*Config, error) {
if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() { if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() {
envs, err := godotenv.Read(cfg.Runner.EnvFile) envs, err := godotenv.Read(cfg.Runner.EnvFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("read env file %q: %w", cfg.Runner.EnvFile, err) return nil, fmt.Errorf("读取环境文件 %q: %w", cfg.Runner.EnvFile, err)
}
if cfg.Runner.Envs == nil {
cfg.Runner.Envs = map[string]string{}
} }
for k, v := range envs { for k, v := range envs {
cfg.Runner.Envs[k] = v cfg.Runner.Envs[k] = v
@ -129,14 +135,13 @@ func LoadDefault(file string) (*Config, error) {
cfg.Runner.FetchInterval = 2 * time.Second cfg.Runner.FetchInterval = 2 * time.Second
} }
// although `container.network_mode` will be deprecated, but we have to be compatible with it for now. // 虽然 `container.network_mode` 将被弃用,但我们现在必须与它兼容。
if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" { if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" {
log.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.") log.Warn("您正在尝试使用已弃用的配置项 `container.network_mode`,请使用 `container.network` 替代。")
if cfg.Container.NetworkMode == "bridge" { if cfg.Container.NetworkMode == "bridge" {
// Previously, if the value of `container.network_mode` is `bridge`, we will create a new network for job. // 以前,如果 `container.network_mode` 的值是 `bridge`,我们会为作业创建一个新的网络。
// But “bridge” is easily confused with the bridge network created by Docker by default. // 但是,“bridge”很容易与 Docker 默认创建的桥接网络混淆。
// So we set the value of `container.network` to empty string to make `act_runner` automatically create a new network for job. // 所以我们将 `container.network` 的值设置为空字符串,以使 `act_runner` 为作业自动创建一个新的网络。
cfg.Container.Network = ""
} else { } else {
cfg.Container.Network = cfg.Container.NetworkMode cfg.Container.Network = cfg.Container.NetworkMode
} }

View File

@ -11,16 +11,16 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// Deprecated: could be removed in the future. TODO: remove it when Gitea 1.20.0 is released. // 已弃用:未来可能会移除。TODO: 在 Gitea 1.20.0 发布时将其移除。
// Be compatible with old envs. // 兼容旧环境。
func compatibleWithOldEnvs(fileUsed bool, cfg *Config) { func compatibleWithOldEnvs(fileUsed bool, cfg *Config) {
handleEnv := func(key string) (string, bool) { handleEnv := func(key string) (string, bool) {
if v, ok := os.LookupEnv(key); ok { if v, ok := os.LookupEnv(key); ok {
if fileUsed { if fileUsed {
log.Warnf("env %s has been ignored because config file is used", key) log.Warnf("环境 %s 已被忽略,因为使用了配置文件", key)
return "", false return "", false
} }
log.Warnf("env %s will be deprecated, please use config file instead", key) log.Warnf("环境变量 %s 将被弃用,请改用配置文件。", key)
return v, true return v, true
} }
return "", false return "", false

View File

@ -8,18 +8,19 @@ import (
"os" "os"
) )
const registrationWarning = "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner." const registrationWarning = "此文件由`Loong Runner`自动生成。除非你知道自己在做什么, 否则不要手动编辑它。删除此文件将导致`Loong Runner`重新注册为新的运行器。"
// Registration is the registration information for a runner // Registration 表示运行器的注册信息
type Registration struct { type Registration struct {
Warning string `json:"WARNING"` // Warning message to display, it's always the registrationWarning constant Warning string `json:"WARNING"` // 警告信息,始终为 registrationWarning 常量
ID int64 `json:"id"` ID int64 `json:"id"` // 唯一标识符(整数类型)
UUID string `json:"uuid"` UUID string `json:"uuid"` // 全局唯一标识符(字符串类型)
Name string `json:"name"` Name string `json:"name"` // 运行器名称
Token string `json:"token"` Token string `json:"token"` // 认证令牌,用于与调度器通信
Address string `json:"address"` Address string `json:"address"` // 运行器的网络地址(如IP或域名)
Labels []string `json:"labels"` Labels []string `json:"labels"` // 运行器关联的标签列表(用于任务匹配)
Ephemeral bool `json:"ephemeral"` // 是否为临时实例(完成任务后自动销毁)
} }
func LoadRegistration(file string) (*Registration, error) { func LoadRegistration(file string) (*Registration, error) {

View File

@ -1,5 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Package envcheck provides a simple way to check if the environment is ready to run jobs. // envcheck包中提供了一种简单的方法, 用来检查环境是否准备好执行工作。
package envcheck package envcheck

View File

@ -27,7 +27,7 @@ func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error {
_, err = cli.Ping(ctx) _, err = cli.Ping(ctx)
if err != nil { if err != nil {
return fmt.Errorf("cannot ping the docker daemon, does it running? %w", err) return fmt.Errorf("无法ping通docker守护进程, 它是否在运行? %w", err)
} }
return nil return nil

View File

@ -9,7 +9,9 @@ import (
) )
const ( const (
SchemeHost = "host" // SchemeHost 表示主机模式
SchemeHost = "host"
// SchemeDocker 表示 Docker 模式
SchemeDocker = "docker" SchemeDocker = "docker"
) )
@ -19,6 +21,7 @@ type Label struct {
Arg string Arg string
} }
// Parse 解析标签字符串并返回 Label 结构体。
func Parse(str string) (*Label, error) { func Parse(str string) (*Label, error) {
splits := strings.SplitN(str, ":", 3) splits := strings.SplitN(str, ":", 3)
label := &Label{ label := &Label{
@ -33,13 +36,14 @@ 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("unsupported schema: %s", label.Schema) return nil, fmt.Errorf("不支持的标签: %s", label.Schema)
} }
return label, nil return label, nil
} }
type Labels []*Label type Labels []*Label
// RequireDocker 检查 Labels 是否需要 Docker。
func (l Labels) RequireDocker() bool { func (l Labels) RequireDocker() bool {
for _, label := range l { for _, label := range l {
if label.Schema == SchemeDocker { if label.Schema == SchemeDocker {
@ -49,18 +53,18 @@ func (l Labels) RequireDocker() bool {
return false return false
} }
// PickPlatform 根据 runsOn 列表选择一个平台。
func (l Labels) PickPlatform(runsOn []string) string { func (l Labels) PickPlatform(runsOn []string) string {
platforms := make(map[string]string, len(l)) platforms := make(map[string]string, len(l))
for _, label := range l { for _, label := range l {
switch label.Schema { switch label.Schema {
case SchemeDocker: case SchemeDocker:
// "//" will be ignored // 忽略 "//"
// TODO maybe we should use 'ubuntu-18.04:docker:node:16-buster' instead
platforms[label.Name] = strings.TrimPrefix(label.Arg, "//") platforms[label.Name] = strings.TrimPrefix(label.Arg, "//")
case SchemeHost: case SchemeHost:
platforms[label.Name] = "-self-hosted" platforms[label.Name] = "-self-hosted"
default: default:
// It should not happen, because Parse has checked it. // 这不应该发生,因为 Parse 已经检查过了。
continue continue
} }
} }
@ -70,19 +74,20 @@ func (l Labels) PickPlatform(runsOn []string) string {
} }
} }
// TODO: support multiple labels // TODO: 支持多个标签
// like: // 例如:
// ["ubuntu-22.04"] => "ubuntu:22.04" // ["debian-12"] => "debian:12"
// ["with-gpu"] => "linux:with-gpu" // ["with-gpu"] => "linux:with-gpu"
// ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu" // ["debian-12", "with-gpu"] => "debian:12_with-gpu"
// return default. // 返回默认值。
// So the runner receives a task with a label that the runner doesn't have, // 因此,当运行器收到一个它没有的标签的任务时,
// it happens when the user have edited the label of the runner in the web UI. // 这发生在用户在 Web UI 中编辑了运行器的标签时。
// TODO: it may be not correct, what if the runner is used as host mode only? // TODO: 这可能不正确,如果运行器仅作为主机模式使用呢?
return "node:16-bullseye" return "lcr.loongnix.cn/library/debian:latest"
} }
// Names 返回所有标签的名称。
func (l Labels) Names() []string { func (l Labels) Names() []string {
names := make([]string, 0, len(l)) names := make([]string, 0, len(l))
for _, label := range l { for _, label := range l {
@ -91,6 +96,7 @@ func (l Labels) Names() []string {
return names return names
} }
// ToStrings 将 Labels 转换为字符串数组。
func (l Labels) ToStrings() []string { func (l Labels) ToStrings() []string {
ls := make([]string, 0, len(l)) ls := make([]string, 0, len(l))
for _, label := range l { for _, label := range l {

View File

@ -10,41 +10,41 @@ 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
wantErr bool wantErr bool
}{ }{
{ {
args: "ubuntu:docker://node:18", args: "debian:docker://node:18",
want: &Label{ want: &Label{
Name: "ubuntu", Name: "debian",
Schema: "docker", Schema: "docker",
Arg: "//node:18", Arg: "//node:18",
}, },
wantErr: false, wantErr: false,
}, },
{ {
args: "ubuntu:host", args: "aosc:host",
want: &Label{ want: &Label{
Name: "ubuntu", Name: "aosc",
Schema: "host", Schema: "host",
Arg: "", Arg: "",
}, },
wantErr: false, wantErr: false,
}, },
{ {
args: "ubuntu", args: "aosc",
want: &Label{ want: &Label{
Name: "ubuntu", Name: "aosc",
Schema: "host", Schema: "host",
Arg: "", Arg: "",
}, },
wantErr: false, wantErr: false,
}, },
{ {
args: "ubuntu:vm:ubuntu-18.04", args: "debian:vm:debian12",
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },

View File

@ -1,5 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Reporter 组件是负责任务执行状态报告和日志记录的核心模块
package report package report
@ -12,41 +13,44 @@ import (
"time" "time"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
retry "github.com/avast/retry-go/v4" "connectrpc.com/connect"
"github.com/bufbuild/connect-go" "github.com/avast/retry-go/v4"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"gitea.com/gitea/act_runner/internal/pkg/client" "git.whlug.cn/LAA/loong_runner/internal/pkg/client"
) )
type Reporter struct { type Reporter struct {
ctx context.Context ctx context.Context // 上下文,用于控制生命周期
cancel context.CancelFunc cancel context.CancelFunc // 取消函数
closed bool // 是否已关闭
client client.Client // Gitea 客户端
clientM sync.Mutex // 客户端访问互斥锁
closed bool logOffset int // 日志偏移量
client client.Client logRows []*runnerv1.LogRow // 日志行缓存
clientM sync.Mutex logReplacer *strings.Replacer // 日志内容替换器
oldnew []string // 需要替换的敏感信息
logOffset int state *runnerv1.TaskState // 任务状态
logRows []*runnerv1.LogRow stateMu sync.RWMutex // 状态访问读写锁
logReplacer *strings.Replacer outputs sync.Map // 输出参数存储
oldnew []string
state *runnerv1.TaskState debugOutputEnabled bool // 是否启用调试输出
stateMu sync.RWMutex stopCommandEndToken string // 停止命令结束标记
outputs sync.Map
debugOutputEnabled bool
stopCommandEndToken string
} }
// NewReporter 构造函数 初始化日志脱敏规则、状态等信息
func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task) *Reporter { func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task) *Reporter {
var oldnew []string var oldnew []string
if v := task.Context.Fields["token"].GetStringValue(); v != "" { if v := task.Context.Fields["token"].GetStringValue(); v != "" {
oldnew = append(oldnew, v, "***") oldnew = append(oldnew, v, "***")
} }
if v := task.Context.Fields["gitea_runtime_token"].GetStringValue(); v != "" {
oldnew = append(oldnew, v, "***")
}
for _, v := range task.Secrets { for _, v := range task.Secrets {
oldnew = append(oldnew, v, "***") oldnew = append(oldnew, v, "***")
} }
@ -69,6 +73,7 @@ func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.C
return rv return rv
} }
// ResetSteps 重置任务状态中的步骤状态数组,为即将开始的新任务做准备
func (r *Reporter) ResetSteps(l int) { func (r *Reporter) ResetSteps(l int) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@ -79,10 +84,12 @@ func (r *Reporter) ResetSteps(l int) {
} }
} }
// Levels 返回所有支持的日志级别(通常是为了兼容 logger 接口)
func (r *Reporter) Levels() []log.Level { func (r *Reporter) Levels() []log.Level {
return log.AllLevels return log.AllLevels
} }
// appendIfNotNil 通用函数,如果传入指针不为空,则将其添加到切片中。
func appendIfNotNil[T any](s []*T, v *T) []*T { func appendIfNotNil[T any](s []*T, v *T) []*T {
if v != nil { if v != nil {
return append(s, v) return append(s, v)
@ -90,6 +97,7 @@ func appendIfNotNil[T any](s []*T, v *T) []*T {
return s return s
} }
// 接收日志条目,处理日志内容,并更新任务状态。
func (r *Reporter) Fire(entry *log.Entry) error { func (r *Reporter) Fire(entry *log.Entry) error {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@ -111,6 +119,9 @@ func (r *Reporter) Fire(entry *log.Entry) error {
for _, s := range r.state.Steps { for _, s := range r.state.Steps {
if s.Result == runnerv1.Result_RESULT_UNSPECIFIED { if s.Result == runnerv1.Result_RESULT_UNSPECIFIED {
s.Result = runnerv1.Result_RESULT_CANCELLED s.Result = runnerv1.Result_RESULT_CANCELLED
if jobResult == runnerv1.Result_RESULT_SKIPPED {
s.Result = runnerv1.Result_RESULT_SKIPPED
}
} }
} }
} }
@ -163,6 +174,7 @@ func (r *Reporter) Fire(entry *log.Entry) error {
return nil return nil
} }
// RunDaemon 定时运行后台任务,定期将缓存中的日志和状态上报给 Gitea
func (r *Reporter) RunDaemon() { func (r *Reporter) RunDaemon() {
if r.closed { if r.closed {
return return
@ -177,6 +189,7 @@ func (r *Reporter) RunDaemon() {
time.AfterFunc(time.Second, r.RunDaemon) time.AfterFunc(time.Second, r.RunDaemon)
} }
// 自定义日志记录方法,格式化输出并保存到 logRows 中
func (r *Reporter) Logf(format string, a ...interface{}) { func (r *Reporter) Logf(format string, a ...interface{}) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
@ -184,6 +197,7 @@ func (r *Reporter) Logf(format string, a ...interface{}) {
r.logf(format, a...) r.logf(format, a...)
} }
// 内部使用的日志记录方法,仅在任务执行期间记录日志
func (r *Reporter) logf(format string, a ...interface{}) { func (r *Reporter) logf(format string, a ...interface{}) {
if !r.duringSteps() { if !r.duringSteps() {
r.logRows = append(r.logRows, &runnerv1.LogRow{ r.logRows = append(r.logRows, &runnerv1.LogRow{
@ -193,18 +207,19 @@ func (r *Reporter) logf(format string, a ...interface{}) {
} }
} }
// SetOutputs 设置输出参数,过滤无效或超限的键值对
func (r *Reporter) SetOutputs(outputs map[string]string) { func (r *Reporter) SetOutputs(outputs map[string]string) {
r.stateMu.Lock() r.stateMu.Lock()
defer r.stateMu.Unlock() defer r.stateMu.Unlock()
for k, v := range outputs { for k, v := range outputs {
if len(k) > 255 { if len(k) > 255 {
r.logf("ignore output because the key is too long: %q", k) r.logf("忽略超长键值对的键: %q", k)
continue continue
} }
if l := len(v); l > 1024*1024 { if l := len(v); l > 1024*1024 {
log.Println("ignore output because the value is too long:", k, l) log.Println("忽略超长键值对的值:", k, l)
r.logf("ignore output because the value %q is too long: %d", k, l) r.logf("忽略超长键值对的值 %q: %d 字节", k, l)
} }
if _, ok := r.outputs.Load(k); ok { if _, ok := r.outputs.Load(k); ok {
continue continue
@ -213,6 +228,7 @@ func (r *Reporter) SetOutputs(outputs map[string]string) {
} }
} }
// Close 关闭 reporter,确保最后一批日志和最终状态被上报
func (r *Reporter) Close(lastWords string) error { func (r *Reporter) Close(lastWords string) error {
r.closed = true r.closed = true
@ -248,6 +264,7 @@ func (r *Reporter) Close(lastWords string) error {
}, retry.Context(r.ctx)) }, retry.Context(r.ctx))
} }
// ReportLog 将当前缓存的日志批量上报到 Gitea
func (r *Reporter) ReportLog(noMore bool) error { func (r *Reporter) ReportLog(noMore bool) error {
r.clientM.Lock() r.clientM.Lock()
defer r.clientM.Unlock() defer r.clientM.Unlock()
@ -268,7 +285,7 @@ func (r *Reporter) ReportLog(noMore bool) error {
ack := int(resp.Msg.AckIndex) ack := int(resp.Msg.AckIndex)
if ack < r.logOffset { if ack < r.logOffset {
return fmt.Errorf("submitted logs are lost") return fmt.Errorf("已提交的日志丢失了")
} }
r.stateMu.Lock() r.stateMu.Lock()
@ -277,12 +294,13 @@ func (r *Reporter) ReportLog(noMore bool) error {
r.stateMu.Unlock() r.stateMu.Unlock()
if noMore && ack < r.logOffset+len(rows) { if noMore && ack < r.logOffset+len(rows) {
return fmt.Errorf("not all logs are submitted") return fmt.Errorf("并非所有日志都已提交")
} }
return nil return nil
} }
// ReportState 将当前任务状态上报到 Gitea
func (r *Reporter) ReportState() error { func (r *Reporter) ReportState() error {
r.clientM.Lock() r.clientM.Lock()
defer r.clientM.Unlock() defer r.clientM.Unlock()
@ -323,12 +341,13 @@ func (r *Reporter) ReportState() error {
return true return true
}) })
if len(noSent) > 0 { if len(noSent) > 0 {
return fmt.Errorf("there are still outputs that have not been sent: %v", noSent) return fmt.Errorf("仍有一些输出尚未发送: %v", noSent)
} }
return nil return nil
} }
// duringSteps 判断当前是否正在执行某个步骤
func (r *Reporter) duringSteps() bool { func (r *Reporter) duringSteps() bool {
if steps := r.state.Steps; len(steps) == 0 { if steps := r.state.Steps; len(steps) == 0 {
return false return false
@ -347,11 +366,12 @@ var stringToResult = map[string]runnerv1.Result{
"cancelled": runnerv1.Result_RESULT_CANCELLED, "cancelled": runnerv1.Result_RESULT_CANCELLED,
} }
// parseResult 将字符串或 Stringer 类型的结果解析为 runnerv1.Result 类型
func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) { func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) {
str := "" str := ""
if v, ok := result.(string); ok { // for jobResult if v, ok := result.(string); ok { // 对于作业结果
str = v str = v
} else if v, ok := result.(fmt.Stringer); ok { // for stepResult } else if v, ok := result.(fmt.Stringer); ok { // 对于步骤结果
str = v.String() str = v.String()
} }
@ -359,8 +379,10 @@ func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) {
return ret, ok return ret, ok
} }
// 匹配 GitHub Actions/Gitea Actions 特殊命令,如 ::set-output::, ::add-mask:: 等
var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`) var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`)
// handleCommand 处理日志中的特殊指令命令(如 ::add-mask::),根据命令执行对应操作
func (r *Reporter) handleCommand(originalContent, command, parameters, value string) *string { func (r *Reporter) handleCommand(originalContent, command, parameters, value string) *string {
if r.stopCommandEndToken != "" && command != r.stopCommandEndToken { if r.stopCommandEndToken != "" && command != r.stopCommandEndToken {
return &originalContent return &originalContent
@ -377,20 +399,19 @@ func (r *Reporter) handleCommand(originalContent, command, parameters, value str
return nil return nil
case "notice": case "notice":
// Not implemented yet, so just return the original content. // 尚未实现,因此仅返回原始内容。
return &originalContent return &originalContent
case "warning": case "warning":
// Not implemented yet, so just return the original content. // 尚未实现,因此仅返回原始内容。
return &originalContent return &originalContent
case "error": case "error":
// Not implemented yet, so just return the original content. // 尚未实现,因此仅返回原始内容。
return &originalContent return &originalContent
case "group": case "group":
// Returning the original content, because I think the frontend // 返回原始内容,因为我认为前端在渲染输出时会使用它。
// will use it when rendering the output.
return &originalContent return &originalContent
case "endgroup": case "endgroup":
// Ditto // 同上
return &originalContent return &originalContent
case "stop-commands": case "stop-commands":
r.stopCommandEndToken = value r.stopCommandEndToken = value
@ -402,6 +423,7 @@ func (r *Reporter) handleCommand(originalContent, command, parameters, value str
return &originalContent return &originalContent
} }
// parseLogRow 将日志条目转换为 LogRow 结构,用于后续上报
func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow { func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow {
content := strings.TrimRightFunc(entry.Message, func(r rune) bool { return r == '\r' || r == '\n' }) content := strings.TrimRightFunc(entry.Message, func(r rune) bool { return r == '\r' || r == '\n' })
@ -418,10 +440,11 @@ func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow {
return &runnerv1.LogRow{ return &runnerv1.LogRow{
Time: timestamppb.New(entry.Time), Time: timestamppb.New(entry.Time),
Content: content, Content: strings.ToValidUTF8(content, "?"),
} }
} }
// addMask 将指定字符串加入脱敏列表,之后所有日志中出现该字符串都会被替换成 ***
func (r *Reporter) addMask(msg string) { func (r *Reporter) addMask(msg string) {
r.oldnew = append(r.oldnew, msg, "***") r.oldnew = append(r.oldnew, msg, "***")
r.logReplacer = strings.NewReplacer(r.oldnew...) r.logReplacer = strings.NewReplacer(r.oldnew...)

View File

@ -9,132 +9,143 @@ import (
"testing" "testing"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
connect_go "github.com/bufbuild/connect-go" connect_go "connectrpc.com/connect"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"gitea.com/gitea/act_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

@ -3,7 +3,7 @@
package ver package ver
// go build -ldflags "-X gitea.com/gitea/act_runner/internal/pkg/ver.version=1.2.3" // go build -ldflags "-X git.whlug.cn/LAA/loong_runner/internal/pkg/ver.version=1.2.3"
var version = "dev" var version = "dev"
func Version() string { func Version() string {

View File

@ -8,12 +8,12 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"gitea.com/gitea/act_runner/internal/app/cmd" "git.whlug.cn/LAA/loong_runner/internal/app/cmd"
) )
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)
} }

6
renovate.json5 Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>gitea/renovate-config"
]
}

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
# wait for docker daemon
while ! nc -z localhost 2376 </dev/null; do
echo 'waiting for docker daemon...'
sleep 5
done
. /opt/act/run.sh

View File

@ -1,30 +1,49 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 如果 /data 目录不存在,则创建它
if [[ ! -d /data ]]; then if [[ ! -d /data ]]; then
mkdir -p /data mkdir -p /data
fi fi
cd /data cd /data
# 运行器状态文件,默认为 '.runner'
RUNNER_STATE_FILE=${RUNNER_STATE_FILE:-'.runner'}
CONFIG_ARG="" CONFIG_ARG=""
# 如果设置了 CONFIG_FILE,则添加配置文件参数
if [[ ! -z "${CONFIG_FILE}" ]]; then if [[ ! -z "${CONFIG_FILE}" ]]; then
CONFIG_ARG="--config ${CONFIG_FILE}" CONFIG_ARG="--config ${CONFIG_FILE}"
fi fi
EXTRA_ARGS="" EXTRA_ARGS=""
# 如果设置了 GITEA_RUNNER_LABELS,则添加标签参数
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
# Use the same ENV variable names as https://github.com/vegardit/docker-gitea-act-runner # 如果没有设置令牌,可以从文件中读取令牌,例如从 Docker Secret
if [[ -z "${GITEA_RUNNER_REGISTRATION_TOKEN}" ]] && [[ -f "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE}" ]]; then
GITEA_RUNNER_REGISTRATION_TOKEN=$(cat "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE}")
fi
if [[ ! -s .runner ]]; then # 使用与 https://github.com/vegardit/docker-gitea-act-runner 相同的 ENV 变量名称
test -f "$RUNNER_STATE_FILE" || echo "$RUNNER_STATE_FILE 丢失或不是常规文件"
# 如果运行器状态文件不存在或为空
if [[ ! -s "$RUNNER_STATE_FILE" ]]; then
try=$((try + 1)) try=$((try + 1))
success=0 success=0
# The point of this loop is to make it simple, when running both act_runner and gitea in docker, # 此循环的目的是使其简单,当在 docker 中同时运行 act_runner gitea 时,
# for the act_runner to wait a moment for gitea to become available before erroring out. Within # 使 act_runner 在出错之前等待一段时间以等待 gitea 变为可用。在单个 docker-compose 的上下文中,
# the context of a single docker-compose, something similar could be done via healthchecks, but # 可以通过健康检查做类似的事情,但这更灵活。
# this is more flexible.
while [[ $success -eq 0 ]] && [[ $try -lt ${GITEA_MAX_REG_ATTEMPTS:-10} ]]; do while [[ $success -eq 0 ]] && [[ $try -lt ${GITEA_MAX_REG_ATTEMPTS:-10} ]]; do
act_runner register \ act_runner register \
--instance "${GITEA_INSTANCE_URL}" \ --instance "${GITEA_INSTANCE_URL}" \
@ -32,17 +51,20 @@ if [[ ! -s .runner ]]; then
--name "${GITEA_RUNNER_NAME:-`hostname`}" \ --name "${GITEA_RUNNER_NAME:-`hostname`}" \
${CONFIG_ARG} ${EXTRA_ARGS} --no-interactive 2>&1 | tee /tmp/reg.log ${CONFIG_ARG} ${EXTRA_ARGS} --no-interactive 2>&1 | tee /tmp/reg.log
# 检查输出中是否有成功注册的消息
cat /tmp/reg.log | grep 'Runner registered successfully' > /dev/null cat /tmp/reg.log | grep 'Runner registered successfully' > /dev/null
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
echo "SUCCESS" echo "成功"
success=1 success=1
else else
echo "Waiting to retry ..." echo "等待重试..."
sleep 5 sleep 5
fi fi
done done
fi fi
# Prevent reading the token from the act_runner process # 防止从 act_runner 进程中读取令牌
unset GITEA_RUNNER_REGISTRATION_TOKEN unset GITEA_RUNNER_REGISTRATION_TOKEN
unset GITEA_RUNNER_REGISTRATION_TOKEN_FILE
act_runner daemon ${CONFIG_ARG} # 启动 act_runner 守护进程
exec act_runner daemon ${CONFIG_ARG} ${RUN_ARGS}

3
scripts/s6/act_runner/finish Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec s6-svscanctl -t /etc/s6

5
scripts/s6/act_runner/run Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
s6-svwait -U /etc/s6/docker
exec run.sh

6
scripts/s6/docker/data/check Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
if ! docker info &> /dev/null; then
echo "等待Docker守护进程启动……"
exit 1
fi

4
scripts/s6/docker/finish Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
exec s6-svscanctl -t /etc/s6

View File

@ -0,0 +1 @@
3

3
scripts/s6/docker/run Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec s6-notifyoncheck dockerd-entrypoint.sh

View File

@ -1,13 +0,0 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
[program:dockerd]
command=/usr/local/bin/dockerd-entrypoint.sh
[program:act_runner]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
command=/opt/act/rootless.sh