mirror of https://code.forgejo.org/forgejo/runner
Refactor to new framework (#98)
- Adjust directory structure ```text ├── internal │ ├── app │ │ ├── artifactcache │ │ ├── cmd │ │ ├── poll │ │ └── run │ └── pkg │ ├── client │ ├── config │ ├── envcheck │ ├── labels │ ├── report │ └── ver └── main.go ``` - New pkg `labels` to parse label - New pkg `report` to report logs to Gitea - Remove pkg `engine`, use `envcheck` to check if docker running. - Rewrite `runtime` to `run` - Rewrite `poller` to `poll` - Simplify some code and remove what's useless. Reviewed-on: https://gitea.com/gitea/act_runner/pulls/98 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Jason Song <i@wolfogre.com> Co-committed-by: Jason Song <i@wolfogre.com>pull/25/head
parent
df3cb60978
commit
220efa69c0
|
@ -58,7 +58,7 @@ builds:
|
||||||
flags:
|
flags:
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X gitea.com/gitea/act_runner/cmd.version={{ .Summary }}
|
- -s -w -X gitea.com/gitea/act_runner/internal/pkg/ver.version={{ .Summary }}
|
||||||
binary: >-
|
binary: >-
|
||||||
{{ .ProjectName }}-
|
{{ .ProjectName }}-
|
||||||
{{- .Version }}-
|
{{- .Version }}-
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -9,7 +9,6 @@ HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
XGO_VERSION := go-1.18.x
|
XGO_VERSION := go-1.18.x
|
||||||
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
|
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
|
||||||
RUNNER_CMD_PACKAGE_PATH := gitea.com/gitea/act_runner/cmd
|
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/arm64
|
||||||
DARWIN_ARCHS ?= darwin-12/amd64,darwin-12/arm64
|
DARWIN_ARCHS ?= darwin-12/amd64,darwin-12/arm64
|
||||||
|
@ -63,7 +62,7 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
TAGS ?=
|
TAGS ?=
|
||||||
LDFLAGS ?= -X "$(RUNNER_CMD_PACKAGE_PATH).version=$(RELASE_VERSION)"
|
LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=$(RELASE_VERSION)"
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
Inspired by:
|
|
||||||
https://github.com/sp-ricard-valverde/github-act-cache-server
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
- Authorization
|
|
||||||
- [Restrictions for accessing a cache](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache)
|
|
||||||
- [Force deleting cache entries](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries)
|
|
136
cmd/daemon.go
136
cmd/daemon.go
|
@ -1,136 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/artifactcache"
|
|
||||||
"gitea.com/gitea/act_runner/client"
|
|
||||||
"gitea.com/gitea/act_runner/config"
|
|
||||||
"gitea.com/gitea/act_runner/engine"
|
|
||||||
"gitea.com/gitea/act_runner/poller"
|
|
||||||
"gitea.com/gitea/act_runner/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
log.Infoln("Starting runner daemon")
|
|
||||||
|
|
||||||
cfg, err := config.LoadDefault(*configFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
initLogging(cfg)
|
|
||||||
|
|
||||||
reg, err := config.LoadRegistration(cfg.Runner.File)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Error("registration file not found, please register the runner first")
|
|
||||||
return err
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("failed to load registration file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// require docker if a runner label uses a docker backend
|
|
||||||
needsDocker := false
|
|
||||||
for _, l := range reg.Labels {
|
|
||||||
_, schema, _, _ := runtime.ParseLabel(l)
|
|
||||||
if schema == "docker" {
|
|
||||||
needsDocker = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsDocker {
|
|
||||||
// try to connect to docker daemon
|
|
||||||
// if failed, exit with error
|
|
||||||
if err := engine.Start(ctx); err != nil {
|
|
||||||
log.WithError(err).Fatalln("failed to connect docker daemon engine")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var g errgroup.Group
|
|
||||||
|
|
||||||
cli := client.New(
|
|
||||||
reg.Address,
|
|
||||||
cfg.Runner.Insecure,
|
|
||||||
reg.UUID,
|
|
||||||
reg.Token,
|
|
||||||
version,
|
|
||||||
)
|
|
||||||
|
|
||||||
runner := &runtime.Runner{
|
|
||||||
Client: cli,
|
|
||||||
Machine: reg.Name,
|
|
||||||
ForgeInstance: reg.Address,
|
|
||||||
Environ: cfg.Runner.Envs,
|
|
||||||
Labels: reg.Labels,
|
|
||||||
Network: cfg.Container.Network,
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
|
|
||||||
if *cfg.Cache.Enabled {
|
|
||||||
if handler, err := artifactcache.NewHandler(cfg.Cache.Dir, cfg.Cache.Host, cfg.Cache.Port); err != nil {
|
|
||||||
log.Errorf("cannot init cache server, it will be disabled: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Infof("cache handler listens on: %v", handler.ExternalURL())
|
|
||||||
runner.CacheHandler = handler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
poller := poller.New(
|
|
||||||
cli,
|
|
||||||
runner.Run,
|
|
||||||
cfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
g.Go(func() error {
|
|
||||||
l := log.WithField("capacity", cfg.Runner.Capacity).
|
|
||||||
WithField("endpoint", reg.Address)
|
|
||||||
l.Infoln("polling the remote server")
|
|
||||||
|
|
||||||
if err := poller.Poll(ctx); err != nil {
|
|
||||||
l.Errorf("poller error: %v", err)
|
|
||||||
}
|
|
||||||
poller.Wait()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
err = g.Wait()
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).
|
|
||||||
Errorln("shutting down the server")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initLogging setup the global logrus logger.
|
|
||||||
func initLogging(cfg *config.Config) {
|
|
||||||
isTerm := isatty.IsTerminal(os.Stdout.Fd())
|
|
||||||
log.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableColors: !isTerm,
|
|
||||||
FullTimestamp: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if l := cfg.Log.Level; l != "" {
|
|
||||||
level, err := log.ParseLevel(l)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).
|
|
||||||
Errorf("invalid log level: %q", l)
|
|
||||||
}
|
|
||||||
if log.GetLevel() != level {
|
|
||||||
log.Infof("log level changed to %v", level)
|
|
||||||
log.SetLevel(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Docker struct {
|
|
||||||
client client.APIClient
|
|
||||||
hidePull bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(opts ...Option) (*Docker, error) {
|
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &Docker{
|
|
||||||
client: cli,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through each option
|
|
||||||
for _, opt := range opts {
|
|
||||||
// Call the option giving the instantiated
|
|
||||||
opt.Apply(srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping pings the Docker daemon.
|
|
||||||
func (e *Docker) Ping(ctx context.Context) error {
|
|
||||||
_, err := e.client.Ping(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Start start docker engine api loop
|
|
||||||
func Start(ctx context.Context) error {
|
|
||||||
engine, err := New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
count := 0
|
|
||||||
for {
|
|
||||||
err := engine.Ping(ctx)
|
|
||||||
if err == context.Canceled {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).
|
|
||||||
Errorln("cannot ping the docker daemon")
|
|
||||||
count++
|
|
||||||
if count == 5 {
|
|
||||||
return fmt.Errorf("retry connect to docker daemon failed: %d times", count)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
} else {
|
|
||||||
log.Infoln("successfully ping the docker daemon")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package engine
|
|
||||||
|
|
||||||
import "github.com/docker/docker/client"
|
|
||||||
|
|
||||||
// An Option configures a mutex.
|
|
||||||
type Option interface {
|
|
||||||
Apply(*Docker)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionFunc is a function that configure a value.
|
|
||||||
type OptionFunc func(*Docker)
|
|
||||||
|
|
||||||
// Apply calls f(option)
|
|
||||||
func (f OptionFunc) Apply(docker *Docker) {
|
|
||||||
f(docker)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithClient set custom client
|
|
||||||
func WithClient(c client.APIClient) Option {
|
|
||||||
return OptionFunc(func(q *Docker) {
|
|
||||||
q.client = c
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHidePull hide pull event.
|
|
||||||
func WithHidePull(v bool) Option {
|
|
||||||
return OptionFunc(func(q *Docker) {
|
|
||||||
q.hidePull = v
|
|
||||||
})
|
|
||||||
}
|
|
8
go.mod
8
go.mod
|
@ -15,10 +15,12 @@ require (
|
||||||
github.com/nektos/act v0.0.0
|
github.com/nektos/act v0.0.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
golang.org/x/sync v0.1.0
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/term v0.6.0
|
golang.org/x/term v0.6.0
|
||||||
|
golang.org/x/time v0.1.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gotest.tools/v3 v3.4.0
|
||||||
modernc.org/sqlite v1.14.2
|
modernc.org/sqlite v1.14.2
|
||||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978
|
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978
|
||||||
xorm.io/xorm v1.3.2
|
xorm.io/xorm v1.3.2
|
||||||
|
@ -33,6 +35,7 @@ require (
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/containerd/containerd v1.6.18 // indirect
|
github.com/containerd/containerd v1.6.18 // indirect
|
||||||
github.com/creack/pty v1.1.18 // indirect
|
github.com/creack/pty v1.1.18 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||||
|
@ -46,6 +49,7 @@ require (
|
||||||
github.com/goccy/go-json v0.8.1 // indirect
|
github.com/goccy/go-json v0.8.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
|
@ -72,6 +76,7 @@ require (
|
||||||
github.com/opencontainers/runc v1.1.3 // indirect
|
github.com/opencontainers/runc v1.1.3 // indirect
|
||||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
github.com/rhysd/actionlint v1.6.23 // indirect
|
github.com/rhysd/actionlint v1.6.23 // indirect
|
||||||
github.com/rivo/uniseg v0.4.3 // indirect
|
github.com/rivo/uniseg v0.4.3 // indirect
|
||||||
|
@ -86,6 +91,7 @@ require (
|
||||||
golang.org/x/crypto v0.2.0 // indirect
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
golang.org/x/mod v0.4.2 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/tools v0.1.5 // indirect
|
golang.org/x/tools v0.1.5 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -193,6 +193,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
|
@ -655,6 +656,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -684,6 +686,7 @@ golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||||
|
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
@ -704,6 +707,7 @@ golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWc
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -770,6 +774,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||||
|
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Package artifactcache provides a cache handler for the runner.
|
||||||
|
//
|
||||||
|
// Inspired by https://github.com/sp-ricard-valverde/github-act-cache-server
|
||||||
|
//
|
||||||
|
// TODO: Authorization
|
||||||
|
// TODO: Restrictions for accessing a cache, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
|
||||||
|
// TODO: Force deleting cache entries, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||||
|
|
||||||
|
package artifactcache
|
|
@ -42,7 +42,7 @@ type Handler struct {
|
||||||
outboundIP string
|
outboundIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(dir, outboundIP string, port uint16) (*Handler, error) {
|
func StartHandler(dir, outboundIP string, port uint16) (*Handler, error) {
|
||||||
h := &Handler{}
|
h := &Handler{}
|
||||||
|
|
||||||
if dir == "" {
|
if dir == "" {
|
|
@ -10,19 +10,17 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/config"
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the version of act_runner
|
|
||||||
var version = "develop"
|
|
||||||
|
|
||||||
func Execute(ctx context.Context) {
|
func Execute(ctx context.Context) {
|
||||||
// ./act_runner
|
// ./act_runner
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"",
|
||||||
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
|
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
Version: version,
|
Version: ver.Version(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
configFile := ""
|
configFile := ""
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"gitea.com/gitea/act_runner/internal/app/poll"
|
||||||
|
"gitea.com/gitea/act_runner/internal/app/run"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/envcheck"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/labels"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error {
|
||||||
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
|
log.Infoln("Starting runner daemon")
|
||||||
|
|
||||||
|
cfg, err := config.LoadDefault(*configFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initLogging(cfg)
|
||||||
|
|
||||||
|
reg, err := config.LoadRegistration(cfg.Runner.File)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Error("registration file not found, please register the runner first")
|
||||||
|
return err
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to load registration file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls := labels.Labels{}
|
||||||
|
for _, l := range reg.Labels {
|
||||||
|
label, err := labels.Parse(l)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warnf("ignored invalid label %q", l)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ls = append(ls, label)
|
||||||
|
}
|
||||||
|
if len(ls) == 0 {
|
||||||
|
log.Warn("no labels configured, runner may not be able to pick up jobs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ls.RequireDocker() {
|
||||||
|
if err := envcheck.CheckIfDockerRunning(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := client.New(
|
||||||
|
reg.Address,
|
||||||
|
cfg.Runner.Insecure,
|
||||||
|
reg.UUID,
|
||||||
|
reg.Token,
|
||||||
|
ver.Version(),
|
||||||
|
)
|
||||||
|
|
||||||
|
runner := run.NewRunner(cfg, reg, cli)
|
||||||
|
poller := poll.New(cfg, cli, runner)
|
||||||
|
|
||||||
|
poller.Poll(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLogging setup the global logrus logger.
|
||||||
|
func initLogging(cfg *config.Config) {
|
||||||
|
isTerm := isatty.IsTerminal(os.Stdout.Fd())
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableColors: !isTerm,
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if l := cfg.Log.Level; l != "" {
|
||||||
|
level, err := log.ParseLevel(l)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).
|
||||||
|
Errorf("invalid log level: %q", l)
|
||||||
|
}
|
||||||
|
if log.GetLevel() != level {
|
||||||
|
log.Infof("log level changed to %v", level)
|
||||||
|
log.SetLevel(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/artifactcache"
|
"gitea.com/gitea/act_runner/internal/app/artifactcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type executeArgs struct {
|
type executeArgs struct {
|
||||||
|
@ -348,7 +348,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// init a cache server
|
// init a cache server
|
||||||
handler, err := artifactcache.NewHandler("", "", 0)
|
handler, err := artifactcache.StartHandler("", "", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -20,9 +20,10 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/client"
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
"gitea.com/gitea/act_runner/config"
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
"gitea.com/gitea/act_runner/runtime"
|
"gitea.com/gitea/act_runner/internal/pkg/labels"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runRegister registers a runner to the server
|
// runRegister registers a runner to the server
|
||||||
|
@ -37,7 +38,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string)
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
log.Infof("Registering runner, arch=%s, os=%s, version=%s.",
|
log.Infof("Registering runner, arch=%s, os=%s, version=%s.",
|
||||||
goruntime.GOARCH, goruntime.GOOS, version)
|
goruntime.GOARCH, goruntime.GOOS, ver.Version())
|
||||||
|
|
||||||
// runner always needs root permission
|
// runner always needs root permission
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
|
@ -116,9 +117,9 @@ func (r *registerInputs) validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLabels(labels []string) error {
|
func validateLabels(ls []string) error {
|
||||||
for _, label := range labels {
|
for _, label := range ls {
|
||||||
if _, _, _, err := runtime.ParseLabel(label); err != nil {
|
if _, err := labels.Parse(label); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,7 +273,7 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
|
||||||
cfg.Runner.Insecure,
|
cfg.Runner.Insecure,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
version,
|
ver.Version(),
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -305,16 +306,16 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
|
||||||
Labels: inputs.CustomLabels,
|
Labels: inputs.CustomLabels,
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := make([]string, len(reg.Labels))
|
ls := make([]string, len(reg.Labels))
|
||||||
for i, v := range reg.Labels {
|
for i, v := range reg.Labels {
|
||||||
l, _, _, _ := runtime.ParseLabel(v)
|
l, _ := labels.Parse(v)
|
||||||
labels[i] = l
|
ls[i] = l.Name
|
||||||
}
|
}
|
||||||
// register new runner.
|
// register new runner.
|
||||||
resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
|
resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
|
||||||
Name: reg.Name,
|
Name: reg.Name,
|
||||||
Token: reg.Token,
|
Token: reg.Token,
|
||||||
AgentLabels: labels,
|
AgentLabels: 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")
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package poll
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
|
"github.com/bufbuild/connect-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
"gitea.com/gitea/act_runner/internal/app/run"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Poller struct {
|
||||||
|
client client.Client
|
||||||
|
runner *run.Runner
|
||||||
|
capacity int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller {
|
||||||
|
return &Poller{
|
||||||
|
client: client,
|
||||||
|
runner: runner,
|
||||||
|
capacity: cfg.Runner.Capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Poller) Poll(ctx context.Context) {
|
||||||
|
limiter := rate.NewLimiter(rate.Every(2*time.Second), 1)
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
for i := 0; i < p.capacity; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go p.poll(ctx, wg, limiter)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Poller) poll(ctx context.Context, wg *sync.WaitGroup, limiter *rate.Limiter) {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
if err := limiter.Wait(ctx); err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
log.WithError(err).Debug("limiter wait failed")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
task, ok := p.fetchTask(ctx)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.runner.Run(ctx, task); err != nil {
|
||||||
|
log.WithError(err).Error("failed to run task")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
||||||
|
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{}))
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("failed to fetch task")
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Msg.Task == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return resp.Msg.Task, true
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package run
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
|
"github.com/nektos/act/pkg/common"
|
||||||
|
"github.com/nektos/act/pkg/model"
|
||||||
|
"github.com/nektos/act/pkg/runner"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"gitea.com/gitea/act_runner/internal/app/artifactcache"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/labels"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/report"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Runner runs the pipeline.
|
||||||
|
type Runner struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
cfg *config.Config
|
||||||
|
|
||||||
|
client client.Client
|
||||||
|
labels labels.Labels
|
||||||
|
envs map[string]string
|
||||||
|
|
||||||
|
runningTasks sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
|
||||||
|
ls := labels.Labels{}
|
||||||
|
for _, v := range reg.Labels {
|
||||||
|
if l, err := labels.Parse(v); err == nil {
|
||||||
|
ls = append(ls, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
envs := make(map[string]string, len(cfg.Runner.Envs))
|
||||||
|
for k, v := range cfg.Runner.Envs {
|
||||||
|
envs[k] = v
|
||||||
|
}
|
||||||
|
if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled {
|
||||||
|
cacheHandler, err := artifactcache.StartHandler(cfg.Cache.Dir, cfg.Cache.Host, cfg.Cache.Port)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cannot init cache server, it will be disabled: %v", err)
|
||||||
|
// go on
|
||||||
|
} else {
|
||||||
|
envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Runner{
|
||||||
|
name: reg.Name,
|
||||||
|
cfg: cfg,
|
||||||
|
client: cli,
|
||||||
|
labels: ls,
|
||||||
|
envs: envs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
|
||||||
|
if _, ok := r.runningTasks.Load(task.Id); ok {
|
||||||
|
return fmt.Errorf("task %d is already running", task.Id)
|
||||||
|
} else {
|
||||||
|
r.runningTasks.Store(task.Id, struct{}{})
|
||||||
|
defer r.runningTasks.Delete(task.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
reporter := report.NewReporter(ctx, cancel, r.client, task)
|
||||||
|
var runErr error
|
||||||
|
defer func() {
|
||||||
|
lastWords := ""
|
||||||
|
if runErr != nil {
|
||||||
|
lastWords = runErr.Error()
|
||||||
|
}
|
||||||
|
_ = reporter.Close(lastWords)
|
||||||
|
}()
|
||||||
|
reporter.RunDaemon()
|
||||||
|
runErr = r.run(ctx, task, reporter)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.Reporter) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", r.name, ver.Version(), task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue())
|
||||||
|
|
||||||
|
workflow, err := model.ReadWorkflow(bytes.NewReader(task.WorkflowPayload))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
jobIDs := workflow.GetJobIDs()
|
||||||
|
if len(jobIDs) != 1 {
|
||||||
|
return fmt.Errorf("multiple jobs found: %v", jobIDs)
|
||||||
|
}
|
||||||
|
jobID := jobIDs[0]
|
||||||
|
plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
job := workflow.GetJob(jobID)
|
||||||
|
reporter.ResetSteps(len(job.Steps))
|
||||||
|
|
||||||
|
taskContext := task.Context.Fields
|
||||||
|
|
||||||
|
log.Infof("task %v repo is %v %v %v", task.Id, taskContext["repository"].GetStringValue(),
|
||||||
|
taskContext["gitea_default_actions_url"].GetStringValue(),
|
||||||
|
r.client.Address())
|
||||||
|
|
||||||
|
preset := &model.GithubContext{
|
||||||
|
Event: taskContext["event"].GetStructValue().AsMap(),
|
||||||
|
RunID: taskContext["run_id"].GetStringValue(),
|
||||||
|
RunNumber: taskContext["run_number"].GetStringValue(),
|
||||||
|
Actor: taskContext["actor"].GetStringValue(),
|
||||||
|
Repository: taskContext["repository"].GetStringValue(),
|
||||||
|
EventName: taskContext["event_name"].GetStringValue(),
|
||||||
|
Sha: taskContext["sha"].GetStringValue(),
|
||||||
|
Ref: taskContext["ref"].GetStringValue(),
|
||||||
|
RefName: taskContext["ref_name"].GetStringValue(),
|
||||||
|
RefType: taskContext["ref_type"].GetStringValue(),
|
||||||
|
HeadRef: taskContext["head_ref"].GetStringValue(),
|
||||||
|
BaseRef: taskContext["base_ref"].GetStringValue(),
|
||||||
|
Token: taskContext["token"].GetStringValue(),
|
||||||
|
RepositoryOwner: taskContext["repository_owner"].GetStringValue(),
|
||||||
|
RetentionDays: taskContext["retention_days"].GetStringValue(),
|
||||||
|
}
|
||||||
|
if t := task.Secrets["GITEA_TOKEN"]; t != "" {
|
||||||
|
preset.Token = t
|
||||||
|
} else if t := task.Secrets["GITHUB_TOKEN"]; t != "" {
|
||||||
|
preset.Token = t
|
||||||
|
}
|
||||||
|
|
||||||
|
eventJSON, err := json.Marshal(preset.Event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxLifetime := 3 * time.Hour
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
maxLifetime = time.Until(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerConfig := &runner.Config{
|
||||||
|
// On Linux, Workdir will be like "/<owner>/<repo>"
|
||||||
|
// On Windows, Workdir will be like "\<owner>\<repo>"
|
||||||
|
Workdir: filepath.FromSlash(string(filepath.Separator) + preset.Repository),
|
||||||
|
BindWorkdir: false,
|
||||||
|
|
||||||
|
ReuseContainers: false,
|
||||||
|
ForcePull: false,
|
||||||
|
ForceRebuild: false,
|
||||||
|
LogOutput: true,
|
||||||
|
JSONLogger: false,
|
||||||
|
Env: r.envs,
|
||||||
|
Secrets: task.Secrets,
|
||||||
|
GitHubInstance: r.client.Address(),
|
||||||
|
AutoRemove: true,
|
||||||
|
NoSkipCheckout: true,
|
||||||
|
PresetGitHubContext: preset,
|
||||||
|
EventJSON: string(eventJSON),
|
||||||
|
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||||
|
ContainerMaxLifetime: maxLifetime,
|
||||||
|
ContainerNetworkMode: r.cfg.Container.NetworkMode,
|
||||||
|
DefaultActionInstance: taskContext["gitea_default_actions_url"].GetStringValue(),
|
||||||
|
PlatformPicker: r.labels.PickPlatform,
|
||||||
|
}
|
||||||
|
|
||||||
|
rr, err := runner.New(runnerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
executor := rr.NewPlanExecutor(plan)
|
||||||
|
|
||||||
|
reporter.Logf("workflow prepared")
|
||||||
|
|
||||||
|
// add logger recorders
|
||||||
|
ctx = common.WithLoggerHook(ctx, reporter)
|
||||||
|
|
||||||
|
return executor(ctx)
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ func getHttpClient(endpoint string, insecure bool) *http.Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new runner client.
|
// New returns a new runner client.
|
||||||
func New(endpoint string, insecure bool, uuid, token, runnerVersion string, opts ...connect.ClientOption) *HTTPClient {
|
func New(endpoint string, insecure bool, uuid, token, version string, opts ...connect.ClientOption) *HTTPClient {
|
||||||
baseURL := strings.TrimRight(endpoint, "/") + "/api/actions"
|
baseURL := strings.TrimRight(endpoint, "/") + "/api/actions"
|
||||||
|
|
||||||
opts = append(opts, connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
|
opts = append(opts, connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
|
||||||
|
@ -39,8 +39,8 @@ func New(endpoint string, insecure bool, uuid, token, runnerVersion string, opts
|
||||||
if token != "" {
|
if token != "" {
|
||||||
req.Header().Set(TokenHeader, token)
|
req.Header().Set(TokenHeader, token)
|
||||||
}
|
}
|
||||||
if runnerVersion != "" {
|
if version != "" {
|
||||||
req.Header().Set(VersionHeader, runnerVersion)
|
req.Header().Set(VersionHeader, version)
|
||||||
}
|
}
|
||||||
return next(ctx, req)
|
return next(ctx, req)
|
||||||
}
|
}
|
|
@ -38,5 +38,5 @@ cache:
|
||||||
port: 0
|
port: 0
|
||||||
|
|
||||||
container:
|
container:
|
||||||
# Which network to use for the job containers.
|
# Which network to use for the job containers. Could be bridge, host, none, or the name of a custom network.
|
||||||
network: bridge
|
network_mode: bridge
|
|
@ -32,7 +32,7 @@ type Config struct {
|
||||||
Port uint16 `yaml:"port"`
|
Port uint16 `yaml:"port"`
|
||||||
} `yaml:"cache"`
|
} `yaml:"cache"`
|
||||||
Container struct {
|
Container struct {
|
||||||
Network string `yaml:"network"`
|
NetworkMode string `yaml:"network_mode"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +87,8 @@ func LoadDefault(file string) (*Config, error) {
|
||||||
cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache")
|
cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.Container.Network == "" {
|
if cfg.Container.NetworkMode == "" {
|
||||||
cfg.Container.Network = "bridge"
|
cfg.Container.NetworkMode = "bridge"
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Package envcheck provides a simple way to check if the environment is ready to run jobs.
|
||||||
|
package envcheck
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package envcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckIfDockerRunning(ctx context.Context) error {
|
||||||
|
// TODO: if runner support configures to use docker, we need config.Config to pass in
|
||||||
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
_, err = cli.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot ping the docker daemon, does it running? %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SchemeHost = "host"
|
||||||
|
SchemeDocker = "docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
Name string
|
||||||
|
Schema string
|
||||||
|
Arg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(str string) (*Label, error) {
|
||||||
|
splits := strings.SplitN(str, ":", 3)
|
||||||
|
label := &Label{
|
||||||
|
Name: splits[0],
|
||||||
|
Schema: "host",
|
||||||
|
Arg: "",
|
||||||
|
}
|
||||||
|
if len(splits) >= 2 {
|
||||||
|
label.Schema = splits[1]
|
||||||
|
}
|
||||||
|
if len(splits) >= 3 {
|
||||||
|
label.Arg = splits[2]
|
||||||
|
}
|
||||||
|
if label.Schema != SchemeHost && label.Schema != SchemeDocker {
|
||||||
|
return nil, fmt.Errorf("unsupported schema: %s", label.Schema)
|
||||||
|
}
|
||||||
|
return label, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Labels []*Label
|
||||||
|
|
||||||
|
func (l Labels) RequireDocker() bool {
|
||||||
|
for _, label := range l {
|
||||||
|
if label.Schema == SchemeDocker {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Labels) PickPlatform(runsOn []string) string {
|
||||||
|
platforms := make(map[string]string, len(l))
|
||||||
|
for _, label := range l {
|
||||||
|
switch label.Schema {
|
||||||
|
case SchemeDocker:
|
||||||
|
// "//" will be ignored
|
||||||
|
// TODO maybe we should use 'ubuntu-18.04:docker:node:16-buster' instead
|
||||||
|
platforms[label.Name] = strings.TrimPrefix(label.Arg, "//")
|
||||||
|
case SchemeHost:
|
||||||
|
platforms[label.Name] = "-self-hosted"
|
||||||
|
default:
|
||||||
|
// It should not happen, because Parse has checked it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range runsOn {
|
||||||
|
if v, ok := platforms[v]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support multiple labels
|
||||||
|
// like:
|
||||||
|
// ["ubuntu-22.04"] => "ubuntu:22.04"
|
||||||
|
// ["with-gpu"] => "linux:with-gpu"
|
||||||
|
// ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu"
|
||||||
|
|
||||||
|
// return default.
|
||||||
|
// So the runner receives a task with a label that the runner doesn't have,
|
||||||
|
// it happens when the user have edited the label of the runner in the web UI.
|
||||||
|
// TODO: it may be not correct, what if the runner is used as host mode only?
|
||||||
|
return "node:16-bullseye"
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args string
|
||||||
|
want *Label
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: "ubuntu:docker://node:18",
|
||||||
|
want: &Label{
|
||||||
|
Name: "ubuntu",
|
||||||
|
Schema: "docker",
|
||||||
|
Arg: "//node:18",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu:host",
|
||||||
|
want: &Label{
|
||||||
|
Name: "ubuntu",
|
||||||
|
Schema: "host",
|
||||||
|
Arg: "",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu",
|
||||||
|
want: &Label{
|
||||||
|
Name: "ubuntu",
|
||||||
|
Schema: "host",
|
||||||
|
Arg: "",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: "ubuntu:vm:ubuntu-18.04",
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.args, func(t *testing.T) {
|
||||||
|
got, err := Parse(tt.args)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, got, tt.want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package runtime
|
package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -11,13 +11,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
"gitea.com/gitea/act_runner/client"
|
|
||||||
|
|
||||||
retry "github.com/avast/retry-go/v4"
|
retry "github.com/avast/retry-go/v4"
|
||||||
"github.com/bufbuild/connect-go"
|
"github.com/bufbuild/connect-go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reporter struct {
|
type Reporter struct {
|
||||||
|
@ -179,6 +179,7 @@ func (r *Reporter) Close(lastWords string) error {
|
||||||
v.Result = runnerv1.Result_RESULT_CANCELLED
|
v.Result = runnerv1.Result_RESULT_CANCELLED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
r.state.Result = runnerv1.Result_RESULT_FAILURE
|
||||||
r.logRows = append(r.logRows, &runnerv1.LogRow{
|
r.logRows = append(r.logRows, &runnerv1.LogRow{
|
||||||
Time: timestamppb.Now(),
|
Time: timestamppb.Now(),
|
||||||
Content: lastWords,
|
Content: lastWords,
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package ver
|
||||||
|
|
||||||
|
// go build -ldflags "-X gitea.com/gitea/act_runner/internal/pkg/ver.version=1.2.3"
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
|
func Version() string {
|
||||||
|
return version
|
||||||
|
}
|
24
main.go
24
main.go
|
@ -5,33 +5,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/cmd"
|
"gitea.com/gitea/act_runner/internal/app/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withContextFunc(ctx context.Context, f func()) context.Context {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
go func() {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer signal.Stop(c)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-c:
|
|
||||||
cancel()
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := withContextFunc(context.Background(), func() {})
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
// run the command
|
// run the command
|
||||||
cmd.Execute(ctx)
|
cmd.Execute(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package poller
|
|
||||||
|
|
||||||
import "sync/atomic"
|
|
||||||
|
|
||||||
// Metric interface
|
|
||||||
type Metric interface {
|
|
||||||
IncBusyWorker() int64
|
|
||||||
DecBusyWorker() int64
|
|
||||||
BusyWorkers() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Metric = (*metric)(nil)
|
|
||||||
|
|
||||||
type metric struct {
|
|
||||||
busyWorkers int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMetric for default metric structure
|
|
||||||
func NewMetric() Metric {
|
|
||||||
return &metric{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) IncBusyWorker() int64 {
|
|
||||||
return atomic.AddInt64(&m.busyWorkers, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) DecBusyWorker() int64 {
|
|
||||||
return atomic.AddInt64(&m.busyWorkers, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) BusyWorkers() int64 {
|
|
||||||
return atomic.LoadInt64(&m.busyWorkers)
|
|
||||||
}
|
|
159
poller/poller.go
159
poller/poller.go
|
@ -1,159 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package poller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
|
||||||
"github.com/bufbuild/connect-go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/client"
|
|
||||||
"gitea.com/gitea/act_runner/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrDataLock = errors.New("Data Lock Error")
|
|
||||||
|
|
||||||
func New(cli client.Client, dispatch func(context.Context, *runnerv1.Task) error, cfg *config.Config) *Poller {
|
|
||||||
return &Poller{
|
|
||||||
Client: cli,
|
|
||||||
Dispatch: dispatch,
|
|
||||||
routineGroup: newRoutineGroup(),
|
|
||||||
metric: &metric{},
|
|
||||||
ready: make(chan struct{}, 1),
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Poller struct {
|
|
||||||
Client client.Client
|
|
||||||
Dispatch func(context.Context, *runnerv1.Task) error
|
|
||||||
|
|
||||||
sync.Mutex
|
|
||||||
routineGroup *routineGroup
|
|
||||||
metric *metric
|
|
||||||
ready chan struct{}
|
|
||||||
cfg *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) schedule() {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
if int(p.metric.BusyWorkers()) >= p.cfg.Runner.Capacity {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case p.ready <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) Wait() {
|
|
||||||
p.routineGroup.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) handle(ctx context.Context, l *log.Entry) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
l.Errorf("handle task panic: %+v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
task, err := p.pollTask(ctx)
|
|
||||||
if task == nil || err != nil {
|
|
||||||
if err != nil {
|
|
||||||
l.Errorf("can't find the task: %v", err.Error())
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
p.metric.IncBusyWorker()
|
|
||||||
p.routineGroup.Run(func() {
|
|
||||||
defer p.schedule()
|
|
||||||
defer p.metric.DecBusyWorker()
|
|
||||||
if err := p.dispatchTask(ctx, task); err != nil {
|
|
||||||
l.Errorf("execute task: %v", err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) Poll(ctx context.Context) error {
|
|
||||||
l := log.WithField("func", "Poll")
|
|
||||||
|
|
||||||
for {
|
|
||||||
// check worker number
|
|
||||||
p.schedule()
|
|
||||||
|
|
||||||
select {
|
|
||||||
// wait worker ready
|
|
||||||
case <-p.ready:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.handle(ctx, l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) pollTask(ctx context.Context) (*runnerv1.Task, error) {
|
|
||||||
l := log.WithField("func", "pollTask")
|
|
||||||
l.Info("poller: request stage from remote server")
|
|
||||||
|
|
||||||
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// request a new build stage for execution from the central
|
|
||||||
// build server.
|
|
||||||
resp, err := p.Client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{}))
|
|
||||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
|
||||||
l.WithError(err).Trace("poller: no stage returned")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err == ErrDataLock {
|
|
||||||
l.WithError(err).Info("task accepted by another runner")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
l.WithError(err).Error("cannot accept task")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit if a nil or empty stage is returned from the system
|
|
||||||
// and allow the runner to retry.
|
|
||||||
if resp.Msg.Task == nil || resp.Msg.Task.Id == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Msg.Task, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Poller) dispatchTask(ctx context.Context, task *runnerv1.Task) error {
|
|
||||||
l := log.WithField("func", "dispatchTask")
|
|
||||||
defer func() {
|
|
||||||
e := recover()
|
|
||||||
if e != nil {
|
|
||||||
l.Errorf("panic error: %v", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
runCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return p.Dispatch(runCtx, task)
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package poller
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
type routineGroup struct {
|
|
||||||
waitGroup sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRoutineGroup() *routineGroup {
|
|
||||||
return new(routineGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *routineGroup) Run(fn func()) {
|
|
||||||
g.waitGroup.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer g.waitGroup.Done()
|
|
||||||
fn()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *routineGroup) Wait() {
|
|
||||||
g.waitGroup.Wait()
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseLabel(str string) (label, schema, arg string, err error) {
|
|
||||||
splits := strings.SplitN(str, ":", 3)
|
|
||||||
label = splits[0]
|
|
||||||
schema = "host"
|
|
||||||
arg = ""
|
|
||||||
if len(splits) >= 2 {
|
|
||||||
schema = splits[1]
|
|
||||||
}
|
|
||||||
if len(splits) >= 3 {
|
|
||||||
arg = splits[2]
|
|
||||||
}
|
|
||||||
if schema != "host" && schema != "docker" {
|
|
||||||
return "", "", "", fmt.Errorf("unsupported schema: %s", schema)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestParseLabel(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
args string
|
|
||||||
wantLabel string
|
|
||||||
wantSchema string
|
|
||||||
wantArg string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
args: "ubuntu:docker://node:18",
|
|
||||||
wantLabel: "ubuntu",
|
|
||||||
wantSchema: "docker",
|
|
||||||
wantArg: "//node:18",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: "ubuntu:host",
|
|
||||||
wantLabel: "ubuntu",
|
|
||||||
wantSchema: "host",
|
|
||||||
wantArg: "",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: "ubuntu",
|
|
||||||
wantLabel: "ubuntu",
|
|
||||||
wantSchema: "host",
|
|
||||||
wantArg: "",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: "ubuntu:vm:ubuntu-18.04",
|
|
||||||
wantLabel: "",
|
|
||||||
wantSchema: "",
|
|
||||||
wantArg: "",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.args, func(t *testing.T) {
|
|
||||||
gotLabel, gotSchema, gotArg, err := ParseLabel(tt.args)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("parseLabel() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gotLabel != tt.wantLabel {
|
|
||||||
t.Errorf("parseLabel() gotLabel = %v, want %v", gotLabel, tt.wantLabel)
|
|
||||||
}
|
|
||||||
if gotSchema != tt.wantSchema {
|
|
||||||
t.Errorf("parseLabel() gotSchema = %v, want %v", gotSchema, tt.wantSchema)
|
|
||||||
}
|
|
||||||
if gotArg != tt.wantArg {
|
|
||||||
t.Errorf("parseLabel() gotArg = %v, want %v", gotArg, tt.wantArg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/artifactcache"
|
|
||||||
"gitea.com/gitea/act_runner/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Runner runs the pipeline.
|
|
||||||
type Runner struct {
|
|
||||||
Machine string
|
|
||||||
Version string
|
|
||||||
ForgeInstance string
|
|
||||||
Environ map[string]string
|
|
||||||
Client client.Client
|
|
||||||
Labels []string
|
|
||||||
Network string
|
|
||||||
CacheHandler *artifactcache.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the pipeline stage.
|
|
||||||
func (s *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
|
|
||||||
env := map[string]string{}
|
|
||||||
for k, v := range s.Environ {
|
|
||||||
env[k] = v
|
|
||||||
}
|
|
||||||
if s.CacheHandler != nil {
|
|
||||||
env["ACTIONS_CACHE_URL"] = s.CacheHandler.ExternalURL() + "/"
|
|
||||||
}
|
|
||||||
return NewTask(task.Id, s.Client, env, s.Network, s.platformPicker).Run(ctx, task, s.Machine, s.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Runner) platformPicker(labels []string) string {
|
|
||||||
platforms := make(map[string]string, len(s.Labels))
|
|
||||||
for _, l := range s.Labels {
|
|
||||||
label, schema, arg, err := ParseLabel(l)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("invaid label %q: %v", l, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch schema {
|
|
||||||
case "docker":
|
|
||||||
// TODO "//" will be ignored, maybe we should use 'ubuntu-18.04:docker:node:16-buster' instead
|
|
||||||
platforms[label] = strings.TrimPrefix(arg, "//")
|
|
||||||
case "host":
|
|
||||||
platforms[label] = "-self-hosted"
|
|
||||||
default:
|
|
||||||
// It should not happen, because ParseLabel has checked it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, label := range labels {
|
|
||||||
if v, ok := platforms[label]; ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support multiple labels
|
|
||||||
// like:
|
|
||||||
// ["ubuntu-22.04"] => "ubuntu:22.04"
|
|
||||||
// ["with-gpu"] => "linux:with-gpu"
|
|
||||||
// ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu"
|
|
||||||
|
|
||||||
// return default.
|
|
||||||
// So the runner receives a task with a label that the runner doesn't have,
|
|
||||||
// it happens when the user have edited the label of the runner in the web UI.
|
|
||||||
return "node:16-bullseye" // TODO: it may be not correct, what if the runner is used as host mode only?
|
|
||||||
}
|
|
267
runtime/task.go
267
runtime/task.go
|
@ -1,267 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
|
||||||
"github.com/nektos/act/pkg/common"
|
|
||||||
"github.com/nektos/act/pkg/model"
|
|
||||||
"github.com/nektos/act/pkg/runner"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"gitea.com/gitea/act_runner/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalTaskMap sync.Map
|
|
||||||
|
|
||||||
type TaskInput struct {
|
|
||||||
repoDirectory string
|
|
||||||
// actor string
|
|
||||||
// workdir string
|
|
||||||
// workflowsPath string
|
|
||||||
// autodetectEvent bool
|
|
||||||
// eventPath string
|
|
||||||
// reuseContainers bool
|
|
||||||
// bindWorkdir bool
|
|
||||||
// secrets []string
|
|
||||||
envs map[string]string
|
|
||||||
// platforms []string
|
|
||||||
// dryrun bool
|
|
||||||
forcePull bool
|
|
||||||
forceRebuild bool
|
|
||||||
// noOutput bool
|
|
||||||
// envfile string
|
|
||||||
// secretfile string
|
|
||||||
insecureSecrets bool
|
|
||||||
// defaultBranch string
|
|
||||||
privileged bool
|
|
||||||
usernsMode string
|
|
||||||
containerArchitecture string
|
|
||||||
containerDaemonSocket string
|
|
||||||
// noWorkflowRecurse bool
|
|
||||||
useGitIgnore bool
|
|
||||||
containerCapAdd []string
|
|
||||||
containerCapDrop []string
|
|
||||||
// autoRemove bool
|
|
||||||
artifactServerPath string
|
|
||||||
artifactServerPort string
|
|
||||||
jsonLogger bool
|
|
||||||
// noSkipCheckout bool
|
|
||||||
// remoteName string
|
|
||||||
|
|
||||||
EnvFile string
|
|
||||||
|
|
||||||
containerNetworkMode string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task struct {
|
|
||||||
BuildID int64
|
|
||||||
Input *TaskInput
|
|
||||||
|
|
||||||
client client.Client
|
|
||||||
log *log.Entry
|
|
||||||
platformPicker func([]string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTask creates a new task
|
|
||||||
func NewTask(buildID int64, client client.Client, runnerEnvs map[string]string, network string, picker func([]string) string) *Task {
|
|
||||||
task := &Task{
|
|
||||||
Input: &TaskInput{
|
|
||||||
envs: runnerEnvs,
|
|
||||||
containerNetworkMode: network,
|
|
||||||
},
|
|
||||||
BuildID: buildID,
|
|
||||||
|
|
||||||
client: client,
|
|
||||||
log: log.WithField("buildID", buildID),
|
|
||||||
platformPicker: picker,
|
|
||||||
}
|
|
||||||
task.Input.repoDirectory, _ = os.Getwd()
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWorkflowsPath return the workflows directory, it will try .gitea first and then fallback to .github
|
|
||||||
func getWorkflowsPath(dir string) (string, error) {
|
|
||||||
p := filepath.Join(dir, ".gitea/workflows")
|
|
||||||
_, err := os.Stat(p)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return filepath.Join(dir, ".github/workflows"), nil
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getToken(task *runnerv1.Task) string {
|
|
||||||
token := task.Secrets["GITHUB_TOKEN"]
|
|
||||||
if task.Secrets["GITEA_TOKEN"] != "" {
|
|
||||||
token = task.Secrets["GITEA_TOKEN"]
|
|
||||||
}
|
|
||||||
if task.Context.Fields["token"].GetStringValue() != "" {
|
|
||||||
token = task.Context.Fields["token"].GetStringValue()
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Task) Run(ctx context.Context, task *runnerv1.Task, runnerName, runnerVersion string) (lastErr error) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
_, exist := globalTaskMap.Load(task.Id)
|
|
||||||
if exist {
|
|
||||||
return fmt.Errorf("task %d already exists", task.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set task ve to global map
|
|
||||||
// when task is done or canceled, it will be removed from the map
|
|
||||||
globalTaskMap.Store(task.Id, t)
|
|
||||||
defer globalTaskMap.Delete(task.Id)
|
|
||||||
|
|
||||||
lastWords := ""
|
|
||||||
reporter := NewReporter(ctx, cancel, t.client, task)
|
|
||||||
defer func() {
|
|
||||||
// set the job to failed on an error return value
|
|
||||||
if lastErr != nil {
|
|
||||||
reporter.Fire(&log.Entry{
|
|
||||||
Data: log.Fields{
|
|
||||||
"jobResult": "failure",
|
|
||||||
},
|
|
||||||
Time: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ = reporter.Close(lastWords)
|
|
||||||
}()
|
|
||||||
reporter.RunDaemon()
|
|
||||||
|
|
||||||
reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", runnerName, runnerVersion, task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue())
|
|
||||||
|
|
||||||
workflowsPath, err := getWorkflowsPath(t.Input.repoDirectory)
|
|
||||||
if err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.log.Debugf("workflows path: %s", workflowsPath)
|
|
||||||
|
|
||||||
workflow, err := model.ReadWorkflow(bytes.NewReader(task.WorkflowPayload))
|
|
||||||
if err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
jobIDs := workflow.GetJobIDs()
|
|
||||||
if len(jobIDs) != 1 {
|
|
||||||
err := fmt.Errorf("multiple jobs found: %v", jobIDs)
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
jobID := jobIDs[0]
|
|
||||||
plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID)
|
|
||||||
if err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
job := workflow.GetJob(jobID)
|
|
||||||
reporter.ResetSteps(len(job.Steps))
|
|
||||||
|
|
||||||
log.Infof("plan: %+v", plan.Stages[0].Runs)
|
|
||||||
|
|
||||||
token := getToken(task)
|
|
||||||
dataContext := task.Context.Fields
|
|
||||||
|
|
||||||
log.Infof("task %v repo is %v %v %v", task.Id, dataContext["repository"].GetStringValue(),
|
|
||||||
dataContext["gitea_default_actions_url"].GetStringValue(),
|
|
||||||
t.client.Address())
|
|
||||||
|
|
||||||
preset := &model.GithubContext{
|
|
||||||
Event: dataContext["event"].GetStructValue().AsMap(),
|
|
||||||
RunID: dataContext["run_id"].GetStringValue(),
|
|
||||||
RunNumber: dataContext["run_number"].GetStringValue(),
|
|
||||||
Actor: dataContext["actor"].GetStringValue(),
|
|
||||||
Repository: dataContext["repository"].GetStringValue(),
|
|
||||||
EventName: dataContext["event_name"].GetStringValue(),
|
|
||||||
Sha: dataContext["sha"].GetStringValue(),
|
|
||||||
Ref: dataContext["ref"].GetStringValue(),
|
|
||||||
RefName: dataContext["ref_name"].GetStringValue(),
|
|
||||||
RefType: dataContext["ref_type"].GetStringValue(),
|
|
||||||
HeadRef: dataContext["head_ref"].GetStringValue(),
|
|
||||||
BaseRef: dataContext["base_ref"].GetStringValue(),
|
|
||||||
Token: token,
|
|
||||||
RepositoryOwner: dataContext["repository_owner"].GetStringValue(),
|
|
||||||
RetentionDays: dataContext["retention_days"].GetStringValue(),
|
|
||||||
}
|
|
||||||
eventJSON, err := json.Marshal(preset.Event)
|
|
||||||
if err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
maxLifetime := 3 * time.Hour
|
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
|
||||||
maxLifetime = time.Until(deadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
input := t.Input
|
|
||||||
config := &runner.Config{
|
|
||||||
// On Linux, Workdir will be like "/<owner>/<repo>"
|
|
||||||
// On Windows, Workdir will be like "\<owner>\<repo>"
|
|
||||||
Workdir: filepath.FromSlash(string(filepath.Separator) + preset.Repository),
|
|
||||||
BindWorkdir: false,
|
|
||||||
ReuseContainers: false,
|
|
||||||
ForcePull: input.forcePull,
|
|
||||||
ForceRebuild: input.forceRebuild,
|
|
||||||
LogOutput: true,
|
|
||||||
JSONLogger: input.jsonLogger,
|
|
||||||
Env: input.envs,
|
|
||||||
Secrets: task.Secrets,
|
|
||||||
InsecureSecrets: input.insecureSecrets,
|
|
||||||
Privileged: input.privileged,
|
|
||||||
UsernsMode: input.usernsMode,
|
|
||||||
ContainerArchitecture: input.containerArchitecture,
|
|
||||||
ContainerDaemonSocket: input.containerDaemonSocket,
|
|
||||||
UseGitIgnore: input.useGitIgnore,
|
|
||||||
GitHubInstance: t.client.Address(),
|
|
||||||
ContainerCapAdd: input.containerCapAdd,
|
|
||||||
ContainerCapDrop: input.containerCapDrop,
|
|
||||||
AutoRemove: true,
|
|
||||||
ArtifactServerPath: input.artifactServerPath,
|
|
||||||
ArtifactServerPort: input.artifactServerPort,
|
|
||||||
NoSkipCheckout: true,
|
|
||||||
PresetGitHubContext: preset,
|
|
||||||
EventJSON: string(eventJSON),
|
|
||||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
|
||||||
ContainerMaxLifetime: maxLifetime,
|
|
||||||
ContainerNetworkMode: input.containerNetworkMode,
|
|
||||||
DefaultActionInstance: dataContext["gitea_default_actions_url"].GetStringValue(),
|
|
||||||
PlatformPicker: t.platformPicker,
|
|
||||||
}
|
|
||||||
r, err := runner.New(config)
|
|
||||||
if err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
executor := r.NewPlanExecutor(plan)
|
|
||||||
|
|
||||||
t.log.Infof("workflow prepared")
|
|
||||||
reporter.Logf("workflow prepared")
|
|
||||||
|
|
||||||
// add logger recorders
|
|
||||||
ctx = common.WithLoggerHook(ctx, reporter)
|
|
||||||
|
|
||||||
if err := executor(ctx); err != nil {
|
|
||||||
lastWords = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue