mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-13 03:13:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea7d39690 | ||
|
|
861d351845 | ||
|
|
cce8543d06 | ||
|
|
75643645f0 | ||
|
|
dff63b3ecc | ||
|
|
a5d9fe9651 | ||
|
|
d607f3b342 | ||
|
|
5e59402fb2 | ||
|
|
dfeb463904 | ||
|
|
594c9ade7c | ||
|
|
2a4d56c650 | ||
|
|
a22119cf88 | ||
|
|
b68ecf2580 | ||
|
|
d1434237c2 | ||
|
|
35c65e2b14 | ||
|
|
c45a4e6d32 | ||
|
|
68d9fc45c9 | ||
|
|
b1c873a66b | ||
|
|
1d6e7879c8 | ||
|
|
13dc9386fe | ||
|
|
8e6b3be96a |
No files matched your search
+1
-1
@@ -40,7 +40,7 @@ cpu.out
|
||||
*.db
|
||||
*.log
|
||||
|
||||
/act_runner
|
||||
/gitea-runner
|
||||
/debug
|
||||
|
||||
/bin
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Echo the tag
|
||||
run: echo "${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}"
|
||||
run: echo "${{ env.DOCKER_ORG }}/runner:nightly${{ matrix.variant.tag_suffix }}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -82,4 +82,4 @@ jobs:
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}
|
||||
${{ env.DOCKER_ORG }}/runner:nightly${{ matrix.variant.tag_suffix }}
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
go-version-file: "go.mod"
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
uses: crazy-max/ghaction-import-gpg@v7
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
@@ -71,17 +71,12 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Repo Meta
|
||||
id: repo_meta
|
||||
run: |
|
||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: "Docker meta"
|
||||
id: docker_meta
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKER_ORG }}/${{ steps.repo_meta.outputs.REPO_NAME }}
|
||||
${{ env.DOCKER_ORG }}/runner
|
||||
tags: |
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
name: checks
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -17,4 +19,4 @@ jobs:
|
||||
- name: build
|
||||
run: make build
|
||||
- name: test
|
||||
run: make test
|
||||
run: make test
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/act_runner
|
||||
/gitea-runner
|
||||
.env
|
||||
.runner
|
||||
coverage.txt
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ formatters:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(gitea.com/gitea/act_runner)
|
||||
- prefix(gitea.com/gitea/runner)
|
||||
- blank
|
||||
- default
|
||||
gofumpt:
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
version: 2
|
||||
|
||||
project_name: gitea-runner
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
@@ -63,7 +65,7 @@ builds:
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X gitea.com/gitea/act_runner/internal/pkg/ver.version={{ .Summary }}
|
||||
- -s -w -X gitea.com/gitea/runner/internal/pkg/ver.version={{ .Summary }}
|
||||
binary: >-
|
||||
{{ .ProjectName }}-
|
||||
{{- .Version }}-
|
||||
@@ -86,7 +88,7 @@ blobs:
|
||||
provider: s3
|
||||
bucket: "{{ .Env.S3_BUCKET }}"
|
||||
region: "{{ .Env.S3_REGION }}"
|
||||
directory: "act_runner/{{.Version}}"
|
||||
directory: "gitea-runner/{{.Version}}"
|
||||
extra_files:
|
||||
- glob: ./**.xz
|
||||
- glob: ./**.sha256
|
||||
|
||||
+7
-7
@@ -9,19 +9,19 @@ RUN apk add --no-cache make git
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-}
|
||||
|
||||
COPY . /opt/src/act_runner
|
||||
WORKDIR /opt/src/act_runner
|
||||
COPY . /opt/src/runner
|
||||
WORKDIR /opt/src/runner
|
||||
|
||||
RUN make clean && make build
|
||||
|
||||
### DIND VARIANT
|
||||
#
|
||||
#
|
||||
FROM docker:28-dind AS dind
|
||||
FROM docker:29-dind AS dind
|
||||
|
||||
RUN apk add --no-cache s6 bash git tzdata
|
||||
|
||||
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
|
||||
COPY --from=builder /opt/src/runner/gitea-runner /usr/local/bin/gitea-runner
|
||||
COPY scripts/run.sh /usr/local/bin/run.sh
|
||||
COPY scripts/s6 /etc/s6
|
||||
|
||||
@@ -32,12 +32,12 @@ ENTRYPOINT ["s6-svscan","/etc/s6"]
|
||||
### DIND-ROOTLESS VARIANT
|
||||
#
|
||||
#
|
||||
FROM docker:28-dind-rootless AS dind-rootless
|
||||
FROM docker:29-dind-rootless AS dind-rootless
|
||||
|
||||
USER root
|
||||
RUN apk add --no-cache s6 bash git tzdata
|
||||
|
||||
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
|
||||
COPY --from=builder /opt/src/runner/gitea-runner /usr/local/bin/gitea-runner
|
||||
COPY scripts/run.sh /usr/local/bin/run.sh
|
||||
COPY scripts/s6 /etc/s6
|
||||
|
||||
@@ -56,7 +56,7 @@ ENTRYPOINT ["s6-svscan","/etc/s6"]
|
||||
FROM alpine AS basic
|
||||
RUN apk add --no-cache tini bash git tzdata
|
||||
|
||||
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
|
||||
COPY --from=builder /opt/src/runner/gitea-runner /usr/local/bin/gitea-runner
|
||||
COPY scripts/run.sh /usr/local/bin/run.sh
|
||||
|
||||
VOLUME /data
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DIST := dist
|
||||
EXECUTABLE := act_runner
|
||||
EXECUTABLE := gitea-runner
|
||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
@@ -13,7 +13,7 @@ DARWIN_ARCHS ?= darwin-12/amd64,darwin-12/arm64
|
||||
WINDOWS_ARCHS ?= windows/amd64
|
||||
GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "generated.*")
|
||||
|
||||
DOCKER_IMAGE ?= gitea/act_runner
|
||||
DOCKER_IMAGE ?= gitea/runner
|
||||
DOCKER_TAG ?= nightly
|
||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
||||
@@ -67,7 +67,7 @@ else
|
||||
endif
|
||||
|
||||
TAGS ?=
|
||||
LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=v$(RELASE_VERSION)"
|
||||
LDFLAGS ?= -X "gitea.com/gitea/runner/internal/pkg/ver.version=v$(RELASE_VERSION)"
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
@@ -86,7 +86,7 @@ go-check:
|
||||
$(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 '.' ' ');))
|
||||
@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 "Gitea Runner requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@@ -140,11 +140,11 @@ test: fmt-check security-check ## test everything
|
||||
@$(GO) test -race -short -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
|
||||
.PHONY: install
|
||||
install: $(GOFILES) ## install the act_runner binary via `go install`
|
||||
install: $(GOFILES) ## install the runner binary via `go install`
|
||||
$(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)'
|
||||
|
||||
.PHONY: build
|
||||
build: go-check $(EXECUTABLE) ## build the act_runner binary
|
||||
build: go-check $(EXECUTABLE) ## build the runner binary
|
||||
|
||||
$(EXECUTABLE): $(GOFILES)
|
||||
$(GO) build -v -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# act runner
|
||||
|
||||
Act runner is a runner for Gitea.
|
||||
# Gitea Runner
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -10,7 +8,7 @@ Docker Engine Community version is required for docker mode. To install Docker C
|
||||
|
||||
### Download pre-built binary
|
||||
|
||||
Visit [here](https://dl.gitea.com/act_runner/) and download the right version for your platform.
|
||||
Visit [here](https://dl.gitea.com/gitea-runner/) and download the right version for your platform.
|
||||
|
||||
### Build from source
|
||||
|
||||
@@ -26,8 +24,8 @@ make docker
|
||||
|
||||
## Quickstart
|
||||
|
||||
Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:
|
||||
|
||||
Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:
|
||||
|
||||
```ini
|
||||
[actions]
|
||||
ENABLED=true
|
||||
@@ -36,7 +34,7 @@ ENABLED=true
|
||||
### Register
|
||||
|
||||
```bash
|
||||
./act_runner register
|
||||
./gitea-runner register
|
||||
```
|
||||
|
||||
And you will be asked to input:
|
||||
@@ -68,7 +66,7 @@ INFO Runner registered successfully.
|
||||
You can also register with command line arguments.
|
||||
|
||||
```bash
|
||||
./act_runner register --instance http://192.168.8.8:3000 --token <my_runner_token> --no-interactive
|
||||
./gitea-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.
|
||||
@@ -76,32 +74,69 @@ If the registry succeed, it will run immediately. Next time, you could run the r
|
||||
### Run
|
||||
|
||||
```bash
|
||||
./act_runner daemon
|
||||
./gitea-runner daemon
|
||||
```
|
||||
|
||||
### Run with docker
|
||||
|
||||
```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/runner:nightly
|
||||
```
|
||||
|
||||
Mount a volume on `/data` if you want the registration file and optional config to survive container recreation (see [scripts/run.sh](scripts/run.sh)).
|
||||
|
||||
### 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`.
|
||||
The runner is configured with a YAML file. Generate a starting point (this matches what ships in the tree):
|
||||
|
||||
```bash
|
||||
./act_runner generate-config > config.yaml
|
||||
./gitea-runner generate-config > config.yaml
|
||||
```
|
||||
|
||||
You can specify the configuration file path with `-c`/`--config` argument.
|
||||
Pass it with `-c` / `--config` on any command that loads configuration (`register`, `daemon`, `cache-server`):
|
||||
|
||||
```bash
|
||||
./act_runner -c config.yaml register # register with config file
|
||||
./act_runner -c config.yaml daemon # run with config file
|
||||
./gitea-runner -c config.yaml register
|
||||
./gitea-runner -c config.yaml daemon
|
||||
./gitea-runner -c config.yaml cache-server
|
||||
```
|
||||
|
||||
You can read the latest version of the configuration file online at [config.example.yaml](internal/pkg/config/config.example.yaml).
|
||||
Every option is described in [config.example.yaml](internal/pkg/config/config.example.yaml) (the same content `generate-config` prints).
|
||||
|
||||
#### Without a config file
|
||||
|
||||
If you omit `-c`, built-in defaults apply (same as an empty YAML document). A small set of **deprecated** environment variables can still override parts of that default config, but **only when no `-c` path was given**; they are ignored if you use a config file:
|
||||
|
||||
| Variable | Effect |
|
||||
| --- | --- |
|
||||
| `GITEA_DEBUG` | If true, sets log level to `debug` |
|
||||
| `GITEA_TRACE` | If true, sets log level to `trace` |
|
||||
| `GITEA_RUNNER_CAPACITY` | Concurrent jobs (integer) |
|
||||
| `GITEA_RUNNER_FILE` | Registration state file path (default `.runner`) |
|
||||
| `GITEA_RUNNER_ENVIRON` | Extra job env vars as comma-separated `KEY:VALUE` pairs |
|
||||
| `GITEA_RUNNER_ENV_FILE` | Path to an env file merged into job env (same idea as `runner.env_file` in YAML) |
|
||||
|
||||
Prefer a YAML file for all settings.
|
||||
|
||||
#### Registration vs config labels
|
||||
|
||||
If `runner.labels` is set in the YAML file, those labels are used during `register` and the `--labels` CLI flag is ignored.
|
||||
|
||||
#### External cache (`actions/cache`)
|
||||
|
||||
If `cache.external_server` is set, you must set `cache.external_secret` to the same value on this runner and on the standalone cache server. Run the server with `gitea-runner cache-server` using a config that defines `cache.external_secret` (and matching `cache.dir` / host / port as needed). Flags `--dir`, `--host`, and `--port` on `cache-server` override the file.
|
||||
|
||||
#### Official Docker image
|
||||
|
||||
Besides `GITEA_INSTANCE_URL` and `GITEA_RUNNER_REGISTRATION_TOKEN`, the image entrypoint supports optional variables such as `CONFIG_FILE` (passed through as `-c`), `GITEA_RUNNER_LABELS`, `GITEA_RUNNER_EPHEMERAL`, `GITEA_RUNNER_ONCE`, `GITEA_RUNNER_NAME`, `GITEA_MAX_REG_ATTEMPTS`, `RUNNER_STATE_FILE`, and `GITEA_RUNNER_REGISTRATION_TOKEN_FILE`. See [scripts/run.sh](scripts/run.sh) for exact behavior.
|
||||
|
||||
For a fuller container-oriented walkthrough, see [examples/docker](examples/docker/README.md).
|
||||
|
||||
When `container.bind_workdir` is enabled, stale task workspace directories can be cleaned while the runner is idle:
|
||||
- directories older than `runner.workdir_cleanup_age` are removed (default: `24h`; set `0` to disable)
|
||||
- cleanup runs every `runner.idle_cleanup_interval` (default: `10m`; set `0` to disable)
|
||||
- only purely numeric subdirectories under `container.workdir_parent` are treated as task workspaces and may be removed
|
||||
- cleanup assumes `container.workdir_parent` is not shared across multiple runners
|
||||
|
||||
### Example Deployments
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
@@ -969,7 +969,7 @@ func TestHandler_ArtifactSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestHandler_SecretPersistsAcrossRestarts is the property that lets
|
||||
// act_runner cache-server be pointed at via cfg.Cache.ExternalServer: a
|
||||
// gitea-runner cache-server be pointed at via cfg.Cache.ExternalServer: a
|
||||
// restart must not invalidate signed URLs the handler has already issued
|
||||
// (within their expiry window).
|
||||
func TestHandler_SecretPersistsAcrossRestarts(t *testing.T) {
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
# Copied from https://github.com/actions/cache#example-cache-workflow
|
||||
name: Caching Primes
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- run: env
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Primes
|
||||
id: cache-primes
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: prime-numbers
|
||||
key: ${{ runner.os }}-primes-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-primes
|
||||
${{ runner.os }}
|
||||
|
||||
- name: Generate Prime Numbers
|
||||
if: steps.cache-primes.outputs.cache-hit != 'true'
|
||||
run: cat /proc/sys/kernel/random/uuid > prime-numbers
|
||||
|
||||
- name: Use Prime Numbers
|
||||
run: cat prime-numbers
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/act_runner/act/runner"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
"gitea.com/gitea/runner/act/runner"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -260,7 +260,7 @@ func TestArtifactFlow(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
platforms := map[string]string{
|
||||
"ubuntu-latest": "node:16-buster", // Don't use node:16-buster-slim because it doesn't have curl command, which is used in the tests
|
||||
"ubuntu-latest": "node:24-bookworm", // Don't use node:24-bookworm-slim because it doesn't have curl command, which is used in the tests
|
||||
}
|
||||
|
||||
tables := []TestJobFileInfo{
|
||||
|
||||
@@ -117,7 +117,7 @@ func NewParallelExecutor(parallel int, executors ...Executor) Executor {
|
||||
log.Debugf("Worker %d executing task %d", workerID, taskCount)
|
||||
// Recover from panics in executors to avoid crashing the worker
|
||||
// goroutine which would leave the runner process hung.
|
||||
// https://gitea.com/gitea/act_runner/issues/371
|
||||
// https://gitea.com/gitea/runner/issues/371
|
||||
errs <- func() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
@@ -38,9 +38,11 @@ var (
|
||||
ErrNoRepo = errors.New("unable to find git repo")
|
||||
)
|
||||
|
||||
// acquireCloneLock returns an unlock function after locking the per-directory mutex for dir.
|
||||
// Only concurrent operations targeting the same directory are erialized; clones into different directories run in parallel.
|
||||
func acquireCloneLock(dir string) func() {
|
||||
// AcquireCloneLock returns an unlock function after locking the per-directory mutex for dir.
|
||||
// Only concurrent operations targeting the same directory are serialized; clones into different directories run in parallel.
|
||||
// Callers reading files inside dir (e.g. tarring a checked-out action into a job container) must hold this lock too,
|
||||
// otherwise a concurrent NewGitCloneExecutor on the same dir can mutate the worktree mid-read.
|
||||
func AcquireCloneLock(dir string) func() {
|
||||
v, _ := cloneLocks.LoadOrStore(dir, &sync.Mutex{})
|
||||
mu := v.(*sync.Mutex)
|
||||
mu.Lock()
|
||||
@@ -305,10 +307,10 @@ func gitOptions(token string) (fetchOptions git.FetchOptions, pullOptions git.Pu
|
||||
func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
logger.Infof(" \u2601 git clone '%s' # ref=%s", input.URL, input.Ref)
|
||||
logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
|
||||
logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
|
||||
|
||||
defer acquireCloneLock(input.Dir)()
|
||||
defer AcquireCloneLock(input.Dir)()
|
||||
|
||||
refName := plumbing.ReferenceName("refs/heads/" + input.Ref)
|
||||
r, err := CloneIfRequired(ctx, refName, input, logger)
|
||||
|
||||
@@ -310,11 +310,11 @@ func TestAcquireCloneLock(t *testing.T) {
|
||||
t.Run("same directory serializes", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
unlock1 := acquireCloneLock(dir)
|
||||
unlock1 := AcquireCloneLock(dir)
|
||||
|
||||
secondAcquired := make(chan struct{})
|
||||
go func() {
|
||||
unlock := acquireCloneLock(dir)
|
||||
unlock := AcquireCloneLock(dir)
|
||||
close(secondAcquired)
|
||||
unlock()
|
||||
}()
|
||||
@@ -338,12 +338,12 @@ func TestAcquireCloneLock(t *testing.T) {
|
||||
dirA := t.TempDir()
|
||||
dirB := t.TempDir()
|
||||
|
||||
unlockA := acquireCloneLock(dirA)
|
||||
unlockA := AcquireCloneLock(dirA)
|
||||
defer unlockA()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
unlock := acquireCloneLock(dirB)
|
||||
unlock := AcquireCloneLock(dirB)
|
||||
unlock()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
@@ -6,13 +6,21 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
// ExitCodeError reports a non-zero process exit code from a container command.
|
||||
type ExitCodeError int
|
||||
|
||||
func (e ExitCodeError) Error() string {
|
||||
return fmt.Sprintf("Process completed with exit code %d.", int(e))
|
||||
}
|
||||
|
||||
// NewContainerInput the input for the New function
|
||||
type NewContainerInput struct {
|
||||
Image string
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
|
||||
@@ -12,11 +12,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
// github.com/docker/docker/builder/dockerignore is deprecated
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
||||
"github.com/moby/patternmatcher"
|
||||
)
|
||||
@@ -26,9 +25,9 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
if input.Platform != "" {
|
||||
logger.Infof("%sdocker build -t %s --platform %s %s", logPrefix, input.ImageTag, input.Platform, input.ContextDir)
|
||||
logger.Infof("docker build -t %s --platform %s %s", input.ImageTag, input.Platform, input.ContextDir)
|
||||
} else {
|
||||
logger.Infof("%sdocker build -t %s %s", logPrefix, input.ImageTag, input.ContextDir)
|
||||
logger.Infof("docker build -t %s %s", input.ImageTag, input.ContextDir)
|
||||
}
|
||||
if common.Dryrun(ctx) {
|
||||
return nil
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestImageExistsLocally(t *testing.T) {
|
||||
|
||||
// Chose alpine latest because it's so small
|
||||
// maybe we should build an image instead so that tests aren't reliable on dockerhub
|
||||
readerDefault, err := cli.ImagePull(ctx, "node:16-buster-slim", types.ImagePullOptions{
|
||||
readerDefault, err := cli.ImagePull(ctx, "node:24-bookworm-slim", types.ImagePullOptions{
|
||||
Platform: "linux/amd64",
|
||||
})
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
@@ -52,12 +52,12 @@ func TestImageExistsLocally(t *testing.T) {
|
||||
_, err = io.ReadAll(readerDefault)
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
imageDefaultArchExists, err := ImageExistsLocally(ctx, "node:16-buster-slim", "linux/amd64")
|
||||
imageDefaultArchExists, err := ImageExistsLocally(ctx, "node:24-bookworm-slim", "linux/amd64")
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assert.True(t, imageDefaultArchExists)
|
||||
|
||||
// Validate if another architecture platform can be pulled
|
||||
readerArm64, err := cli.ImagePull(ctx, "node:16-buster-slim", types.ImagePullOptions{
|
||||
readerArm64, err := cli.ImagePull(ctx, "node:24-bookworm-slim", types.ImagePullOptions{
|
||||
Platform: "linux/arm64",
|
||||
})
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
@@ -65,7 +65,7 @@ func TestImageExistsLocally(t *testing.T) {
|
||||
_, err = io.ReadAll(readerArm64)
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
imageArm64Exists, err := ImageExistsLocally(ctx, "node:16-buster-slim", "linux/arm64")
|
||||
imageArm64Exists, err := ImageExistsLocally(ctx, "node:24-bookworm-slim", "linux/arm64")
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assert.True(t, imageArm64Exists)
|
||||
}
|
||||
@@ -26,8 +26,6 @@ type dockerMessage struct {
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
const logPrefix = " \U0001F433 "
|
||||
|
||||
func logDockerResponse(logger logrus.FieldLogger, dockerResponse io.ReadCloser, isError bool) error {
|
||||
if dockerResponse == nil {
|
||||
return nil
|
||||
|
||||
@@ -9,7 +9,7 @@ package container
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
logger.Debugf("%sdocker pull %v", logPrefix, input.Image)
|
||||
logger.Debugf("docker pull %v", input.Image)
|
||||
|
||||
if common.Dryrun(ctx) {
|
||||
return nil
|
||||
|
||||
+11
-15
@@ -20,8 +20,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/filecollector"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/filecollector"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
@@ -53,7 +53,7 @@ func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
|
||||
|
||||
func (cr *containerReference) ConnectToNetwork(name string) common.Executor {
|
||||
return common.
|
||||
NewDebugExecutor("%sdocker network connect %s %s", logPrefix, name, cr.input.Name).
|
||||
NewDebugExecutor("docker network connect %s %s", name, cr.input.Name).
|
||||
Then(
|
||||
common.NewPipelineExecutor(
|
||||
cr.connect(),
|
||||
@@ -90,7 +90,7 @@ func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) b
|
||||
|
||||
func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
|
||||
return common.
|
||||
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||
NewInfoExecutor("docker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||
Then(
|
||||
common.NewPipelineExecutor(
|
||||
cr.connect(),
|
||||
@@ -102,7 +102,7 @@ func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
|
||||
|
||||
func (cr *containerReference) Start(attach bool) common.Executor {
|
||||
return common.
|
||||
NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||
NewInfoExecutor("docker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||
Then(
|
||||
common.NewPipelineExecutor(
|
||||
cr.connect(),
|
||||
@@ -125,7 +125,7 @@ func (cr *containerReference) Start(attach bool) common.Executor {
|
||||
|
||||
func (cr *containerReference) Pull(forcePull bool) common.Executor {
|
||||
return common.
|
||||
NewInfoExecutor("%sdocker pull image=%s platform=%s username=%s forcePull=%t", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Username, forcePull).
|
||||
NewInfoExecutor("docker pull image=%s platform=%s username=%s forcePull=%t", cr.input.Image, cr.input.Platform, cr.input.Username, forcePull).
|
||||
Then(
|
||||
NewDockerPullExecutor(NewDockerPullExecutorInput{
|
||||
Image: cr.input.Image,
|
||||
@@ -147,7 +147,7 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.
|
||||
|
||||
func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
|
||||
return common.NewPipelineExecutor(
|
||||
common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath),
|
||||
common.NewInfoExecutor("docker cp src=%s dst=%s", srcPath, destPath),
|
||||
cr.copyDir(destPath, srcPath, useGitIgnore),
|
||||
func(ctx context.Context) error {
|
||||
// If this fails, then folders have wrong permissions on non root container
|
||||
@@ -177,7 +177,7 @@ func (cr *containerReference) UpdateFromImageEnv(env *map[string]string) common.
|
||||
|
||||
func (cr *containerReference) Exec(command []string, env map[string]string, user, workdir string) common.Executor {
|
||||
return common.NewPipelineExecutor(
|
||||
common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir),
|
||||
common.NewInfoExecutor("docker exec cmd=[%s] user=%s workdir=%s", strings.Join(command, " "), user, workdir),
|
||||
cr.connect(),
|
||||
cr.find(),
|
||||
cr.exec(command, env, user, workdir),
|
||||
@@ -633,14 +633,10 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo
|
||||
return fmt.Errorf("failed to inspect exec: %w", err)
|
||||
}
|
||||
|
||||
switch inspectResp.ExitCode {
|
||||
case 0:
|
||||
if inspectResp.ExitCode == 0 {
|
||||
return nil
|
||||
case 127:
|
||||
return fmt.Errorf("exitcode '%d': command not found, please refer to https://github.com/nektos/act/issues/107 for more information", inspectResp.ExitCode)
|
||||
default:
|
||||
return fmt.Errorf("exitcode '%d': failure", inspectResp.ExitCode)
|
||||
}
|
||||
return ExitCodeError(inspectResp.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,7 +926,7 @@ func (cr *containerReference) wait() common.Executor {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("exit with `FAILURE`: %v", statusCode)
|
||||
return ExitCodeError(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
@@ -85,6 +86,11 @@ func (m *mockDockerClient) ContainerExecInspect(ctx context.Context, execID stri
|
||||
return args.Get(0).(types.ContainerExecInspect), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockDockerClient) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
args := m.Called(ctx, containerID, condition)
|
||||
return args.Get(0).(<-chan container.WaitResponse), args.Get(1).(<-chan error)
|
||||
}
|
||||
|
||||
func (m *mockDockerClient) CopyToContainer(ctx context.Context, id, path string, content io.Reader, options types.CopyToContainerOptions) error {
|
||||
args := m.Called(ctx, id, path, content, options)
|
||||
return args.Error(0)
|
||||
@@ -174,12 +180,43 @@ func TestDockerExecFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
err := cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
||||
assert.Error(t, err, "exit with `FAILURE`: 1") //nolint:testifylint // pre-existing issue from nektos/act
|
||||
var exitErr ExitCodeError
|
||||
require.ErrorAs(t, err, &exitErr)
|
||||
assert.Equal(t, ExitCodeError(1), exitErr)
|
||||
assert.Equal(t, "Process completed with exit code 1.", err.Error())
|
||||
|
||||
conn.AssertExpectations(t)
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestDockerWaitFailure(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
statusCh := make(chan container.WaitResponse, 1)
|
||||
statusCh <- container.WaitResponse{StatusCode: 2}
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
client := &mockDockerClient{}
|
||||
client.On("ContainerWait", ctx, "123", container.WaitConditionNotRunning).
|
||||
Return((<-chan container.WaitResponse)(statusCh), (<-chan error)(errCh))
|
||||
|
||||
cr := &containerReference{
|
||||
id: "123",
|
||||
cli: client,
|
||||
input: &NewContainerInput{
|
||||
Image: "image",
|
||||
},
|
||||
}
|
||||
|
||||
err := cr.wait()(ctx)
|
||||
var exitErr ExitCodeError
|
||||
require.ErrorAs(t, err, &exitErr)
|
||||
assert.Equal(t, ExitCodeError(2), exitErr)
|
||||
assert.Equal(t, "Process completed with exit code 2.", err.Error())
|
||||
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestDockerCopyTarStream(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -9,7 +9,7 @@ package container
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
@@ -42,7 +42,7 @@ func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executo
|
||||
func removeExecutor(volume string, force bool) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
logger.Debugf("%sdocker volume rm %s", logPrefix, volume)
|
||||
logger.Debugf("docker volume rm %s", volume)
|
||||
|
||||
if common.Dryrun(ctx) {
|
||||
return nil
|
||||
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/filecollector"
|
||||
"gitea.com/gitea/act_runner/act/lookpath"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/filecollector"
|
||||
"gitea.com/gitea/runner/act/lookpath"
|
||||
|
||||
"github.com/go-git/go-billy/v5/helper/polyfill"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
@@ -34,9 +36,15 @@ type HostEnvironment struct {
|
||||
TmpDir string
|
||||
ToolCache string
|
||||
Workdir string
|
||||
ActPath string
|
||||
CleanUp func()
|
||||
StdOut io.Writer
|
||||
// BindWorkdir is true when the app runner mounts the workspace on the host and
|
||||
// deletes the task directory after the job; host teardown must not remove Workdir.
|
||||
BindWorkdir bool
|
||||
ActPath string
|
||||
CleanUp func()
|
||||
StdOut io.Writer
|
||||
|
||||
mu sync.Mutex
|
||||
runningPIDs map[int]struct{}
|
||||
}
|
||||
|
||||
func (e *HostEnvironment) Create(_, _ []string) common.Executor {
|
||||
@@ -344,8 +352,30 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
|
||||
if ppty != nil {
|
||||
go writeKeepAlive(ppty)
|
||||
}
|
||||
err = cmd.Run()
|
||||
// Split Start/Wait so the PID can be registered before the process can exit;
|
||||
// cmd.Run() would block until exit, by which time the PID may have been reused.
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Process != nil {
|
||||
e.mu.Lock()
|
||||
if e.runningPIDs == nil {
|
||||
e.runningPIDs = map[int]struct{}{}
|
||||
}
|
||||
e.runningPIDs[cmd.Process.Pid] = struct{}{}
|
||||
e.mu.Unlock()
|
||||
defer func(pid int) {
|
||||
e.mu.Lock()
|
||||
delete(e.runningPIDs, pid)
|
||||
e.mu.Unlock()
|
||||
}(cmd.Process.Pid)
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
return ExitCodeError(exitErr.ExitCode())
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tty != nil {
|
||||
@@ -385,12 +415,83 @@ func (e *HostEnvironment) UpdateFromEnv(srcPath string, env *map[string]string)
|
||||
return parseEnvFile(e, srcPath, env)
|
||||
}
|
||||
|
||||
func removePathWithRetry(ctx context.Context, path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
attempts := 1
|
||||
delay := time.Duration(0)
|
||||
if runtime.GOOS == "windows" {
|
||||
attempts = 5
|
||||
delay = 200 * time.Millisecond
|
||||
}
|
||||
var lastErr error
|
||||
for i := 0; i < attempts; i++ {
|
||||
if i > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
lastErr = os.RemoveAll(path)
|
||||
if lastErr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (e *HostEnvironment) terminateRunningProcesses(ctx context.Context) {
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
e.mu.Lock()
|
||||
pids := make([]int, 0, len(e.runningPIDs))
|
||||
for pid := range e.runningPIDs {
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
if len(pids) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
logger := common.Logger(ctx)
|
||||
for _, pid := range pids {
|
||||
// Best-effort: forcibly terminate process tree to release file handles
|
||||
// so that workspace cleanup can succeed on Windows.
|
||||
cmd := exec.CommandContext(ctx, "taskkill", "/PID", strconv.Itoa(pid), "/T", "/F")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logger.Debugf("taskkill failed for pid=%d: %v output=%s", pid, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *HostEnvironment) Remove() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
// Ensure any lingering child processes are ended before attempting
|
||||
// to remove the workspace (Windows file locks otherwise prevent cleanup).
|
||||
e.terminateRunningProcesses(ctx)
|
||||
|
||||
// Only removes per-job misc state. Must not remove the cache/toolcache root.
|
||||
if e.CleanUp != nil {
|
||||
e.CleanUp()
|
||||
}
|
||||
return os.RemoveAll(e.Path)
|
||||
logger := common.Logger(ctx)
|
||||
var errs []error
|
||||
if err := removePathWithRetry(ctx, e.Path); err != nil {
|
||||
logger.Warnf("failed to remove host misc state %s: %v", e.Path, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if !e.BindWorkdir && e.Workdir != "" {
|
||||
if err := removePathWithRetry(ctx, e.Workdir); err != nil {
|
||||
logger.Warnf("failed to remove host workspace %s: %v", e.Workdir, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,14 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Type assert HostEnvironment implements ExecutionsEnvironment
|
||||
@@ -69,3 +74,76 @@ func TestGetContainerArchive(t *testing.T) {
|
||||
_, err = reader.Next()
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
}
|
||||
|
||||
func TestHostEnvironmentExecExitCode(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("uses POSIX shell")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
ctx := context.Background()
|
||||
e := &HostEnvironment{
|
||||
Path: filepath.Join(dir, "path"),
|
||||
TmpDir: filepath.Join(dir, "tmp"),
|
||||
ToolCache: filepath.Join(dir, "tool_cache"),
|
||||
ActPath: filepath.Join(dir, "act_path"),
|
||||
StdOut: io.Discard,
|
||||
Workdir: filepath.Join(dir, "path"),
|
||||
}
|
||||
for _, p := range []string{e.Path, e.TmpDir, e.ToolCache, e.ActPath} {
|
||||
assert.NoError(t, os.MkdirAll(p, 0o700)) //nolint:testifylint // test setup
|
||||
}
|
||||
|
||||
err := e.Exec([]string{"sh", "-c", "exit 3"}, map[string]string{"PATH": os.Getenv("PATH")}, "", "")(ctx)
|
||||
var exitErr ExitCodeError
|
||||
require.ErrorAs(t, err, &exitErr)
|
||||
assert.Equal(t, ExitCodeError(3), exitErr)
|
||||
assert.Equal(t, "Process completed with exit code 3.", err.Error())
|
||||
}
|
||||
|
||||
func TestHostEnvironmentRemoveCleansWorkdir(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
ctx := common.WithLogger(context.Background(), logrus.NewEntry(logger))
|
||||
base := t.TempDir()
|
||||
miscRoot := filepath.Join(base, "misc")
|
||||
path := filepath.Join(miscRoot, "hostexecutor")
|
||||
require.NoError(t, os.MkdirAll(path, 0o700))
|
||||
workdir := filepath.Join(base, "workspace", "owner", "repo")
|
||||
require.NoError(t, os.MkdirAll(workdir, 0o700))
|
||||
|
||||
e := &HostEnvironment{
|
||||
Path: path,
|
||||
Workdir: workdir,
|
||||
BindWorkdir: false,
|
||||
CleanUp: func() {
|
||||
_ = os.RemoveAll(miscRoot)
|
||||
},
|
||||
StdOut: os.Stdout,
|
||||
}
|
||||
require.NoError(t, e.Remove()(ctx))
|
||||
_, err := os.Stat(workdir)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestHostEnvironmentRemoveSkipsWorkdirWhenBindWorkdir(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
ctx := common.WithLogger(context.Background(), logrus.NewEntry(logger))
|
||||
base := t.TempDir()
|
||||
miscRoot := filepath.Join(base, "misc")
|
||||
path := filepath.Join(miscRoot, "hostexecutor")
|
||||
require.NoError(t, os.MkdirAll(path, 0o700))
|
||||
workdir := filepath.Join(base, "workspace", "123", "owner", "repo")
|
||||
require.NoError(t, os.MkdirAll(workdir, 0o700))
|
||||
|
||||
e := &HostEnvironment{
|
||||
Path: path,
|
||||
Workdir: workdir,
|
||||
BindWorkdir: true,
|
||||
CleanUp: func() {
|
||||
_ = os.RemoveAll(miscRoot)
|
||||
},
|
||||
StdOut: os.Stdout,
|
||||
}
|
||||
require.NoError(t, e.Remove()(ctx))
|
||||
_, err := os.Stat(workdir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
)
|
||||
|
||||
func parseEnvFile(e Container, srcPath string, env *map[string]string) common.Executor {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
||||
"github.com/rhysd/actionlint"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/rhysd/actionlint"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -73,10 +73,16 @@ func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string
|
||||
if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove any existing destination so we can overwrite read-only files
|
||||
// (e.g. git pack files at mode 0444 trip EACCES on macOS and "Access is
|
||||
// denied" on Windows when reopened with O_WRONLY) and so os.Symlink does
|
||||
// not fail with EEXIST. os.Remove clears the Windows read-only attribute
|
||||
// internally; on Unix unlink only needs write permission on the parent.
|
||||
_ = os.Remove(fdestpath)
|
||||
if f == nil {
|
||||
return os.Symlink(linkName, fdestpath)
|
||||
}
|
||||
df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY, fi.Mode())
|
||||
df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fi.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
"github.com/go-git/go-git/v5/plumbing/format/index"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type memoryFs struct {
|
||||
@@ -174,3 +177,47 @@ func TestSymlinks(t *testing.T) {
|
||||
assert.Equal(t, ".env", files["test.env"].Linkname)
|
||||
assert.ErrorIs(t, err, io.EOF, "tar must be read cleanly to EOF")
|
||||
}
|
||||
|
||||
// Regression for https://gitea.com/gitea/runner/issues/876 and /941:
|
||||
// re-copying an action directory must overwrite a pre-existing read-only
|
||||
// file (e.g. a git pack .idx at mode 0444) instead of failing with EACCES
|
||||
// on macOS or "Access is denied" on Windows.
|
||||
func TestCopyCollectorWriteFileOverwritesReadOnlyFile(t *testing.T) {
|
||||
dst := t.TempDir()
|
||||
target := filepath.Join(dst, "sub", "pack.idx")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(target), 0o755))
|
||||
require.NoError(t, os.WriteFile(target, []byte("old"), 0o444))
|
||||
|
||||
src := filepath.Join(t.TempDir(), "pack.idx")
|
||||
require.NoError(t, os.WriteFile(src, []byte("new"), 0o444))
|
||||
fi, err := os.Stat(src)
|
||||
require.NoError(t, err)
|
||||
|
||||
cc := &CopyCollector{DstDir: dst}
|
||||
require.NoError(t, cc.WriteFile("sub/pack.idx", fi, "", strings.NewReader("new")))
|
||||
|
||||
got, err := os.ReadFile(target)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "new", string(got))
|
||||
}
|
||||
|
||||
// Without the destination removal, os.Symlink fails with EEXIST when the
|
||||
// path already holds a regular file from an earlier copy of the action.
|
||||
func TestCopyCollectorWriteFileOverwritesFileWithSymlink(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("creating symlinks requires elevated privileges on Windows")
|
||||
}
|
||||
dst := t.TempDir()
|
||||
target := filepath.Join(dst, "link")
|
||||
require.NoError(t, os.WriteFile(target, []byte("stale"), 0o644))
|
||||
|
||||
fi, err := os.Lstat(target)
|
||||
require.NoError(t, err)
|
||||
|
||||
cc := &CopyCollector{DstDir: dst}
|
||||
require.NoError(t, cc.WriteFile("link", fi, "target", nil))
|
||||
|
||||
resolved, err := os.Readlink(target)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "target", resolved)
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/common/git"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common/git"
|
||||
)
|
||||
|
||||
type GithubContext struct {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ jobs:
|
||||
with-volumes:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:16-buster-slim
|
||||
image: node:24-bookworm-slim
|
||||
volumes:
|
||||
- my_docker_volume:/path/to/volume
|
||||
- /path/to/nonexist/directory
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
+24
-8
@@ -18,9 +18,10 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/container"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common/git"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/kballard/go-shellquote"
|
||||
)
|
||||
@@ -44,6 +45,11 @@ type runAction func(step actionStep, actionDir string, remoteAction *remoteActio
|
||||
//go:embed res/trampoline.js
|
||||
var trampoline embed.FS
|
||||
|
||||
var (
|
||||
ContainerImageExistsLocally = container.ImageExistsLocally
|
||||
ContainerNewDockerBuildExecutor = container.NewDockerBuildExecutor
|
||||
)
|
||||
|
||||
func readActionImpl(ctx context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
|
||||
logger := common.Logger(ctx)
|
||||
allErrors := []error{}
|
||||
@@ -148,6 +154,8 @@ func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actio
|
||||
return rc.JobContainer.CopyTarStream(ctx, containerActionDirCopy, ta)
|
||||
}
|
||||
|
||||
defer git.AcquireCloneLock(actionDir)()
|
||||
|
||||
if err := removeGitIgnore(ctx, actionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -197,7 +205,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||
if remoteAction == nil {
|
||||
location = containerActionDir
|
||||
}
|
||||
return execAsDocker(ctx, step, actionName, location, remoteAction == nil)
|
||||
return execAsDocker(ctx, step, actionName, actionDir, location, remoteAction == nil)
|
||||
case x.IsComposite():
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
@@ -265,7 +273,7 @@ func removeGitIgnore(ctx context.Context, directory string) error {
|
||||
}
|
||||
|
||||
// TODO: break out parts of function to reduce complexicity
|
||||
func execAsDocker(ctx context.Context, step actionStep, actionName, basedir string, localAction bool) error {
|
||||
func execAsDocker(ctx context.Context, step actionStep, actionName, actionDir, basedir string, localAction bool) error {
|
||||
logger := common.Logger(ctx)
|
||||
rc := step.getRunContext()
|
||||
action := step.getActionModel()
|
||||
@@ -284,12 +292,12 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, basedir stri
|
||||
image = strings.ToLower(image)
|
||||
contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
|
||||
|
||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
||||
anyArchExists, err := ContainerImageExistsLocally(ctx, image, "any")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||
correctArchExists, err := ContainerImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -321,13 +329,21 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, basedir stri
|
||||
}
|
||||
defer buildContext.Close()
|
||||
}
|
||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
prepImage = ContainerNewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
ContextDir: contextDir,
|
||||
Dockerfile: fileName,
|
||||
ImageTag: image,
|
||||
BuildContext: buildContext,
|
||||
Platform: rc.Config.ContainerArchitecture,
|
||||
})
|
||||
if buildContext == nil {
|
||||
// Held across the whole build: the daemon drains contextDir lazily.
|
||||
inner := prepImage
|
||||
prepImage = func(ctx context.Context) error {
|
||||
defer git.AcquireCloneLock(actionDir)()
|
||||
return inner(ctx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
)
|
||||
|
||||
func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step actionStep) map[string]string {
|
||||
|
||||
+156
-1
@@ -9,9 +9,14 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common/git"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -252,3 +257,153 @@ func TestActionRunner(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeCopyToActionDirHoldsCloneLock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
actionDir := t.TempDir()
|
||||
|
||||
releaseCopy := make(chan struct{})
|
||||
release := sync.OnceFunc(func() { close(releaseCopy) })
|
||||
defer release()
|
||||
|
||||
copyEntered := make(chan struct{})
|
||||
|
||||
cm := &containerMock{}
|
||||
cm.On("CopyDir", "/var/run/act/actions/", actionDir+"/", false).Return(func(ctx context.Context) error {
|
||||
close(copyEntered)
|
||||
<-releaseCopy
|
||||
return nil
|
||||
})
|
||||
|
||||
step := &stepActionRemote{
|
||||
Step: &model.Step{Uses: "remote/action@v1"},
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{},
|
||||
JobContainer: cm,
|
||||
},
|
||||
}
|
||||
|
||||
copyDone := make(chan error, 1)
|
||||
go func() {
|
||||
copyDone <- maybeCopyToActionDir(ctx, step, actionDir, "", "/var/run/act/actions/")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-copyEntered:
|
||||
case err := <-copyDone:
|
||||
t.Fatalf("maybeCopyToActionDir returned before CopyDir was entered: %v", err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("CopyDir was not entered within 1 second")
|
||||
}
|
||||
|
||||
peerAcquired := make(chan struct{})
|
||||
go func() {
|
||||
unlock := git.AcquireCloneLock(actionDir)
|
||||
close(peerAcquired)
|
||||
unlock()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-peerAcquired:
|
||||
t.Fatal("peer AcquireCloneLock returned while CopyDir was running")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
|
||||
release()
|
||||
|
||||
select {
|
||||
case err := <-copyDone:
|
||||
if err != nil {
|
||||
t.Fatalf("maybeCopyToActionDir returned error: %v", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("maybeCopyToActionDir did not return after CopyDir was unblocked")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-peerAcquired:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("peer AcquireCloneLock did not proceed after lock released")
|
||||
}
|
||||
|
||||
cm.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestExecAsDockerHoldsCloneLockForRemoteUncached(t *testing.T) {
|
||||
actionDir := t.TempDir()
|
||||
|
||||
unlockOnce := sync.OnceFunc(git.AcquireCloneLock(actionDir))
|
||||
defer unlockOnce()
|
||||
|
||||
innerEntered := make(chan struct{})
|
||||
releaseInner := make(chan struct{})
|
||||
releaseOnce := sync.OnceFunc(func() { close(releaseInner) })
|
||||
defer releaseOnce()
|
||||
|
||||
origImageExists := ContainerImageExistsLocally
|
||||
ContainerImageExistsLocally = func(_ context.Context, _, _ string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
defer func() { ContainerImageExistsLocally = origImageExists }()
|
||||
|
||||
origBuildExec := ContainerNewDockerBuildExecutor
|
||||
ContainerNewDockerBuildExecutor = func(_ container.NewDockerBuildExecutorInput) common.Executor {
|
||||
return func(_ context.Context) error {
|
||||
close(innerEntered)
|
||||
<-releaseInner
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer func() { ContainerNewDockerBuildExecutor = origBuildExec }()
|
||||
|
||||
step := &stepActionRemote{
|
||||
Step: &model.Step{ID: "1", Uses: "remote/action@v1", With: map[string]string{}},
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{},
|
||||
Run: &model.Run{
|
||||
JobID: "1",
|
||||
Workflow: &model.Workflow{
|
||||
Name: "wf",
|
||||
Jobs: map[string]*model.Job{"1": {}},
|
||||
},
|
||||
},
|
||||
JobContainer: &containerMock{},
|
||||
},
|
||||
action: &model.Action{Runs: model.ActionRuns{Using: "docker", Image: "Dockerfile"}},
|
||||
env: map[string]string{},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- execAsDocker(ctx, step, "test-action", actionDir, actionDir, false) }()
|
||||
|
||||
select {
|
||||
case <-innerEntered:
|
||||
t.Fatal("inner build executor ran before clone lock was released")
|
||||
case err := <-done:
|
||||
t.Fatalf("execAsDocker returned before inner was entered: %v", err)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
|
||||
unlockOnce()
|
||||
|
||||
select {
|
||||
case <-innerEntered:
|
||||
case err := <-done:
|
||||
t.Fatalf("execAsDocker returned without entering inner: %v", err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("inner build executor not entered after lock released")
|
||||
}
|
||||
|
||||
cancel()
|
||||
releaseOnce()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("execAsDocker did not return after inner was released and ctx was canceled")
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
)
|
||||
|
||||
var commandPatternGA *regexp.Regexp
|
||||
@@ -120,7 +120,7 @@ func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string,
|
||||
|
||||
result, ok := rc.StepResults[stepID]
|
||||
if !ok {
|
||||
logger.Infof(" \U00002757 no outputs used step '%s'", stepID)
|
||||
logger.Infof("No outputs registered for step '%s'", stepID)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/container"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/container"
|
||||
"gitea.com/gitea/act_runner/act/exprparser"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
"gitea.com/gitea/runner/act/exprparser"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
_ "embed"
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/exprparser"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/exprparser"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
yaml "go.yaml.in/yaml/v4"
|
||||
|
||||
+26
-16
@@ -10,8 +10,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/common"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
)
|
||||
|
||||
type jobInfo interface {
|
||||
@@ -24,6 +24,13 @@ type jobInfo interface {
|
||||
result(result string)
|
||||
}
|
||||
|
||||
// reportStepError emits the GitHub Actions ##[error] annotation and records
|
||||
// the error against the job so the job is reported as failed.
|
||||
func reportStepError(ctx context.Context, err error) {
|
||||
common.Logger(ctx).Errorf("##[error]%v", err)
|
||||
common.SetJobError(ctx, err)
|
||||
}
|
||||
|
||||
func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor {
|
||||
steps := make([]common.Executor, 0)
|
||||
preSteps := make([]common.Executor, 0)
|
||||
@@ -32,7 +39,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
steps = append(steps, func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
if len(info.matrix()) > 0 {
|
||||
logger.Infof("\U0001F9EA Matrix: %v", info.matrix())
|
||||
logger.Infof("Matrix: %v", info.matrix())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -75,33 +82,36 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
|
||||
preExec := step.pre()
|
||||
preSteps = append(preSteps, useStepLogger(rc, stepModel, stepStagePre, func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
preErr := preExec(ctx)
|
||||
if preErr != nil {
|
||||
logger.Errorf("%v", preErr)
|
||||
common.SetJobError(ctx, preErr)
|
||||
reportStepError(ctx, preErr)
|
||||
} else if ctx.Err() != nil {
|
||||
logger.Errorf("%v", ctx.Err())
|
||||
common.SetJobError(ctx, ctx.Err())
|
||||
reportStepError(ctx, ctx.Err())
|
||||
}
|
||||
return preErr
|
||||
}))
|
||||
|
||||
stepExec := step.main()
|
||||
steps = append(steps, useStepLogger(rc, stepModel, stepStageMain, func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
err := stepExec(ctx)
|
||||
if err != nil {
|
||||
logger.Errorf("%v", err)
|
||||
common.SetJobError(ctx, err)
|
||||
reportStepError(ctx, err)
|
||||
} else if ctx.Err() != nil {
|
||||
logger.Errorf("%v", ctx.Err())
|
||||
common.SetJobError(ctx, ctx.Err())
|
||||
reportStepError(ctx, ctx.Err())
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
postExec := useStepLogger(rc, stepModel, stepStagePost, step.post())
|
||||
postFn := step.post()
|
||||
postExec := useStepLogger(rc, stepModel, stepStagePost, func(ctx context.Context) error {
|
||||
err := postFn(ctx)
|
||||
if err != nil {
|
||||
reportStepError(ctx, err)
|
||||
} else if ctx.Err() != nil {
|
||||
reportStepError(ctx, ctx.Err())
|
||||
}
|
||||
return err
|
||||
})
|
||||
if postExecutor != nil {
|
||||
// run the post executor in reverse order
|
||||
postExecutor = postExec.Finally(postExecutor)
|
||||
@@ -136,7 +146,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
// if !rc.IsHostEnv(ctx) && rc.Config.ContainerNetworkMode == "" {
|
||||
// // clean network in docker mode only
|
||||
// // if the value of `ContainerNetworkMode` is empty string,
|
||||
// // it means that the network to which containers are connecting is created by `act_runner`,
|
||||
// // it means that the network to which containers are connecting is created by `runner`,
|
||||
// // so, we should remove the network at last.
|
||||
// networkName, _ := rc.networkName()
|
||||
// logger.Infof("Cleaning up network for job %s, and network name is: %s", rc.JobName, networkName)
|
||||
@@ -196,7 +206,7 @@ func setJobResult(ctx context.Context, info jobInfo, rc *RunContext, success boo
|
||||
jobResultMessage = "failed"
|
||||
}
|
||||
|
||||
logger.WithField("jobResult", jobResult).Infof("\U0001F3C1 Job %s", jobResultMessage)
|
||||
logger.WithField("jobResult", jobResult).Infof("Job %s", jobResultMessage)
|
||||
}
|
||||
|
||||
func setJobOutputs(ctx context.Context, rc *RunContext) {
|
||||
|
||||
Loaded 50 of 1168 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user