forgejo-runner

git clone git://git.lin.moe/forgejo-runner.git

  1// Copyright 2022 The Gitea Authors. All rights reserved.
  2// SPDX-License-Identifier: MIT
  3
  4package config
  5
  6import (
  7	"fmt"
  8	"os"
  9	"path/filepath"
 10	"time"
 11
 12	"github.com/joho/godotenv"
 13	log "github.com/sirupsen/logrus"
 14	"gopkg.in/yaml.v3"
 15)
 16
 17// Log represents the configuration for logging.
 18type Log struct {
 19	Level string `yaml:"level"` // Level indicates the logging level.
 20}
 21
 22// Runner represents the configuration for the runner.
 23type Runner struct {
 24	File            string            `yaml:"file"`             // File specifies the file path for the runner.
 25	Capacity        int               `yaml:"capacity"`         // Capacity specifies the capacity of the runner.
 26	Envs            map[string]string `yaml:"envs"`             // Envs stores environment variables for the runner.
 27	EnvFile         string            `yaml:"env_file"`         // EnvFile specifies the path to the file containing environment variables for the runner.
 28	Timeout         time.Duration     `yaml:"timeout"`          // Timeout specifies the duration for runner timeout.
 29	ShutdownTimeout time.Duration     `yaml:"shutdown_timeout"` // ShutdownTimeout specifies the duration to wait for running jobs to complete during a shutdown of the runner.
 30	Insecure        bool              `yaml:"insecure"`         // Insecure indicates whether the runner operates in an insecure mode.
 31	FetchTimeout    time.Duration     `yaml:"fetch_timeout"`    // FetchTimeout specifies the timeout duration for fetching resources.
 32	FetchInterval   time.Duration     `yaml:"fetch_interval"`   // FetchInterval specifies the interval duration for fetching resources.
 33	ReportInterval  time.Duration     `yaml:"report_interval"`  // ReportInterval specifies the interval duration for reporting status and logs of a running job.
 34	Labels          []string          `yaml:"labels"`           // Labels specify the labels of the runner. Labels are declared on each startup
 35}
 36
 37// Cache represents the configuration for caching.
 38type Cache struct {
 39	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.
 40	Dir            string `yaml:"dir"`             // Dir specifies the directory path for caching.
 41	Host           string `yaml:"host"`            // Host specifies the caching host.
 42	Port           uint16 `yaml:"port"`            // Port specifies the caching port.
 43	ExternalServer string `yaml:"external_server"` // ExternalServer specifies the URL of external cache server
 44}
 45
 46// Container represents the configuration for the container.
 47type Container struct {
 48	Network       string   `yaml:"network"`        // Network specifies the network for the container.
 49	NetworkMode   string   `yaml:"network_mode"`   // Deprecated: use Network instead. Could be removed after Gitea 1.20
 50	EnableIPv6    bool     `yaml:"enable_ipv6"`    // EnableIPv6 indicates whether the network is created with IPv6 enabled.
 51	Privileged    bool     `yaml:"privileged"`     // Privileged indicates whether the container runs in privileged mode.
 52	Options       string   `yaml:"options"`        // Options specifies additional options for the container.
 53	WorkdirParent string   `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory.
 54	ValidVolumes  []string `yaml:"valid_volumes"`  // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers.
 55	DockerHost    string   `yaml:"docker_host"`    // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST.
 56	ForcePull     bool     `yaml:"force_pull"`     // Pull docker image(s) even if already present
 57}
 58
 59// Host represents the configuration for the host.
 60type Host struct {
 61	WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the host's working directory.
 62}
 63
 64// Config represents the overall configuration.
 65type Config struct {
 66	Log       Log       `yaml:"log"`       // Log represents the configuration for logging.
 67	Runner    Runner    `yaml:"runner"`    // Runner represents the configuration for the runner.
 68	Cache     Cache     `yaml:"cache"`     // Cache represents the configuration for caching.
 69	Container Container `yaml:"container"` // Container represents the configuration for the container.
 70	Host      Host      `yaml:"host"`      // Host represents the configuration for the host.
 71}
 72
 73// Tune the config settings accordingly to the Forgejo instance that will be used.
 74func (c *Config) Tune(instanceURL string) {
 75	if instanceURL == "https://codeberg.org" {
 76		if c.Runner.FetchInterval < 30*time.Second {
 77			log.Info("The runner is configured to be used by a public instance, fetch interval is set to 30 seconds.")
 78			c.Runner.FetchInterval = 30 * time.Second
 79		}
 80	}
 81}
 82
 83// LoadDefault returns the default configuration.
 84// If file is not empty, it will be used to load the configuration.
 85func LoadDefault(file string) (*Config, error) {
 86	cfg := &Config{}
 87	if file != "" {
 88		content, err := os.ReadFile(file)
 89		if err != nil {
 90			return nil, fmt.Errorf("open config file %q: %w", file, err)
 91		}
 92		if err := yaml.Unmarshal(content, cfg); err != nil {
 93			return nil, fmt.Errorf("parse config file %q: %w", file, err)
 94		}
 95	}
 96	compatibleWithOldEnvs(file != "", cfg)
 97
 98	if cfg.Runner.EnvFile != "" {
 99		if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() {
100			envs, err := godotenv.Read(cfg.Runner.EnvFile)
101			if err != nil {
102				return nil, fmt.Errorf("read env file %q: %w", cfg.Runner.EnvFile, err)
103			}
104			if cfg.Runner.Envs == nil {
105				cfg.Runner.Envs = map[string]string{}
106			}
107			for k, v := range envs {
108				cfg.Runner.Envs[k] = v
109			}
110		}
111	}
112
113	if cfg.Log.Level == "" {
114		cfg.Log.Level = "info"
115	}
116	if cfg.Runner.File == "" {
117		cfg.Runner.File = ".runner"
118	}
119	if cfg.Runner.Capacity <= 0 {
120		cfg.Runner.Capacity = 1
121	}
122	if cfg.Runner.Timeout <= 0 {
123		cfg.Runner.Timeout = 3 * time.Hour
124	}
125	if cfg.Cache.Enabled == nil {
126		b := true
127		cfg.Cache.Enabled = &b
128	}
129	if *cfg.Cache.Enabled {
130		if cfg.Cache.Dir == "" {
131			home, _ := os.UserHomeDir()
132			cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache")
133		}
134	}
135	if cfg.Container.WorkdirParent == "" {
136		cfg.Container.WorkdirParent = "workspace"
137	}
138	if cfg.Host.WorkdirParent == "" {
139		home, _ := os.UserHomeDir()
140		cfg.Host.WorkdirParent = filepath.Join(home, ".cache", "act")
141	}
142	if cfg.Runner.FetchTimeout <= 0 {
143		cfg.Runner.FetchTimeout = 5 * time.Second
144	}
145	if cfg.Runner.FetchInterval <= 0 {
146		cfg.Runner.FetchInterval = 2 * time.Second
147	}
148	if cfg.Runner.ReportInterval <= 0 {
149		cfg.Runner.ReportInterval = time.Second
150	}
151
152	// although `container.network_mode` will be deprecated, but we have to be compatible with it for now.
153	if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" {
154		log.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.")
155		if cfg.Container.NetworkMode == "bridge" {
156			// Previously, if the value of `container.network_mode` is `bridge`, we will create a new network for job.
157			// But “bridge” is easily confused with the bridge network created by Docker by default.
158			// So we set the value of `container.network` to empty string to make `act_runner` automatically create a new network for job.
159			cfg.Container.Network = ""
160		} else {
161			cfg.Container.Network = cfg.Container.NetworkMode
162		}
163	}
164
165	return cfg, nil
166}