mirror of
				https://gitea.com/gitea/act_runner.git
				synced 2025-11-04 14:48:56 +01:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -58,7 +58,7 @@ builds:
 | 
			
		||||
  flags:
 | 
			
		||||
  - -trimpath
 | 
			
		||||
  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: >-
 | 
			
		||||
    {{ .ProjectName }}-
 | 
			
		||||
    {{- .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_VERSION := go-1.18.x
 | 
			
		||||
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
 | 
			
		||||
DARWIN_ARCHS ?= darwin-12/amd64,darwin-12/arm64
 | 
			
		||||
@@ -63,7 +62,7 @@ else
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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/sirupsen/logrus v1.9.0
 | 
			
		||||
	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/time v0.1.0
 | 
			
		||||
	google.golang.org/protobuf v1.28.1
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
	gotest.tools/v3 v3.4.0
 | 
			
		||||
	modernc.org/sqlite v1.14.2
 | 
			
		||||
	xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978
 | 
			
		||||
	xorm.io/xorm v1.3.2
 | 
			
		||||
@@ -33,6 +35,7 @@ require (
 | 
			
		||||
	github.com/ajg/form v1.5.1 // indirect
 | 
			
		||||
	github.com/containerd/containerd v1.6.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/distribution v2.8.1+incompatible // 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/gogo/protobuf v1.3.2 // 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/uuid v1.3.0 // 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/selinux v1.11.0 // 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/rhysd/actionlint v1.6.23 // 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/mod v0.4.2 // 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/tools v0.1.5 // 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.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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
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/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-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-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-20210320140829-1e4c9ba3b0c4/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-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/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-20180828015842-6cd1fcedba52/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-20201124115921-2c860bdd6e78/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/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								internal/app/artifactcache/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								internal/app/artifactcache/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewHandler(dir, outboundIP string, port uint16) (*Handler, error) {
 | 
			
		||||
func StartHandler(dir, outboundIP string, port uint16) (*Handler, error) {
 | 
			
		||||
	h := &Handler{}
 | 
			
		||||
 | 
			
		||||
	if dir == "" {
 | 
			
		||||
@@ -10,19 +10,17 @@ import (
 | 
			
		||||
 | 
			
		||||
	"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) {
 | 
			
		||||
	// ./act_runner
 | 
			
		||||
	rootCmd := &cobra.Command{
 | 
			
		||||
		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.",
 | 
			
		||||
		Args:         cobra.MaximumNArgs(1),
 | 
			
		||||
		Version:      version,
 | 
			
		||||
		Version:      ver.Version(),
 | 
			
		||||
		SilenceUsage: true,
 | 
			
		||||
	}
 | 
			
		||||
	configFile := ""
 | 
			
		||||
							
								
								
									
										98
									
								
								internal/app/cmd/daemon.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								internal/app/cmd/daemon.go
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
			
		||||
	"golang.org/x/term"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/act_runner/artifactcache"
 | 
			
		||||
	"gitea.com/gitea/act_runner/internal/app/artifactcache"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type executeArgs struct {
 | 
			
		||||
@@ -348,7 +348,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// init a cache server
 | 
			
		||||
		handler, err := artifactcache.NewHandler("", "", 0)
 | 
			
		||||
		handler, err := artifactcache.StartHandler("", "", 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -20,9 +20,10 @@ import (
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/act_runner/client"
 | 
			
		||||
	"gitea.com/gitea/act_runner/config"
 | 
			
		||||
	"gitea.com/gitea/act_runner/runtime"
 | 
			
		||||
	"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/ver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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.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
 | 
			
		||||
		if os.Getuid() != 0 {
 | 
			
		||||
@@ -116,9 +117,9 @@ func (r *registerInputs) validate() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateLabels(labels []string) error {
 | 
			
		||||
	for _, label := range labels {
 | 
			
		||||
		if _, _, _, err := runtime.ParseLabel(label); err != nil {
 | 
			
		||||
func validateLabels(ls []string) error {
 | 
			
		||||
	for _, label := range ls {
 | 
			
		||||
		if _, err := labels.Parse(label); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -272,7 +273,7 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
 | 
			
		||||
		cfg.Runner.Insecure,
 | 
			
		||||
		"",
 | 
			
		||||
		"",
 | 
			
		||||
		version,
 | 
			
		||||
		ver.Version(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
@@ -305,16 +306,16 @@ func doRegister(cfg *config.Config, inputs *registerInputs) error {
 | 
			
		||||
		Labels:  inputs.CustomLabels,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	labels := make([]string, len(reg.Labels))
 | 
			
		||||
	ls := make([]string, len(reg.Labels))
 | 
			
		||||
	for i, v := range reg.Labels {
 | 
			
		||||
		l, _, _, _ := runtime.ParseLabel(v)
 | 
			
		||||
		labels[i] = l
 | 
			
		||||
		l, _ := labels.Parse(v)
 | 
			
		||||
		ls[i] = l.Name
 | 
			
		||||
	}
 | 
			
		||||
	// register new runner.
 | 
			
		||||
	resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{
 | 
			
		||||
		Name:        reg.Name,
 | 
			
		||||
		Token:       reg.Token,
 | 
			
		||||
		AgentLabels: labels,
 | 
			
		||||
		AgentLabels: ls,
 | 
			
		||||
	}))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("poller: cannot register new runner")
 | 
			
		||||
							
								
								
									
										82
									
								
								internal/app/poll/poller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								internal/app/poll/poller.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										199
									
								
								internal/app/run/runner.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								internal/app/run/runner.go
									
									
									
									
									
										Normal file
									
								
							@@ -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.
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
	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 != "" {
 | 
			
		||||
				req.Header().Set(TokenHeader, token)
 | 
			
		||||
			}
 | 
			
		||||
			if runnerVersion != "" {
 | 
			
		||||
				req.Header().Set(VersionHeader, runnerVersion)
 | 
			
		||||
			if version != "" {
 | 
			
		||||
				req.Header().Set(VersionHeader, version)
 | 
			
		||||
			}
 | 
			
		||||
			return next(ctx, req)
 | 
			
		||||
		}
 | 
			
		||||
@@ -38,5 +38,5 @@ cache:
 | 
			
		||||
  port: 0
 | 
			
		||||
 | 
			
		||||
container:
 | 
			
		||||
  # Which network to use for the job containers.
 | 
			
		||||
  network: bridge
 | 
			
		||||
  # Which network to use for the job containers. Could be bridge, host, none, or the name of a custom network.
 | 
			
		||||
  network_mode: bridge
 | 
			
		||||
@@ -32,7 +32,7 @@ type Config struct {
 | 
			
		||||
		Port    uint16 `yaml:"port"`
 | 
			
		||||
	} `yaml:"cache"`
 | 
			
		||||
	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")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Container.Network == "" {
 | 
			
		||||
		cfg.Container.Network = "bridge"
 | 
			
		||||
	if cfg.Container.NetworkMode == "" {
 | 
			
		||||
		cfg.Container.NetworkMode = "bridge"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cfg, nil
 | 
			
		||||
							
								
								
									
										5
									
								
								internal/pkg/envcheck/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								internal/pkg/envcheck/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										27
									
								
								internal/pkg/envcheck/docker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								internal/pkg/envcheck/docker.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								internal/pkg/labels/labels.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								internal/pkg/labels/labels.go
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								internal/pkg/labels/labels_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/pkg/labels/labels_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package runtime
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
@@ -11,13 +11,13 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
 | 
			
		||||
	"gitea.com/gitea/act_runner/client"
 | 
			
		||||
 | 
			
		||||
	retry "github.com/avast/retry-go/v4"
 | 
			
		||||
	"github.com/bufbuild/connect-go"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"google.golang.org/protobuf/proto"
 | 
			
		||||
	"google.golang.org/protobuf/types/known/timestamppb"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/act_runner/internal/pkg/client"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Reporter struct {
 | 
			
		||||
@@ -179,6 +179,7 @@ func (r *Reporter) Close(lastWords string) error {
 | 
			
		||||
				v.Result = runnerv1.Result_RESULT_CANCELLED
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		r.state.Result = runnerv1.Result_RESULT_FAILURE
 | 
			
		||||
		r.logRows = append(r.logRows, &runnerv1.LogRow{
 | 
			
		||||
			Time:    timestamppb.Now(),
 | 
			
		||||
			Content: lastWords,
 | 
			
		||||
							
								
								
									
										11
									
								
								internal/pkg/ver/version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/pkg/ver/version.go
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"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() {
 | 
			
		||||
	ctx := withContextFunc(context.Background(), func() {})
 | 
			
		||||
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
 | 
			
		||||
	defer stop()
 | 
			
		||||
	// run the command
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user