8 Commits

Author SHA1 Message Date
Jason Song
45bfe0a9b2 Accept empty file as config (#241)
Close #240

`yaml.Decoder.Decode` will return EOF when the root node is nil , see https://github.com/go-yaml/yaml/blob/v3/yaml.go#L125

While `yaml.Unmarshal` will accept it, see https://github.com/go-yaml/yaml/blob/v3/yaml.go#L162

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/241
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
2023-06-15 05:40:37 +00:00
Jason Song
316534996a Build docker image gitea/act_runner/x.y.z-dind-rootless (#239)
Follow #208

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/239
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
2023-06-15 05:27:35 +00:00
sillyguodong
67b1363d25 Support changing labels (#201)
Implement proposal: https://github.com/go-gitea/gitea/issues/24540

Related:
- Protocol: https://gitea.com/gitea/actions-proto-def/pulls/9
- Gitea side: https://github.com/go-gitea/gitea/pull/24806

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/201
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-06-15 03:59:15 +00:00
Tomasz Duda
946c41cf4f Improve run.sh to handle empty labels and log to stdout (#237)
1. Print logs on standard output
2. Don't add labels if GITEA_RUNNER_LABELS not set

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/237
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Tomasz Duda <tomaszduda23@gmail.com>
Co-committed-by: Tomasz Duda <tomaszduda23@gmail.com>
2023-06-13 04:09:11 +00:00
ccureau
341d49a24d implement act_runner rootless image (#208)
This PR creates a rootless Docker image that runs both `dockerd` and `act_runner` using `supervisord`.  It has been tested locally for a few days and seems stable.

Co-authored-by: ccureau <ccureau@noreply.gitea.io>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/208
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: ccureau <ccureau@noreply.gitea.com>
Co-committed-by: ccureau <ccureau@noreply.gitea.com>
2023-06-12 06:35:27 +00:00
MarkusLoeffler01
b21d476aca Exit with Code 1 if registering a runner fails (#228)
### It's a "simple dirty fix" and I don't have any experiences with Go, so if this doesn't match your coding compliance, please adjust the code as needed

I'm using bash scripts to register a token

`./act_runner/act_runner register --no-interactive --name runner$number --instance http://localhost:3000 --token $token`

But when a token is invalid, the command still returns 0, which is not practical for automation.
A simple non-zero return would be more convenient for power users and developers.

Co-authored-by: Markus Löffler <markus.loeffler@netcare.de>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/228
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: MarkusLoeffler01 <markusloeffler01@noreply.gitea.com>
Co-committed-by: MarkusLoeffler01 <markusloeffler01@noreply.gitea.com>
2023-06-09 17:34:23 +00:00
Jason Song
a29307a9d9 Remove hadolint and improve Dockerfile (#234)
Replace #190

See:

- https://gitea.com/gitea/act_runner/pulls/190#issuecomment-741196
- https://gitea.com/gitea/act_runner/pulls/208#issuecomment-741049

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/234
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: silverwind <silverwind@noreply.gitea.com>
Reviewed-by: delvh <dev.lh@web.de>
2023-06-09 02:50:30 +00:00
a1012112796
4bfbfec477 fix defaultActionsUrls config for exec (#233)
follow #200

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/233
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-committed-by: a1012112796 <1012112796@qq.com>
2023-06-08 04:26:52 +00:00
23 changed files with 311 additions and 65 deletions

View File

@@ -58,11 +58,6 @@ jobs:
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- name: dockerfile lint check
uses: https://github.com/hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@@ -95,3 +90,16 @@ 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
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile.rootless
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind-rootless

View File

@@ -69,11 +69,6 @@ jobs:
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- name: dockerfile lint check
uses: https://github.com/hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@@ -106,3 +101,18 @@ jobs:
tags: | tags: |
${{ 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
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile.rootless
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind-rootless
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind-rootless

View File

@@ -36,7 +36,3 @@ jobs:
run: make build run: make build
- name: test - name: test
run: make test run: make test
- name: dockerfile lint check
uses: https://github.com/hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile

View File

@@ -1,18 +1,16 @@
FROM golang:1.20-alpine3.17 as builder FROM golang:1.20-alpine3.18 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=4.3-r1 git=2.38.5-r0 RUN apk add --no-cache make git
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.17 FROM alpine:3.18
RUN apk add --no-cache \ RUN apk add --no-cache git bash tini
git=2.38.5-r0 bash=5.2.15-r0 tini=0.19.0-r1 \
&& rm -rf /var/cache/apk/*
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 run.sh /opt/act/run.sh COPY scripts/run.sh /opt/act/run.sh
ENTRYPOINT ["/sbin/tini","--","/opt/act/run.sh"] ENTRYPOINT ["/sbin/tini","--","/opt/act/run.sh"]

24
Dockerfile.rootless Normal file
View File

@@ -0,0 +1,24 @@
FROM golang:1.20-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

@@ -19,6 +19,7 @@ GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "genera
DOCKER_IMAGE ?= gitea/act_runner DOCKER_IMAGE ?= gitea/act_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
ifneq ($(shell uname), Darwin) ifneq ($(shell uname), Darwin)
EXTLDFLAGS = -extldflags "-static" $(null) EXTLDFLAGS = -extldflags "-static" $(null)
@@ -69,7 +70,7 @@ GO_PACKAGES_TO_VET ?= $(filter-out gitea.com/gitea/act_runner/internal/pkg/clien
TAGS ?= TAGS ?=
LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=$(RELASE_VERSION)" LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=v$(RELASE_VERSION)"
all: build all: build
@@ -169,6 +170,7 @@ 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

@@ -6,3 +6,6 @@ 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. 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.
- [`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.

View File

@@ -0,0 +1,68 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: act-runner-vol
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
---
apiVersion: v1
data:
token: << runner registration token goes here >>
kind: Secret
metadata:
name: runner-secret
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: act-runner
name: act-runner
spec:
replicas: 1
selector:
matchLabels:
app: act-runner
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: act-runner
spec:
restartPolicy: Always
volumes:
- name: runner-data
persistentVolumeClaim:
claimName: act-runner-vol
containers:
- name: runner
image: gitea/act_runner:nightly-dind-rootless
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"]
env:
- name: DOCKER_HOST
value: tcp://localhost:2376
- name: DOCKER_CERT_PATH
value: /certs/client
- name: DOCKER_TLS_VERIFY
value: "1"
- name: GITEA_INSTANCE_URL
value: http://gitea-http.gitea.svc.cluster.local:3000
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: runner-secret
key: token
securityContext:
privileged: true
volumeMounts:
- name: runner-data
mountPath: /data

2
go.mod
View File

@@ -3,7 +3,7 @@ module gitea.com/gitea/act_runner
go 1.20 go 1.20
require ( require (
code.gitea.io/actions-proto-go v0.2.1 code.gitea.io/actions-proto-go v0.3.0
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5
github.com/avast/retry-go/v4 v4.3.1 github.com/avast/retry-go/v4 v4.3.1
github.com/bufbuild/connect-go v1.3.1 github.com/bufbuild/connect-go v1.3.1

4
go.sum
View File

@@ -1,5 +1,5 @@
code.gitea.io/actions-proto-go v0.2.1 h1:ToMN/8thz2q10TuCq8dL2d8mI+/pWpJcHCvG+TELwa0= code.gitea.io/actions-proto-go v0.3.0 h1:9Tvg8+TaaCXPKi6EnWl9vVgs2VZsj1Cs5afnsHa4AmM=
code.gitea.io/actions-proto-go v0.2.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= code.gitea.io/actions-proto-go v0.3.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A=
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 h1:daBEK2GQeqGikJESctP5Cu1i33z5ztAD4kyQWiw185M= code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 h1:daBEK2GQeqGikJESctP5Cu1i33z5ztAD4kyQWiw185M=
code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
gitea.com/gitea/act v0.245.2-0.20230606002131-6ce5c93cc815 h1:u4rHwJLJnH6mej1BjEc4iubwknVeJmRVq9xQP9cAMeQ= gitea.com/gitea/act v0.245.2-0.20230606002131-6ce5c93cc815 h1:u4rHwJLJnH6mej1BjEc4iubwknVeJmRVq9xQP9cAMeQ=

View File

@@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/bufbuild/connect-go"
"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"
@@ -43,8 +44,13 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
return fmt.Errorf("failed to load registration file: %w", err) return fmt.Errorf("failed to load registration file: %w", err)
} }
lbls := reg.Labels
if len(cfg.Runner.Labels) > 0 {
lbls = cfg.Runner.Labels
}
ls := labels.Labels{} ls := labels.Labels{}
for _, l := range reg.Labels { 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("ignored invalid label %q", l)
@@ -71,6 +77,24 @@ 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())
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
// Gitea instance is older version. skip declare step.
log.Warn("Because the Gitea instance is an old version, skip declare labels and version.")
} else if err != nil {
log.WithError(err).Error("fail to invoke Declare")
return err
} else {
log.Infof("runner: %s, with version: %s, with labels: %v, declare successfully",
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) poller.Poll(ctx)

View File

@@ -39,7 +39,7 @@ type executeArgs struct {
envs []string envs []string
envfile string envfile string
secrets []string secrets []string
defaultActionsUrl string defaultActionsUrls []string
insecureSecrets bool insecureSecrets bool
privileged bool privileged bool
usernsMode string usernsMode string
@@ -403,10 +403,10 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
NoSkipCheckout: execArgs.noSkipCheckout, NoSkipCheckout: execArgs.noSkipCheckout,
// PresetGitHubContext: preset, // PresetGitHubContext: preset,
// EventJSON: string(eventJSON), // EventJSON: string(eventJSON),
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%s", eventName), ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%s", eventName),
ContainerMaxLifetime: maxLifetime, ContainerMaxLifetime: maxLifetime,
ContainerNetworkMode: container.NetworkMode(execArgs.network), ContainerNetworkMode: container.NetworkMode(execArgs.network),
DefaultActionInstance: execArgs.defaultActionsUrl, DefaultActionsURLs: execArgs.defaultActionsUrls,
PlatformPicker: func(_ []string) string { PlatformPicker: func(_ []string) string {
return execArgs.image return execArgs.image
}, },
@@ -470,7 +470,7 @@ func loadExecCmd(ctx context.Context) *cobra.Command {
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", "", ".", "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.artifactServerAddr, "artifact-server-addr", "", "", "Defines the address where the artifact server listens") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerAddr, "artifact-server-addr", "", "", "Defines the address where the artifact server listens")
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", "Defines the port where the artifact server listens (will only bind to localhost).")
execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsUrl, "default-actions-url", "", "https://gitea.com", "Defines the default url of action instance.") execCmd.PersistentFlags().StringArrayVarP(&execArg.defaultActionsUrls, "default-actions-url", "", []string{"https://gitea.com", "https://github.com"}, "Defines the default url list of action instance.")
execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout") execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout")
execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "enable debug log") execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "enable debug log")
execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun mode") execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun mode")

View File

@@ -85,7 +85,7 @@ const (
StageInputInstance StageInputInstance
StageInputToken StageInputToken
StageInputRunnerName StageInputRunnerName
StageInputCustomLabels StageInputLabels
StageWaitingForRegistration StageWaitingForRegistration
StageExit StageExit
) )
@@ -101,7 +101,7 @@ type registerInputs struct {
InstanceAddr string InstanceAddr string
Token string Token string
RunnerName string RunnerName string
CustomLabels []string Labels []string
} }
func (r *registerInputs) validate() error { func (r *registerInputs) validate() error {
@@ -111,8 +111,8 @@ func (r *registerInputs) validate() error {
if r.Token == "" { if r.Token == "" {
return fmt.Errorf("token is empty") return fmt.Errorf("token is empty")
} }
if len(r.CustomLabels) > 0 { if len(r.Labels) > 0 {
return validateLabels(r.CustomLabels) return validateLabels(r.Labels)
} }
return nil return nil
} }
@@ -126,7 +126,7 @@ func validateLabels(ls []string) error {
return nil return nil
} }
func (r *registerInputs) assignToNext(stage registerStage, value string) registerStage { func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage {
// must set instance address and token. // must set instance address and token.
// if empty, keep current stage. // if empty, keep current stage.
if stage == StageInputInstance || stage == StageInputToken { if stage == StageInputInstance || stage == StageInputToken {
@@ -154,16 +154,33 @@ func (r *registerInputs) assignToNext(stage registerStage, value string) registe
return StageInputRunnerName return StageInputRunnerName
case StageInputRunnerName: case StageInputRunnerName:
r.RunnerName = value r.RunnerName = value
return StageInputCustomLabels // if there are some labels configured in config file, skip input labels stage
case StageInputCustomLabels: if len(cfg.Runner.Labels) > 0 {
r.CustomLabels = defaultLabels ls := make([]string, 0, len(cfg.Runner.Labels))
for _, l := range cfg.Runner.Labels {
_, err := labels.Parse(l)
if err != nil {
log.WithError(err).Warnf("ignored invalid label %q", l)
continue
}
ls = append(ls, l)
}
if len(ls) == 0 {
log.Warn("no valid labels configured in config file, runner may not be able to pick up jobs")
}
r.Labels = ls
return StageWaitingForRegistration
}
return StageInputLabels
case StageInputLabels:
r.Labels = defaultLabels
if value != "" { if value != "" {
r.CustomLabels = strings.Split(value, ",") r.Labels = strings.Split(value, ",")
} }
if validateLabels(r.CustomLabels) != 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("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)")
return StageInputCustomLabels return StageInputLabels
} }
return StageWaitingForRegistration return StageWaitingForRegistration
} }
@@ -192,12 +209,12 @@ func registerInteractive(configFile string) error {
if err != nil { if err != nil {
return err return err
} }
stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString)) 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.CustomLabels) log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels)
if err := doRegister(cfg, inputs); err != nil { if err := doRegister(cfg, inputs); err != nil {
log.Errorf("Failed to register runner: %v", err) return fmt.Errorf("Failed to register runner: %w", err)
} else { } else {
log.Infof("Runner registered successfully.") log.Infof("Runner registered successfully.")
} }
@@ -226,7 +243,7 @@ func printStageHelp(stage registerStage) {
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("Enter the runner name (if set empty, use hostname: %s):\n", hostname)
case StageInputCustomLabels: 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("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):")
case StageWaitingForRegistration: case StageWaitingForRegistration:
log.Infoln("Waiting for registration...") log.Infoln("Waiting for registration...")
@@ -242,12 +259,21 @@ func registerNoInteractive(configFile string, regArgs *registerArgs) error {
InstanceAddr: regArgs.InstanceAddr, InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token, Token: regArgs.Token,
RunnerName: regArgs.RunnerName, RunnerName: regArgs.RunnerName,
CustomLabels: defaultLabels, Labels: defaultLabels,
} }
regArgs.Labels = strings.TrimSpace(regArgs.Labels) regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// command line flag.
if regArgs.Labels != "" { if regArgs.Labels != "" {
inputs.CustomLabels = strings.Split(regArgs.Labels, ",") inputs.Labels = strings.Split(regArgs.Labels, ",")
} }
// specify labels in config file.
if len(cfg.Runner.Labels) > 0 {
if regArgs.Labels != "" {
log.Warn("Labels from command will be ignored, use labels defined in config file.")
}
inputs.Labels = cfg.Runner.Labels
}
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("Runner name is empty, use hostname '%s'.", inputs.RunnerName)
@@ -257,8 +283,7 @@ func registerNoInteractive(configFile string, regArgs *registerArgs) error {
return nil return nil
} }
if err := doRegister(cfg, inputs); err != nil { if err := doRegister(cfg, inputs); err != nil {
log.Errorf("Failed to register runner: %v", err) return fmt.Errorf("Failed to register runner: %w", err)
return nil
} }
log.Infof("Runner registered successfully.") log.Infof("Runner registered successfully.")
return nil return nil
@@ -303,7 +328,7 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
Name: inputs.RunnerName, Name: inputs.RunnerName,
Token: inputs.Token, Token: inputs.Token,
Address: inputs.InstanceAddr, Address: inputs.InstanceAddr,
Labels: inputs.CustomLabels, Labels: inputs.Labels,
} }
ls := make([]string, len(reg.Labels)) ls := make([]string, len(reg.Labels))
@@ -315,7 +340,9 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
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,
AgentLabels: ls, Version: ver.Version(),
AgentLabels: ls, // Could be removed after Gitea 1.20
Labels: ls,
})) }))
if err != nil { if err != nil {
log.WithError(err).Error("poller: cannot register new runner") log.WithError(err).Error("poller: cannot register new runner")

View File

@@ -13,6 +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"
"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"
@@ -224,3 +225,10 @@ func parseDefaultActionsURLs(s string) []string {
} }
return trimmed return trimmed
} }
func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) {
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
Version: ver.Version(),
Labels: labels,
}))
}

View File

@@ -4,7 +4,8 @@
package client 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
VersionHeader = "x-runner-version" VersionHeader = "x-runner-version"
) )

View File

@@ -39,6 +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.
if version != "" { if version != "" {
req.Header().Set(VersionHeader, version) req.Header().Set(VersionHeader, version)
} }

View File

@@ -33,6 +33,32 @@ func (_m *Client) Address() string {
return r0 return r0
} }
// Declare provides a mock function with given fields: _a0, _a1
func (_m *Client) Declare(_a0 context.Context, _a1 *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) {
ret := _m.Called(_a0, _a1)
var r0 *connect.Response[runnerv1.DeclareResponse]
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) *connect.Response[runnerv1.DeclareResponse]); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*connect.Response[runnerv1.DeclareResponse])
}
}
if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FetchTask provides a mock function with given fields: _a0, _a1 // FetchTask provides a mock function with given fields: _a0, _a1
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)

View File

@@ -26,6 +26,11 @@ runner:
fetch_timeout: 5s fetch_timeout: 5s
# The interval for fetching the job from the Gitea instance. # The interval for fetching the job from the Gitea instance.
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"]
# If it's empty when registering, it will ask for inputting labels.
# If it's empty when execute `deamon`, will use labels in `.runner` file.
labels: []
cache: cache:
# Enable cache server to use actions/cache. # Enable cache server to use actions/cache.

View File

@@ -29,6 +29,7 @@ type Runner struct {
Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode. Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode.
FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources. FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources.
FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources. FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources.
Labels []string `yaml:"labels"` // Labels specifies the labels of the runner. Labels are declared on each startup
} }
// Cache represents the configuration for caching. // Cache represents the configuration for caching.
@@ -61,14 +62,12 @@ type Config struct {
func LoadDefault(file string) (*Config, error) { func LoadDefault(file string) (*Config, error) {
cfg := &Config{} cfg := &Config{}
if file != "" { if file != "" {
f, err := os.Open(file) content, err := os.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("open config file %q: %w", file, err)
} }
defer f.Close() if err := yaml.Unmarshal(content, cfg); err != nil {
decoder := yaml.NewDecoder(f) return nil, fmt.Errorf("parse config file %q: %w", file, err)
if err := decoder.Decode(&cfg); err != nil {
return nil, err
} }
} }
compatibleWithOldEnvs(file != "", cfg) compatibleWithOldEnvs(file != "", cfg)

View File

@@ -82,3 +82,26 @@ func (l Labels) PickPlatform(runsOn []string) string {
// TODO: it may be not correct, what if the runner is used as host mode only? // TODO: it may be not correct, what if the runner is used as host mode only?
return "node:16-bullseye" return "node:16-bullseye"
} }
func (l Labels) Names() []string {
names := make([]string, 0, len(l))
for _, label := range l {
names = append(names, label.Name)
}
return names
}
func (l Labels) ToStrings() []string {
ls := make([]string, 0, len(l))
for _, label := range l {
lbl := label.Name
if label.Schema != "" {
lbl += ":" + label.Schema
if label.Arg != "" {
lbl += ":" + label.Arg
}
}
ls = append(ls, lbl)
}
return ls
}

9
scripts/rootless.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/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

@@ -10,6 +10,10 @@ CONFIG_ARG=""
if [[ ! -z "${CONFIG_FILE}" ]]; then if [[ ! -z "${CONFIG_FILE}" ]]; then
CONFIG_ARG="--config ${CONFIG_FILE}" CONFIG_ARG="--config ${CONFIG_FILE}"
fi fi
EXTRA_ARGS=""
if [[ ! -z "${GITEA_RUNNER_LABELS}" ]]; then
EXTRA_ARGS="${EXTRA_ARGS} --labels ${GITEA_RUNNER_LABELS}"
fi
# Use the same ENV variable names as https://github.com/vegardit/docker-gitea-act-runner # Use the same ENV variable names as https://github.com/vegardit/docker-gitea-act-runner
@@ -26,10 +30,7 @@ if [[ ! -s .runner ]]; then
--instance "${GITEA_INSTANCE_URL}" \ --instance "${GITEA_INSTANCE_URL}" \
--token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \ --token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \
--name "${GITEA_RUNNER_NAME:-`hostname`}" \ --name "${GITEA_RUNNER_NAME:-`hostname`}" \
--labels "${GITEA_RUNNER_LABELS}" \ ${CONFIG_ARG} ${EXTRA_ARGS} --no-interactive 2>&1 | tee /tmp/reg.log
${CONFIG_ARG} --no-interactive > /tmp/reg.log 2>&1
cat /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

13
scripts/supervisord.conf Normal file
View File

@@ -0,0 +1,13 @@
[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