summaryrefslogtreecommitdiffstats
path: root/internal/pkg/config
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-20 22:50:50 +0200
committerDaniel Baumann <daniel@debian.org>2024-10-20 22:50:50 +0200
commit9fa26b7837ed8e6679b7e6115425cab6ecbc9a8a (patch)
treec5b6f218ae267153042529217fdabeac4849ca1e /internal/pkg/config
parentInitial commit. (diff)
downloadforgejo-runner-upstream/3.5.1.tar.xz
forgejo-runner-upstream/3.5.1.zip
Adding upstream version 3.5.1.HEADupstream/3.5.1upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'internal/pkg/config')
-rw-r--r--internal/pkg/config/config.example.yaml100
-rw-r--r--internal/pkg/config/config.go166
-rw-r--r--internal/pkg/config/config_test.go37
-rw-r--r--internal/pkg/config/deprecated.go62
-rw-r--r--internal/pkg/config/embed.go9
-rw-r--r--internal/pkg/config/registration.go54
6 files changed, 428 insertions, 0 deletions
diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml
new file mode 100644
index 0000000..32dfb68
--- /dev/null
+++ b/internal/pkg/config/config.example.yaml
@@ -0,0 +1,100 @@
+# Example configuration file, it's safe to copy this as the default config file without any modification.
+
+# You don't have to copy this file to your instance,
+# just run `./act_runner generate-config > config.yaml` to generate a config file.
+
+log:
+ # The level of logging, can be trace, debug, info, warn, error, fatal
+ level: info
+
+runner:
+ # Where to store the registration result.
+ file: .runner
+ # Execute how many tasks concurrently at the same time.
+ capacity: 1
+ # Extra environment variables to run jobs.
+ envs:
+ A_TEST_ENV_NAME_1: a_test_env_value_1
+ A_TEST_ENV_NAME_2: a_test_env_value_2
+ # Extra environment variables to run jobs from a file.
+ # It will be ignored if it's empty or the file doesn't exist.
+ env_file: .env
+ # The timeout for a job to be finished.
+ # Please note that the Forgejo instance also has a timeout (3h by default) for the job.
+ # So the job could be stopped by the Forgejo instance if it's timeout is shorter than this.
+ timeout: 3h
+ # The timeout for the runner to wait for running jobs to finish when
+ # shutting down because a TERM or INT signal has been received. Any
+ # running jobs that haven't finished after this timeout will be
+ # cancelled.
+ # If unset or zero the jobs will be cancelled immediately.
+ shutdown_timeout: 3h
+ # Whether skip verifying the TLS certificate of the instance.
+ insecure: false
+ # The timeout for fetching the job from the Forgejo instance.
+ fetch_timeout: 5s
+ # The interval for fetching the job from the Forgejo instance.
+ fetch_interval: 2s
+ # The interval for reporting the job status and logs to the Forgejo instance.
+ report_interval: 1s
+ # The labels of a runner are used to determine which jobs the runner can run, and how to run them.
+ # Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"]
+ # If it's empty when registering, it will ask for inputting labels.
+ # If it's empty when execute `deamon`, will use labels in `.runner` file.
+ labels: []
+
+cache:
+ # Enable cache server to use actions/cache.
+ enabled: true
+ # The directory to store the cache data.
+ # If it's empty, the cache data will be stored in $HOME/.cache/actcache.
+ dir: ""
+ # The host of the cache server.
+ # It's not for the address to listen, but the address to connect from job containers.
+ # So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
+ host: ""
+ # The port of the cache server.
+ # 0 means to use a random available port.
+ port: 0
+ # The external cache server URL. Valid only when enable is true.
+ # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself.
+ # The URL should generally end with "/".
+ external_server: ""
+
+container:
+ # Specifies the network to which the container will connect.
+ # Could be host, bridge or the name of a custom network.
+ # If it's empty, create a network automatically.
+ network: ""
+ # Whether to create networks with IPv6 enabled. Requires the Docker daemon to be set up accordingly.
+ # Only takes effect if "network" is set to "".
+ enable_ipv6: false
+ # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
+ privileged: false
+ # And other options to be used when the container is started (eg, --add-host=my.forgejo.url:host-gateway).
+ options:
+ # The parent directory of a job's working directory.
+ # If it's empty, /workspace will be used.
+ workdir_parent:
+ # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
+ # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
+ # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
+ # valid_volumes:
+ # - data
+ # - /src/*.json
+ # If you want to allow any volume, please use the following configuration:
+ # valid_volumes:
+ # - '**'
+ valid_volumes: []
+ # overrides the docker client host with the specified one.
+ # If it's empty, act_runner will find an available docker host automatically.
+ # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
+ # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
+ docker_host: ""
+ # Pull docker image(s) even if already present
+ force_pull: false
+
+host:
+ # The parent directory of a job's working directory.
+ # If it's empty, $HOME/.cache/act/ will be used.
+ workdir_parent:
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
new file mode 100644
index 0000000..a1536b3
--- /dev/null
+++ b/internal/pkg/config/config.go
@@ -0,0 +1,166 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package config
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/joho/godotenv"
+ log "github.com/sirupsen/logrus"
+ "gopkg.in/yaml.v3"
+)
+
+// Log represents the configuration for logging.
+type Log struct {
+ Level string `yaml:"level"` // Level indicates the logging level.
+}
+
+// Runner represents the configuration for the runner.
+type Runner struct {
+ File string `yaml:"file"` // File specifies the file path for the runner.
+ Capacity int `yaml:"capacity"` // Capacity specifies the capacity of the runner.
+ Envs map[string]string `yaml:"envs"` // Envs stores environment variables for the runner.
+ EnvFile string `yaml:"env_file"` // EnvFile specifies the path to the file containing environment variables for the runner.
+ Timeout time.Duration `yaml:"timeout"` // Timeout specifies the duration for runner timeout.
+ ShutdownTimeout time.Duration `yaml:"shutdown_timeout"` // ShutdownTimeout specifies the duration to wait for running jobs to complete during a shutdown of the runner.
+ Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode.
+ FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources.
+ FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources.
+ ReportInterval time.Duration `yaml:"report_interval"` // ReportInterval specifies the interval duration for reporting status and logs of a running job.
+ Labels []string `yaml:"labels"` // Labels specify the labels of the runner. Labels are declared on each startup
+}
+
+// Cache represents the configuration for caching.
+type Cache struct {
+ Enabled *bool `yaml:"enabled"` // Enabled indicates whether caching is enabled. It is a pointer to distinguish between false and not set. If not set, it will be true.
+ Dir string `yaml:"dir"` // Dir specifies the directory path for caching.
+ Host string `yaml:"host"` // Host specifies the caching host.
+ Port uint16 `yaml:"port"` // Port specifies the caching port.
+ ExternalServer string `yaml:"external_server"` // ExternalServer specifies the URL of external cache server
+}
+
+// Container represents the configuration for the container.
+type Container struct {
+ Network string `yaml:"network"` // Network specifies the network for the container.
+ NetworkMode string `yaml:"network_mode"` // Deprecated: use Network instead. Could be removed after Gitea 1.20
+ EnableIPv6 bool `yaml:"enable_ipv6"` // EnableIPv6 indicates whether the network is created with IPv6 enabled.
+ Privileged bool `yaml:"privileged"` // Privileged indicates whether the container runs in privileged mode.
+ Options string `yaml:"options"` // Options specifies additional options for the container.
+ WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory.
+ ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers.
+ DockerHost string `yaml:"docker_host"` // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST.
+ ForcePull bool `yaml:"force_pull"` // Pull docker image(s) even if already present
+}
+
+// Host represents the configuration for the host.
+type Host struct {
+ WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the host's working directory.
+}
+
+// Config represents the overall configuration.
+type Config struct {
+ Log Log `yaml:"log"` // Log represents the configuration for logging.
+ Runner Runner `yaml:"runner"` // Runner represents the configuration for the runner.
+ Cache Cache `yaml:"cache"` // Cache represents the configuration for caching.
+ Container Container `yaml:"container"` // Container represents the configuration for the container.
+ Host Host `yaml:"host"` // Host represents the configuration for the host.
+}
+
+// Tune the config settings accordingly to the Forgejo instance that will be used.
+func (c *Config) Tune(instanceURL string) {
+ if instanceURL == "https://codeberg.org" {
+ if c.Runner.FetchInterval < 30*time.Second {
+ log.Info("The runner is configured to be used by a public instance, fetch interval is set to 30 seconds.")
+ c.Runner.FetchInterval = 30 * time.Second
+ }
+ }
+}
+
+// LoadDefault returns the default configuration.
+// If file is not empty, it will be used to load the configuration.
+func LoadDefault(file string) (*Config, error) {
+ cfg := &Config{}
+ if file != "" {
+ content, err := os.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("open config file %q: %w", file, err)
+ }
+ if err := yaml.Unmarshal(content, cfg); err != nil {
+ return nil, fmt.Errorf("parse config file %q: %w", file, err)
+ }
+ }
+ compatibleWithOldEnvs(file != "", cfg)
+
+ if cfg.Runner.EnvFile != "" {
+ if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() {
+ envs, err := godotenv.Read(cfg.Runner.EnvFile)
+ if err != nil {
+ return nil, fmt.Errorf("read env file %q: %w", cfg.Runner.EnvFile, err)
+ }
+ if cfg.Runner.Envs == nil {
+ cfg.Runner.Envs = map[string]string{}
+ }
+ for k, v := range envs {
+ cfg.Runner.Envs[k] = v
+ }
+ }
+ }
+
+ if cfg.Log.Level == "" {
+ cfg.Log.Level = "info"
+ }
+ if cfg.Runner.File == "" {
+ cfg.Runner.File = ".runner"
+ }
+ if cfg.Runner.Capacity <= 0 {
+ cfg.Runner.Capacity = 1
+ }
+ if cfg.Runner.Timeout <= 0 {
+ cfg.Runner.Timeout = 3 * time.Hour
+ }
+ if cfg.Cache.Enabled == nil {
+ b := true
+ cfg.Cache.Enabled = &b
+ }
+ if *cfg.Cache.Enabled {
+ if cfg.Cache.Dir == "" {
+ home, _ := os.UserHomeDir()
+ cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache")
+ }
+ }
+ if cfg.Container.WorkdirParent == "" {
+ cfg.Container.WorkdirParent = "workspace"
+ }
+ if cfg.Host.WorkdirParent == "" {
+ home, _ := os.UserHomeDir()
+ cfg.Host.WorkdirParent = filepath.Join(home, ".cache", "act")
+ }
+ if cfg.Runner.FetchTimeout <= 0 {
+ cfg.Runner.FetchTimeout = 5 * time.Second
+ }
+ if cfg.Runner.FetchInterval <= 0 {
+ cfg.Runner.FetchInterval = 2 * time.Second
+ }
+ if cfg.Runner.ReportInterval <= 0 {
+ cfg.Runner.ReportInterval = time.Second
+ }
+
+ // although `container.network_mode` will be deprecated, but we have to be compatible with it for now.
+ if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" {
+ log.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.")
+ if cfg.Container.NetworkMode == "bridge" {
+ // Previously, if the value of `container.network_mode` is `bridge`, we will create a new network for job.
+ // But “bridge” is easily confused with the bridge network created by Docker by default.
+ // So we set the value of `container.network` to empty string to make `act_runner` automatically create a new network for job.
+ cfg.Container.Network = ""
+ } else {
+ cfg.Container.Network = cfg.Container.NetworkMode
+ }
+ }
+
+ return cfg, nil
+}
diff --git a/internal/pkg/config/config_test.go b/internal/pkg/config/config_test.go
new file mode 100644
index 0000000..d2ddf2f
--- /dev/null
+++ b/internal/pkg/config/config_test.go
@@ -0,0 +1,37 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package config
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConfigTune(t *testing.T) {
+ c := &Config{
+ Runner: Runner{},
+ }
+
+ t.Run("Public instance tuning", func(t *testing.T) {
+ c.Runner.FetchInterval = 60 * time.Second
+ c.Tune("https://codeberg.org")
+ assert.EqualValues(t, 60*time.Second, c.Runner.FetchInterval)
+
+ c.Runner.FetchInterval = 2 * time.Second
+ c.Tune("https://codeberg.org")
+ assert.EqualValues(t, 30*time.Second, c.Runner.FetchInterval)
+ })
+
+ t.Run("Non-public instance tuning", func(t *testing.T) {
+ c.Runner.FetchInterval = 60 * time.Second
+ c.Tune("https://example.com")
+ assert.EqualValues(t, 60*time.Second, c.Runner.FetchInterval)
+
+ c.Runner.FetchInterval = 2 * time.Second
+ c.Tune("https://codeberg.com")
+ assert.EqualValues(t, 2*time.Second, c.Runner.FetchInterval)
+ })
+}
diff --git a/internal/pkg/config/deprecated.go b/internal/pkg/config/deprecated.go
new file mode 100644
index 0000000..b5051aa
--- /dev/null
+++ b/internal/pkg/config/deprecated.go
@@ -0,0 +1,62 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package config
+
+import (
+ "os"
+ "strconv"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// Deprecated: could be removed in the future. TODO: remove it when Gitea 1.20.0 is released.
+// Be compatible with old envs.
+func compatibleWithOldEnvs(fileUsed bool, cfg *Config) {
+ handleEnv := func(key string) (string, bool) {
+ if v, ok := os.LookupEnv(key); ok {
+ if fileUsed {
+ log.Warnf("env %s has been ignored because config file is used", key)
+ return "", false
+ }
+ log.Warnf("env %s will be deprecated, please use config file instead", key)
+ return v, true
+ }
+ return "", false
+ }
+
+ if v, ok := handleEnv("GITEA_DEBUG"); ok {
+ if b, _ := strconv.ParseBool(v); b {
+ cfg.Log.Level = "debug"
+ }
+ }
+ if v, ok := handleEnv("GITEA_TRACE"); ok {
+ if b, _ := strconv.ParseBool(v); b {
+ cfg.Log.Level = "trace"
+ }
+ }
+ if v, ok := handleEnv("GITEA_RUNNER_CAPACITY"); ok {
+ if i, _ := strconv.Atoi(v); i > 0 {
+ cfg.Runner.Capacity = i
+ }
+ }
+ if v, ok := handleEnv("GITEA_RUNNER_FILE"); ok {
+ cfg.Runner.File = v
+ }
+ if v, ok := handleEnv("GITEA_RUNNER_ENVIRON"); ok {
+ splits := strings.Split(v, ",")
+ if cfg.Runner.Envs == nil {
+ cfg.Runner.Envs = map[string]string{}
+ }
+ for _, split := range splits {
+ kv := strings.SplitN(split, ":", 2)
+ if len(kv) == 2 && kv[0] != "" {
+ cfg.Runner.Envs[kv[0]] = kv[1]
+ }
+ }
+ }
+ if v, ok := handleEnv("GITEA_RUNNER_ENV_FILE"); ok {
+ cfg.Runner.EnvFile = v
+ }
+}
diff --git a/internal/pkg/config/embed.go b/internal/pkg/config/embed.go
new file mode 100644
index 0000000..cf445cf
--- /dev/null
+++ b/internal/pkg/config/embed.go
@@ -0,0 +1,9 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package config
+
+import _ "embed"
+
+//go:embed config.example.yaml
+var Example []byte
diff --git a/internal/pkg/config/registration.go b/internal/pkg/config/registration.go
new file mode 100644
index 0000000..be66b4f
--- /dev/null
+++ b/internal/pkg/config/registration.go
@@ -0,0 +1,54 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package config
+
+import (
+ "encoding/json"
+ "os"
+)
+
+const registrationWarning = "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner."
+
+// Registration is the registration information for a runner
+type Registration struct {
+ Warning string `json:"WARNING"` // Warning message to display, it's always the registrationWarning constant
+
+ ID int64 `json:"id"`
+ UUID string `json:"uuid"`
+ Name string `json:"name"`
+ Token string `json:"token"`
+ Address string `json:"address"`
+ Labels []string `json:"labels"`
+}
+
+func LoadRegistration(file string) (*Registration, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var reg Registration
+ if err := json.NewDecoder(f).Decode(&reg); err != nil {
+ return nil, err
+ }
+
+ reg.Warning = ""
+
+ return &reg, nil
+}
+
+func SaveRegistration(file string, reg *Registration) error {
+ f, err := os.Create(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ reg.Warning = registrationWarning
+
+ enc := json.NewEncoder(f)
+ enc.SetIndent("", " ")
+ return enc.Encode(reg)
+}