245 lines
7.1 KiB
Go
245 lines
7.1 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/burrow/forgejo-nsc/internal/forgejo"
|
|
)
|
|
|
|
// Duration wraps time.Duration to support YAML unmarshalling from strings.
|
|
type Duration struct {
|
|
time.Duration
|
|
}
|
|
|
|
// UnmarshalYAML implements yaml.v3 unmarshalling for Duration.
|
|
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
|
|
switch value.Tag {
|
|
case "!!int":
|
|
var seconds int64
|
|
if err := value.Decode(&seconds); err != nil {
|
|
return err
|
|
}
|
|
d.Duration = time.Duration(seconds) * time.Second
|
|
return nil
|
|
default:
|
|
parsed, err := time.ParseDuration(value.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.Duration = parsed
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MarshalYAML implements yaml.v3 marshalling.
|
|
func (d Duration) MarshalYAML() (any, error) {
|
|
return d.Duration.String(), nil
|
|
}
|
|
|
|
type Config struct {
|
|
Listen string `yaml:"listen"`
|
|
Forgejo ForgejoConfig `yaml:"forgejo"`
|
|
Namespace NamespaceConfig `yaml:"namespace"`
|
|
Runner RunnerConfig `yaml:"runner"`
|
|
}
|
|
|
|
type CacheVolumeConfig struct {
|
|
Tag string `yaml:"tag"`
|
|
MountPoint string `yaml:"mount_point"`
|
|
SizeGb int64 `yaml:"size_gb"`
|
|
}
|
|
|
|
type ForgejoConfig struct {
|
|
BaseURL string `yaml:"base_url"`
|
|
// InstanceURL is the URL runners should use when registering with Forgejo.
|
|
// This must be reachable from the spawned runner (e.g. the public URL like
|
|
// https://git.burrow.net), and may differ from BaseURL (which can be a local
|
|
// loopback URL on the forge host).
|
|
InstanceURL string `yaml:"instance_url"`
|
|
Token string `yaml:"token"`
|
|
DefaultScope ScopeConfig `yaml:"default_scope"`
|
|
DefaultLabels []string `yaml:"default_labels"`
|
|
Timeout Duration `yaml:"timeout"`
|
|
ExtraHeaders yaml.Node `yaml:"extra_headers"`
|
|
}
|
|
|
|
type ScopeConfig struct {
|
|
Level string `yaml:"level"`
|
|
Owner string `yaml:"owner,omitempty"`
|
|
Name string `yaml:"name,omitempty"`
|
|
}
|
|
|
|
type NamespaceConfig struct {
|
|
NSCBinary string `yaml:"nsc_binary"`
|
|
// ComputeBaseURL is the Namespace Cloud Compute API endpoint (Connect RPC base URL).
|
|
// This is used for macOS runners, since NSC "run" is container-based (Linux-only).
|
|
// Example: "https://ord4.compute.namespaceapis.com"
|
|
ComputeBaseURL string `yaml:"compute_base_url"`
|
|
Image string `yaml:"image"`
|
|
MachineType string `yaml:"machine_type"`
|
|
// MacosBaseImageID selects which macOS base image to use (e.g. "tahoe").
|
|
MacosBaseImageID string `yaml:"macos_base_image_id"`
|
|
// MacosMachineArch is the architecture used for macOS instances (typically "arm64").
|
|
MacosMachineArch string `yaml:"macos_machine_arch"`
|
|
Duration Duration `yaml:"duration"`
|
|
WorkDir string `yaml:"workdir"`
|
|
MaxParallel int64 `yaml:"max_parallel"`
|
|
Environment []string `yaml:"environment"`
|
|
AllowLabels []string `yaml:"allow_labels"`
|
|
AllowScopes []string `yaml:"allow_scopes"`
|
|
Network string `yaml:"network"`
|
|
InstanceTags []string `yaml:"instance_tags"`
|
|
LinuxCachePath string `yaml:"linux_cache_path"`
|
|
LinuxCacheVolumes []CacheVolumeConfig `yaml:"linux_cache_volumes"`
|
|
MacosCachePath string `yaml:"macos_cache_path"`
|
|
MacosCacheVolumes []CacheVolumeConfig `yaml:"macos_cache_volumes"`
|
|
}
|
|
|
|
type RunnerConfig struct {
|
|
NamePrefix string `yaml:"name_prefix"`
|
|
Executor string `yaml:"executor"`
|
|
}
|
|
|
|
func Load(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := cfg.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
if c.Listen == "" {
|
|
c.Listen = ":8080"
|
|
}
|
|
if c.Runner.NamePrefix == "" {
|
|
c.Runner.NamePrefix = "nscloud-"
|
|
}
|
|
if c.Runner.Executor == "" {
|
|
c.Runner.Executor = "shell"
|
|
}
|
|
|
|
if c.Forgejo.BaseURL == "" {
|
|
return errors.New("forgejo.base_url is required")
|
|
}
|
|
if c.Forgejo.InstanceURL == "" {
|
|
// Backwards-compatible default: assume runners can reach the same URL.
|
|
c.Forgejo.InstanceURL = c.Forgejo.BaseURL
|
|
}
|
|
if c.Forgejo.Token == "" {
|
|
return errors.New("forgejo.token is required")
|
|
}
|
|
if c.Forgejo.Timeout.Duration == 0 {
|
|
c.Forgejo.Timeout.Duration = 30 * time.Second
|
|
}
|
|
if _, err := c.Forgejo.DefaultScope.ToScope(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.Namespace.NSCBinary == "" {
|
|
c.Namespace.NSCBinary = "nsc"
|
|
}
|
|
if c.Namespace.Image == "" {
|
|
c.Namespace.Image = "code.forgejo.org/forgejo/runner:11"
|
|
}
|
|
if c.Namespace.MacosBaseImageID == "" {
|
|
c.Namespace.MacosBaseImageID = "tahoe"
|
|
}
|
|
if c.Namespace.MacosMachineArch == "" {
|
|
c.Namespace.MacosMachineArch = "arm64"
|
|
}
|
|
if c.Namespace.Duration.Duration == 0 {
|
|
c.Namespace.Duration.Duration = 30 * time.Minute
|
|
}
|
|
if c.Namespace.MaxParallel <= 0 {
|
|
c.Namespace.MaxParallel = 4
|
|
}
|
|
if c.Namespace.LinuxCachePath == "" {
|
|
c.Namespace.LinuxCachePath = "/var/cache/burrow"
|
|
}
|
|
if len(c.Namespace.LinuxCacheVolumes) == 0 {
|
|
c.Namespace.LinuxCacheVolumes = []CacheVolumeConfig{
|
|
{
|
|
Tag: "burrow-forgejo-linux-nix",
|
|
MountPoint: "/nix",
|
|
SizeGb: 60,
|
|
},
|
|
{
|
|
Tag: "burrow-forgejo-linux-cache",
|
|
MountPoint: c.Namespace.LinuxCachePath,
|
|
SizeGb: 40,
|
|
},
|
|
}
|
|
}
|
|
if c.Namespace.MacosCachePath == "" {
|
|
c.Namespace.MacosCachePath = "/Users/runner/.cache/burrow"
|
|
}
|
|
if len(c.Namespace.MacosCacheVolumes) == 0 {
|
|
c.Namespace.MacosCacheVolumes = []CacheVolumeConfig{
|
|
{
|
|
Tag: "burrow-forgejo-macos-shared-v1",
|
|
MountPoint: c.Namespace.MacosCachePath + "/shared",
|
|
SizeGb: 80,
|
|
},
|
|
{
|
|
Tag: "burrow-forgejo-macos-macos-v1",
|
|
MountPoint: c.Namespace.MacosCachePath + "/lane/macos",
|
|
SizeGb: 80,
|
|
},
|
|
{
|
|
Tag: "burrow-forgejo-macos-ios-simulator-v1",
|
|
MountPoint: c.Namespace.MacosCachePath + "/lane/ios-simulator",
|
|
SizeGb: 80,
|
|
},
|
|
}
|
|
}
|
|
for _, volume := range append(append([]CacheVolumeConfig{}, c.Namespace.LinuxCacheVolumes...), c.Namespace.MacosCacheVolumes...) {
|
|
if strings.TrimSpace(volume.Tag) == "" {
|
|
return errors.New("namespace cache volume tag is required")
|
|
}
|
|
if strings.TrimSpace(volume.MountPoint) == "" {
|
|
return fmt.Errorf("namespace cache volume %q mount_point is required", volume.Tag)
|
|
}
|
|
if volume.SizeGb <= 0 {
|
|
return fmt.Errorf("namespace cache volume %q size_gb must be positive", volume.Tag)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s ScopeConfig) ToScope() (forgejo.Scope, error) {
|
|
level := forgejo.ScopeLevel(strings.ToLower(s.Level))
|
|
switch level {
|
|
case forgejo.ScopeInstance:
|
|
return forgejo.Scope{Level: level}, nil
|
|
case forgejo.ScopeOrganization:
|
|
if s.Owner == "" {
|
|
return forgejo.Scope{}, errors.New("forgejo default scope requires owner for organization level")
|
|
}
|
|
return forgejo.Scope{Level: level, Owner: s.Owner}, nil
|
|
case forgejo.ScopeRepository:
|
|
if s.Owner == "" || s.Name == "" {
|
|
return forgejo.Scope{}, errors.New("forgejo default scope requires owner and name for repository level")
|
|
}
|
|
return forgejo.Scope{Level: level, Owner: s.Owner, Name: s.Name}, nil
|
|
default:
|
|
return forgejo.Scope{}, fmt.Errorf("unknown scope level %q", s.Level)
|
|
}
|
|
}
|