9 Commits

Author SHA1 Message Date
vbrandl
7c71c94366 Document persisting /data for docker container (#160)
`/data` must be kept between container restarts.

Co-authored-by: Valentin Brandl <mail@vbrandl.net>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/160
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: vbrandl <vbrandl@noreply.gitea.io>
Co-committed-by: vbrandl <vbrandl@noreply.gitea.io>
2023-04-29 03:05:00 +08:00
appleboy
49d2cb0cb5 ci: improve API usage and test robustness across platforms (#159)
- Add `dir: ./dist/` to `.goreleaser.yaml` builds configuration

fix https://gitea.com/gitea/act_runner/issues/158

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

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/159
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-04-28 23:46:46 +08:00
sillyguodong
85626b6bbd Support configuration variables (#157)
related to: https://gitea.com/gitea/act_runner/issues/127

`act_runner` only needs to pass `vars` from `Gitea` to `act`.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/157
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-04-28 22:06:08 +08:00
Zettat123
35400f76fa Add parent directory for working directory (#154)
Fixes #145

At present, the working directory of a work flow is a path like `/<owner>/<repo>`, so the directory may conflict with system directory like `/usr/bin`. We need to add a parent directory for the working directory.
In this PR, the parent directory is `/workspace` by default and users could configure it by the `workdir_parent` option.

This change doesn't affect the host mode because in host mode the working directory will always be in `$HOME/.cache/act/` directory.

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/154
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2023-04-28 22:03:52 +08:00
Jason Song
0cf31b2d22 Update docker image tag (#153)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/153
2023-04-27 15:02:39 +08:00
Søren L. Hansen
c8cc7b2448 Workflow commands (#149)
Establishes a simple framework for supporting workflow commands.

Fully implements `::add-mask::`, `::debug::`, and `::stop-commands::`.

Addresses #148

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/149
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Søren L. Hansen <sorenisanerd@gmail.com>
Co-committed-by: Søren L. Hansen <sorenisanerd@gmail.com>
2023-04-27 12:32:48 +08:00
Jason Song
3be962cdb3 Rename the download folder from main -> nightly (#152)
Close https://gitea.com/gitea/act_runner/issues/117

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/152
2023-04-27 12:23:15 +08:00
Jason Song
a5edbc9ac4 Release docker images (#151)
Did some tests to make sure it worked.

See https://hub.docker.com/r/gitea/act_runner/tags

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/151
2023-04-27 12:08:41 +08:00
seepine
66bab3d805 ci(actions): add build docker image workflow (#118)
### Add secret
```
DOCKERHUB_TOKEN=xxx
```

### Tag
when tag like `v1.0.0`, it will build multi platform docker image `gitea/act_runner:1.0.0` and `gitea/act_runner:latest`, then push to docker hub

### Use
> volume `/data` save `.runner` config file
> volume `/root/.cache` save actcache and actions cache

```sh
docker run -e GITEA_INSTANCE_URL=***                    \
           -e GITEA_RUNNER_REGISTRATION_TOKEN=***       \
           -e GITEA_RUNNER_NAME=***                     \
           -v /var/run/docker.sock:/var/run/docker.sock \
           -v /root/act_runner/data:/data               \
           -v /root/act_runner/cache:/root/.cache       \
           gitea/act_runner
```
Test join runners success
![image](/attachments/f5287e93-e27c-420f-a3d5-8f9b54bfdbb6)
Test run action success
![image](/attachments/7235af17-f598-4fc8-88b4-d4771b1f07cd)

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/118
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: seepine <seepine@noreply.gitea.io>
Co-committed-by: seepine <seepine@noreply.gitea.io>
2023-04-27 11:32:28 +08:00
9 changed files with 365 additions and 16 deletions

View File

@@ -1,4 +1,4 @@
name: goreleaser name: release-nightly
on: on:
push: push:
@@ -45,3 +45,48 @@ 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 }}
release-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: nightly
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

View File

@@ -1,4 +1,4 @@
name: goreleaser name: release-tag
on: on:
push: push:
@@ -56,3 +56,48 @@ jobs:
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:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

View File

@@ -72,6 +72,7 @@ builds:
hooks: hooks:
post: post:
- cmd: tar -cJf {{ .Path }}.xz {{ .Path }} - cmd: tar -cJf {{ .Path }}.xz {{ .Path }}
dir: ./dist/
env: env:
- XZ_OPT=-9 - XZ_OPT=-9
- cmd: sh .goreleaser.checksum.sh {{ .Path }} - cmd: sh .goreleaser.checksum.sh {{ .Path }}
@@ -101,11 +102,11 @@ snapshot:
name_template: "{{ .Branch }}-devel" name_template: "{{ .Branch }}-devel"
nightly: nightly:
name_template: "{{ .Branch }}" name_template: "nightly"
gitea_urls: gitea_urls:
api: https://gitea.com/api/v1 api: https://gitea.com/api/v1
download: https://gitea.com download: https://gitea.com
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json # yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj # vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@@ -91,5 +91,29 @@ You can specify the configuration file path with `-c`/`--config` argument.
### Run a docker container ### Run a docker container
```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 --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/act_runner:nightly
```
The `/data` directory inside the docker container contains the runner API keys after registration.
It must be persisted, otherwise the runner would try to register again, using the same, now defunct registration token.
### Running in docker-compose
```yml
...
gitea:
image: gitea/gitea
...
runner:
image: gitea/act_runner
restart: always
depends_on:
- gitea
volumes:
- ./data/act_runner:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
- GITEA_INSTANCE_URL=<instance url>
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration token>
``` ```

View File

@@ -166,9 +166,9 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
} }
runnerConfig := &runner.Config{ runnerConfig := &runner.Config{
// On Linux, Workdir will be like "/<owner>/<repo>" // On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
// On Windows, Workdir will be like "\<owner>\<repo>" // On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
Workdir: filepath.FromSlash(string(filepath.Separator) + preset.Repository), Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", r.cfg.Container.WorkdirParent, preset.Repository)),
BindWorkdir: false, BindWorkdir: false,
ReuseContainers: false, ReuseContainers: false,
@@ -190,6 +190,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
Privileged: r.cfg.Container.Privileged, Privileged: r.cfg.Container.Privileged,
DefaultActionInstance: taskContext["gitea_default_actions_url"].GetStringValue(), DefaultActionInstance: taskContext["gitea_default_actions_url"].GetStringValue(),
PlatformPicker: r.labels.PickPlatform, PlatformPicker: r.labels.PickPlatform,
Vars: task.Vars,
} }
rr, err := runner.New(runnerConfig) rr, err := runner.New(runnerConfig)

View File

@@ -48,3 +48,6 @@ container:
privileged: false privileged: false
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). # And other options to be used when the container is started (eg, --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.
workdir_parent:

View File

@@ -34,9 +34,10 @@ type Config struct {
Port uint16 `yaml:"port"` Port uint16 `yaml:"port"`
} `yaml:"cache"` } `yaml:"cache"`
Container struct { Container struct {
NetworkMode string `yaml:"network_mode"` NetworkMode string `yaml:"network_mode"`
Privileged bool `yaml:"privileged"` Privileged bool `yaml:"privileged"`
Options string `yaml:"options"` Options string `yaml:"options"`
WorkdirParent string `yaml:"workdir_parent"`
} `yaml:"container"` } `yaml:"container"`
} }
@@ -94,6 +95,9 @@ func LoadDefault(file string) (*Config, error) {
if cfg.Container.NetworkMode == "" { if cfg.Container.NetworkMode == "" {
cfg.Container.NetworkMode = "bridge" cfg.Container.NetworkMode = "bridge"
} }
if cfg.Container.WorkdirParent == "" {
cfg.Container.WorkdirParent = "workspace"
}
if cfg.Runner.FetchTimeout <= 0 { if cfg.Runner.FetchTimeout <= 0 {
cfg.Runner.FetchTimeout = 5 * time.Second cfg.Runner.FetchTimeout = 5 * time.Second
} }

View File

@@ -6,6 +6,7 @@ package report
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -31,10 +32,14 @@ type Reporter struct {
logOffset int logOffset int
logRows []*runnerv1.LogRow logRows []*runnerv1.LogRow
logReplacer *strings.Replacer logReplacer *strings.Replacer
oldnew []string
state *runnerv1.TaskState state *runnerv1.TaskState
stateMu sync.RWMutex stateMu sync.RWMutex
outputs sync.Map outputs sync.Map
debugOutputEnabled bool
stopCommandEndToken string
} }
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 {
@@ -46,15 +51,22 @@ func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.C
oldnew = append(oldnew, v, "***") oldnew = append(oldnew, v, "***")
} }
return &Reporter{ rv := &Reporter{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
client: client, client: client,
oldnew: oldnew,
logReplacer: strings.NewReplacer(oldnew...), logReplacer: strings.NewReplacer(oldnew...),
state: &runnerv1.TaskState{ state: &runnerv1.TaskState{
Id: task.Id, Id: task.Id,
}, },
} }
if task.Secrets["ACTIONS_STEP_DEBUG"] == "true" {
rv.debugOutputEnabled = true
}
return rv
} }
func (r *Reporter) ResetSteps(l int) { func (r *Reporter) ResetSteps(l int) {
@@ -71,6 +83,13 @@ func (r *Reporter) Levels() []log.Level {
return log.AllLevels return log.AllLevels
} }
func appendIfNotNil[T any](s []*T, v *T) []*T {
if v != nil {
return append(s, v)
}
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()
@@ -97,7 +116,7 @@ func (r *Reporter) Fire(entry *log.Entry) error {
} }
} }
if !r.duringSteps() { if !r.duringSteps() {
r.logRows = append(r.logRows, r.parseLogRow(entry)) r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry))
} }
return nil return nil
} }
@@ -110,7 +129,7 @@ func (r *Reporter) Fire(entry *log.Entry) error {
} }
if step == nil { if step == nil {
if !r.duringSteps() { if !r.duringSteps() {
r.logRows = append(r.logRows, r.parseLogRow(entry)) r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry))
} }
return nil return nil
} }
@@ -124,10 +143,10 @@ func (r *Reporter) Fire(entry *log.Entry) error {
step.LogIndex = int64(r.logOffset + len(r.logRows)) step.LogIndex = int64(r.logOffset + len(r.logRows))
} }
step.LogLength++ step.LogLength++
r.logRows = append(r.logRows, r.parseLogRow(entry)) r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry))
} }
} else if !r.duringSteps() { } else if !r.duringSteps() {
r.logRows = append(r.logRows, r.parseLogRow(entry)) r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry))
} }
if v, ok := entry.Data["stepResult"]; ok { if v, ok := entry.Data["stepResult"]; ok {
if stepResult, ok := r.parseResult(v); ok { if stepResult, ok := r.parseResult(v); ok {
@@ -338,11 +357,70 @@ func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) {
return ret, ok return ret, ok
} }
var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`)
func (r *Reporter) handleCommand(originalContent, command, parameters, value string) *string {
if r.stopCommandEndToken != "" && command != r.stopCommandEndToken {
return &originalContent
}
switch command {
case "add-mask":
r.addMask(value)
return nil
case "debug":
if r.debugOutputEnabled {
return &value
}
return nil
case "notice":
// Not implemented yet, so just return the original content.
return &originalContent
case "warning":
// Not implemented yet, so just return the original content.
return &originalContent
case "error":
// Not implemented yet, so just return the original content.
return &originalContent
case "group":
// Returning the original content, because I think the frontend
// will use it when rendering the output.
return &originalContent
case "endgroup":
// Ditto
return &originalContent
case "stop-commands":
r.stopCommandEndToken = value
return nil
case r.stopCommandEndToken:
r.stopCommandEndToken = ""
return nil
}
return &originalContent
}
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' })
matches := cmdRegex.FindStringSubmatch(content)
if matches != nil {
if output := r.handleCommand(content, matches[1], matches[2], matches[3]); output != nil {
content = *output
} else {
return nil
}
}
content = r.logReplacer.Replace(content) content = r.logReplacer.Replace(content)
return &runnerv1.LogRow{ return &runnerv1.LogRow{
Time: timestamppb.New(entry.Time), Time: timestamppb.New(entry.Time),
Content: content, Content: content,
} }
} }
func (r *Reporter) addMask(msg string) {
r.oldnew = append(r.oldnew, msg, "***")
r.logReplacer = strings.NewReplacer(r.oldnew...)
}

View File

@@ -0,0 +1,148 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package report
import (
"strings"
"testing"
log "github.com/sirupsen/logrus"
"gotest.tools/v3/assert"
)
func TestReporter_parseLogRow(t *testing.T) {
tests := []struct {
name string
debugOutputEnabled bool
args []string
want []string
}{
{
"No command", false,
[]string{"Hello, world!"},
[]string{"Hello, world!"},
},
{
"Add-mask", false,
[]string{
"foo mysecret bar",
"::add-mask::mysecret",
"foo mysecret bar",
},
[]string{
"foo mysecret bar",
"<nil>",
"foo *** bar",
},
},
{
"Debug enabled", true,
[]string{
"::debug::GitHub Actions runtime token access controls",
},
[]string{
"GitHub Actions runtime token access controls",
},
},
{
"Debug not enabled", false,
[]string{
"::debug::GitHub Actions runtime token access controls",
},
[]string{
"<nil>",
},
},
{
"notice", false,
[]string{
"::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
[]string{
"::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
},
{
"warning", false,
[]string{
"::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
[]string{
"::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
},
{
"error", false,
[]string{
"::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
[]string{
"::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work",
},
},
{
"group", false,
[]string{
"::group::",
"::endgroup::",
},
[]string{
"::group::",
"::endgroup::",
},
},
{
"stop-commands", false,
[]string{
"::add-mask::foo",
"::stop-commands::myverycoolstoptoken",
"::add-mask::bar",
"::debug::Stuff",
"myverycoolstoptoken",
"::add-mask::baz",
"::myverycoolstoptoken::",
"::add-mask::wibble",
"foo bar baz wibble",
},
[]string{
"<nil>",
"<nil>",
"::add-mask::bar",
"::debug::Stuff",
"myverycoolstoptoken",
"::add-mask::baz",
"<nil>",
"<nil>",
"*** bar baz ***",
},
},
{
"unknown command", false,
[]string{
"::set-mask::foo",
},
[]string{
"::set-mask::foo",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Reporter{
logReplacer: strings.NewReplacer(),
debugOutputEnabled: tt.debugOutputEnabled,
}
for idx, arg := range tt.args {
rv := r.parseLogRow(&log.Entry{Message: arg})
got := "<nil>"
if rv != nil {
got = rv.Content
}
assert.Equal(t, tt.want[idx], got)
}
})
}
}