config

package
v0.3.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 22 Imported by: 0

README

Configuration Layer

This package handles all configuration management for UniRTM.

Responsibilities

  • Parse TOML and YAML configuration files
  • Implement hierarchical configuration loading (system → global → project → local)
  • Validate configuration structures
  • Merge configurations with proper precedence rules
  • Handle environment-specific overrides

Key Components

  • config.go - Core configuration data structures ✅ Implemented
  • manager.go - Configuration manager implementation (planned)
  • validator.go - Configuration validation logic (integrated in config.go)
  • parser.go - Configuration parsing utilities (planned)

Data Structures

Config

Root configuration structure containing:

  • Tools: Map of tool names to version specifications
  • Env: Environment variable definitions
  • Settings: Global settings (cache, data directories, TTL, concurrency)
  • Tasks: Task definitions with dependencies
ToolConfig

Tool version specification with:

  • Version: Required version string (exact, range, or alias)
  • Backend: Optional backend name (github, aqua, http)
  • Provider: Optional provider name (node, python, go, generic)
Settings

Global settings with:

  • CacheDir: Cache directory path
  • DataDir: Data directory path
  • CacheTTL: Cache time-to-live in seconds
  • Concurrency: Maximum concurrent operations
Task

Task definition with:

  • Description: Human-readable description
  • Run: Command to execute
  • Env: Task-specific environment variables
  • Depends: List of task dependencies

Validation

All structures implement Validate() methods that:

  • Check required fields are present
  • Validate field values are within acceptable ranges
  • Report all validation errors (not just the first one)
  • Detect circular task dependencies

Usage

import "github.com/snowdreamtech/unirtm/internal/config"

// Create configuration
cfg := config.Config{
    Tools: map[string]config.ToolConfig{
        "node": {Version: "20.0.0"},
    },
    Settings: config.Settings{
        CacheTTL: 3600,
        Concurrency: 4,
    },
}

// Validate configuration
if err := cfg.Validate(); err != nil {
    log.Fatal(err)
}

Testing

Comprehensive unit tests with 100% code coverage:

go test -v ./internal/config/
go test -race -cover ./internal/config/

Status

  • Task 2.1 Complete: Configuration data structures with TOML/YAML tags and validation methods
  • 🚧 Task 2.2 Planned: ConfigManager interface with go-toml/yaml.v3 integration
  • 🚧 Task 2.3-2.6 Planned: Property-based tests and environment overrides

Requirements

Implements requirements:

  • 1.3: Configuration validation with required field checking
  • 26.1: TOML tag support for all structures
  • 26.2: YAML tag support for all structures
  • 🚧 1.1, 1.2, 1.4-1.8: Configuration parsing and loading (planned)
  • 🚧 26.3-26.8: Round-trip properties and pretty printing (planned)

Documentation

Overview

Package config provides configuration management for UniRTM.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	OsReadFile  = os.ReadFile
	OsWriteFile = os.WriteFile
	OsStat      = os.Stat
	OsMkdirAll  = os.MkdirAll
	OsRemove    = os.Remove
	OsReadDir   = os.ReadDir
	OsOpen      = os.Open
	OsOpenFile  = os.OpenFile

	FilepathAbs = filepath.Abs
)

Functions

func FormatFile

func FormatFile(path string, fmtCheck bool) (bool, error)

FormatFile formats the configuration file at the given path with canonical ordering. It returns true if the file content was modified, and an error if one occurred. If fmtCheck is true, it does not write changes back to the disk.

func FormatTOML

func FormatTOML(content string) (string, error)

FormatTOML formats TOML content strings with canonical key ordering.

func GetGlobalConfigPath

func GetGlobalConfigPath() string

GetGlobalConfigPath returns the path to the global unirtm.toml configuration file. (Copied from env package to avoid circular dependency if needed, or just use full path)

func LoadRawTOML

func LoadRawTOML(path string) (map[string]interface{}, error)

LoadRawTOML reads a TOML file into a generic map. Returns an empty map when the file does not yet exist.

func ParseDurationToSeconds

func ParseDurationToSeconds(s string) (int, error)

ParseDurationToSeconds parses a human-readable duration string (e.g., "7d", "24h", "30m", "60s") into a number of seconds. It is exported so that service-layer code can parse minimum_release_age values without duplicating the logic.

func ReadFileOrEmpty

func ReadFileOrEmpty(path string) (string, error)

ReadFileOrEmpty reads a file content or returns an empty string if the file doesn't exist.

func SaveRawTOML

func SaveRawTOML(path string, m map[string]interface{}) error

SaveRawTOML writes a generic map to a TOML file and formats it.

func UnsetEnvVar

func UnsetEnvVar(content, key string) (string, bool)

UnsetEnvVar removes an environment variable entry from the TOML env section.

func UpsertEnvVar

func UpsertEnvVar(content, key, value string) string

UpsertEnvVar adds or updates an environment variable entry in the TOML env section.

func UpsertToolVersion

func UpsertToolVersion(content, tool, version string) string

UpsertToolVersion adds or updates a tool version entry in the TOML [tools] section.

Types

type Config

type Config struct {
	Tools        ToolMap                      `toml:"-" yaml:"-" mapstructure:"-"`
	ToolsRaw     map[string]interface{}       `toml:"tools" yaml:"tools" mapstructure:"tools"`
	Env          map[string]interface{}       `toml:"env" yaml:"env" mapstructure:"env"`
	Settings     Settings                     `toml:"settings" yaml:"settings" mapstructure:"settings"`
	Tasks        map[string]Task              `toml:"tasks" yaml:"tasks" mapstructure:"tasks"`
	Environments map[string]EnvironmentConfig `toml:"environments,omitempty" yaml:"environments,omitempty" mapstructure:"environments,omitempty"`
	Aliases      map[string]map[string]string `toml:"aliases,omitempty" yaml:"aliases,omitempty" mapstructure:"aliases,omitempty"`
}
Example

ExampleConfig demonstrates basic usage of the Config structure.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/config"
)

func main() {
	cfg := config.Config{
		Tools: map[string]config.ToolConfig{
			"node": {
				Version: "20.0.0",
				Backend: "github",
			},
			"python": {
				Version: "3.11.0",
			},
		},
		Env: map[string]interface{}{
			"NODE_ENV": "development",
		},
		Settings: config.Settings{
			CacheDir: "/tmp/unirtm/cache",
			DataDir:  "/tmp/unirtm/data",
			CacheTTL: 3600,
		},
		Tasks: map[string]config.Task{
			"build": {
				Description: "Build the project",
				Run:         "go build -o bin/app",
			},
			"test": {
				Description: "Run tests",
				Run:         "go test ./...",
				Depends:     []string{"build"},
			},
		},
	}

	if err := cfg.Validate(); err != nil {
		fmt.Printf("Validation failed: %v\n", err)
		return
	}

	fmt.Println("Configuration is valid")
}
Output:
Configuration is valid

func Load

func Load() (*Config, error)

Load loads the UniRTM project configuration from the current directory.

func LoadFromDir

func LoadFromDir(dir string) (*Config, error)

LoadFromDir loads the UniRTM project configuration from the specified directory.

func LoadFull

func LoadFull() (*Config, error)

LoadFull loads the UniRTM configuration by merging current directory configs, parent directory configs, and the global configuration (hierarchy). This provides full alignment with mise's hierarchical config loading.

func LoadGlobal

func LoadGlobal() (*Config, error)

LoadGlobal loads only the global UniRTM configuration.

func LoadHierarchy

func LoadHierarchy(startDir string) (*Config, error)

LoadHierarchy loads the UniRTM configuration by merging configs starting from the given directory, parent directories up to root, and the global configuration.

func (*Config) ApplyEnvironment

func (c *Config) ApplyEnvironment()

ApplyEnvironment sets the environment variables defined in the configuration to the current process environment.

func (*Config) Merge

func (c *Config) Merge(other *Config)

Merge merges another configuration into this one. The current configuration takes precedence over the other one (deep merge).

func (*Config) PostLoad

func (c *Config) PostLoad()

func (*Config) ResolveAlias

func (c *Config) ResolveAlias(tool, version string) string

ResolveAlias returns the aliased version for a tool if it exists. Otherwise, it returns the original version request.

func (*Config) ResolveEnvironment

func (c *Config) ResolveEnvironment() (map[string]string, []string, []string, error)

ResolveEnvironment resolves environment variables defined in the configuration. It returns a map of rendered environment variables, a list of scripts to source, a list of redacted keys, and an error if any required variables are missing. It supports pongo2 (Jinja2-like) templates.

func (*Config) Validate

func (c *Config) Validate() error
Example (Error)

ExampleConfig_Validate_error demonstrates validation error handling.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/config"
)

func main() {
	cfg := config.Config{
		Tools: map[string]config.ToolConfig{
			"node": {
				Version: "", // Invalid: empty version
			},
		},
		Settings: config.Settings{
			CacheTTL: -100, // Invalid: negative value
		},
	}

	if err := cfg.Validate(); err != nil {
		fmt.Println("Validation failed as expected")
	}
}
Output:
Validation failed as expected

type ConfigManager

type ConfigManager interface {
	// Load loads configuration from the specified path.
	// Returns an error if the file cannot be read or parsed.
	Load(ctx context.Context, path string) (*Config, error)

	// LoadHierarchy loads configuration from all hierarchy levels.
	// Hierarchy: system → global → project → local
	// Returns the merged configuration with local overriding project overriding global overriding system.
	LoadHierarchy(ctx context.Context) (*Config, error)

	// LoadWithEnvironment loads configuration from all hierarchy levels and applies
	// environment-specific overrides for the specified environment.
	// Returns the merged configuration with environment overrides applied.
	LoadWithEnvironment(ctx context.Context, environment string) (*Config, error)

	// Validate validates the configuration.
	// Returns an error if validation fails, with all validation errors reported.
	Validate(ctx context.Context, config *Config) error

	// Merge merges multiple configurations with precedence rules.
	// Later configurations in the slice override earlier ones.
	// Returns the merged configuration or an error if merging fails.
	Merge(configs ...*Config) (*Config, error)

	// ApplyEnvironment applies environment-specific overrides to a configuration.
	// Returns a new configuration with the environment overrides applied.
	ApplyEnvironment(config *Config, environment string) (*Config, error)
}

ConfigManager defines the interface for configuration management operations.

It provides methods for loading, validating, and merging configuration files from multiple sources with hierarchical precedence rules.

func NewConfigManager

func NewConfigManager() ConfigManager

NewConfigManager creates a new ConfigManager instance.

It parses TOML and YAML configuration files and supports hierarchical configuration loading with proper precedence rules.

type DurationOrInt

type DurationOrInt int

DurationOrInt represents a duration in seconds, which can be parsed from either an integer number of seconds or a duration string (e.g. "30s", "1h", "20m").

func (*DurationOrInt) UnmarshalJSON

func (d *DurationOrInt) UnmarshalJSON(data []byte) error

func (*DurationOrInt) UnmarshalText

func (d *DurationOrInt) UnmarshalText(text []byte) error

func (*DurationOrInt) UnmarshalYAML

func (d *DurationOrInt) UnmarshalYAML(value *yaml.Node) error

type EnvironmentConfig

type EnvironmentConfig struct {
	Tools    ToolMap                `toml:"-" yaml:"-" mapstructure:"-"`
	ToolsRaw map[string]interface{} `toml:"tools,omitempty" yaml:"tools,omitempty" mapstructure:"tools,omitempty"`
	Env      map[string]interface{} `toml:"env,omitempty" yaml:"env,omitempty" mapstructure:"env,omitempty"`
	Settings Settings               `toml:"settings,omitempty" yaml:"settings,omitempty" mapstructure:"settings,omitempty"`
	Tasks    map[string]Task        `toml:"tasks,omitempty" yaml:"tasks,omitempty" mapstructure:"tasks,omitempty"`
}

func (*EnvironmentConfig) PostLoad

func (ec *EnvironmentConfig) PostLoad()

func (*EnvironmentConfig) Validate

func (ec *EnvironmentConfig) Validate() error

type Settings

type Settings struct {
	CacheDir           string                            `toml:"cache_dir" yaml:"cache_dir" mapstructure:"cache_dir"`
	DataDir            string                            `toml:"data_dir" yaml:"data_dir" mapstructure:"data_dir"`
	CacheTTL           DurationOrInt                     `toml:"cache_ttl" yaml:"cache_ttl" mapstructure:"cache_ttl"`
	Lockfile           bool                              `toml:"lockfile,omitempty" yaml:"lockfile,omitempty" mapstructure:"lockfile,omitempty"`
	Locked             bool                              `toml:"locked,omitempty" yaml:"locked,omitempty" mapstructure:"locked,omitempty"`
	GitHubProxy        string                            `toml:"github_proxy,omitempty" yaml:"github_proxy,omitempty" mapstructure:"github_proxy,omitempty"`
	HttpProxy          string                            `toml:"http_proxy,omitempty" yaml:"http_proxy,omitempty" mapstructure:"http_proxy,omitempty"`
	HttpsProxy         string                            `toml:"https_proxy,omitempty" yaml:"https_proxy,omitempty" mapstructure:"https_proxy,omitempty"`
	GitHubToken        string                            `toml:"github_token,omitempty" yaml:"github_token,omitempty" mapstructure:"github_token,omitempty"`
	HTTPTimeout        DurationOrInt                     `toml:"http_timeout,omitempty" yaml:"http_timeout,omitempty" mapstructure:"http_timeout,omitempty"`
	TaskTimeout        DurationOrInt                     `toml:"task_timeout,omitempty" yaml:"task_timeout,omitempty" mapstructure:"task_timeout,omitempty"`
	TaskOutput         string                            `toml:"task_output,omitempty" yaml:"task_output,omitempty" mapstructure:"task_output,omitempty"`
	Experimental       bool                              `toml:"experimental,omitempty" yaml:"experimental,omitempty" mapstructure:"experimental,omitempty"`
	AutoInstall        *bool                             `toml:"auto_install,omitempty" yaml:"auto_install,omitempty" mapstructure:"auto_install,omitempty"`
	Color              string                            `toml:"color,omitempty" yaml:"color,omitempty" mapstructure:"color,omitempty"`
	Editor             string                            `toml:"editor,omitempty" yaml:"editor,omitempty" mapstructure:"editor,omitempty"`
	Shell              string                            `toml:"shell,omitempty" yaml:"shell,omitempty" mapstructure:"shell,omitempty"`
	AlwaysKeepDownload bool                              `toml:"always_keep_download,omitempty" yaml:"always_keep_download,omitempty" mapstructure:"always_keep_download,omitempty"`
	CeilingPaths       []string                          `toml:"ceiling_paths,omitempty" yaml:"ceiling_paths,omitempty" mapstructure:"ceiling_paths,omitempty"`
	TrustedConfigPaths []string                          `toml:"trusted_config_paths,omitempty" yaml:"trusted_config_paths,omitempty" mapstructure:"trusted_config_paths,omitempty"`
	GPGVerify          string                            `toml:"gpg_verify" yaml:"gpg_verify" mapstructure:"gpg_verify"`
	GPGKeys            []string                          `toml:"gpg_keys" yaml:"gpg_keys" mapstructure:"gpg_keys"`
	VerifyMetadata     *bool                             `toml:"verify_metadata,omitempty" yaml:"verify_metadata,omitempty" mapstructure:"verify_metadata,omitempty"`
	NoProxy            []string                          `toml:"no_proxy,omitempty" yaml:"no_proxy,omitempty" mapstructure:"no_proxy,omitempty"`
	Jobs               int                               `toml:"jobs,omitempty" yaml:"jobs,omitempty" mapstructure:"jobs,omitempty"`
	Tools              map[string]map[string]interface{} `toml:"tools,omitempty" yaml:"tools,omitempty" mapstructure:"tools,omitempty"`
	MinimumReleaseAge  string                            `toml:"minimum_release_age,omitempty" yaml:"minimum_release_age,omitempty" mapstructure:"minimum_release_age,omitempty"`
}
Example

ExampleSettings demonstrates settings configuration.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/config"
)

func main() {
	settings := config.Settings{
		CacheDir: "/var/cache/unirtm",
		DataDir:  "/var/lib/unirtm",
		CacheTTL: 86400, // 24 hours
	}

	if err := settings.Validate(); err != nil {
		fmt.Printf("Validation failed: %v\n", err)
		return
	}

	fmt.Println("Settings are valid")
}
Output:
Settings are valid

func (*Settings) LoadFromEnv

func (s *Settings) LoadFromEnv()

func (*Settings) SetDefaults

func (s *Settings) SetDefaults()

SetDefaults applies sensible defaults for settings that were not provided either via config file or environment variables.

func (*Settings) Validate

func (s *Settings) Validate() error

type Task

type Task struct {
	Description string                 `toml:"description" yaml:"description" mapstructure:"description"`
	Run         string                 `toml:"run" yaml:"run" mapstructure:"run"`
	Env         map[string]interface{} `toml:"env,omitempty" yaml:"env,omitempty" mapstructure:"env,omitempty"`
	Depends     []string               `toml:"depends,omitempty" yaml:"depends,omitempty" mapstructure:"depends,omitempty"`
	Timeout     int                    `toml:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"`
	Output      string                 `toml:"output,omitempty" yaml:"output,omitempty" mapstructure:"output,omitempty"`
}
Example

ExampleTask demonstrates task configuration with dependencies.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/config"
)

func main() {
	task := config.Task{
		Description: "Deploy to production",
		Run:         "./deploy.sh production",
		Env: map[string]interface{}{
			"ENVIRONMENT": "production",
		},
		Depends: []string{"test", "build"},
	}

	if err := task.Validate(); err != nil {
		fmt.Printf("Validation failed: %v\n", err)
		return
	}

	fmt.Println("Task configuration is valid")
}
Output:
Task configuration is valid

func (*Task) Validate

func (t *Task) Validate() error

type ToolConfig

type ToolConfig struct {
	Version           string   `toml:"version" yaml:"version" mapstructure:"version"`
	Backend           string   `toml:"backend,omitempty" yaml:"backend,omitempty" mapstructure:"backend,omitempty"`
	Provider          string   `toml:"provider,omitempty" yaml:"provider,omitempty" mapstructure:"provider,omitempty"`
	PreInstall        string   `toml:"pre_install,omitempty" yaml:"pre_install,omitempty" mapstructure:"pre_install,omitempty"`
	PostInstall       string   `toml:"post_install,omitempty" yaml:"post_install,omitempty" mapstructure:"post_install,omitempty"`
	GPGKeys           []string `toml:"gpg_keys,omitempty" yaml:"gpg_keys,omitempty" mapstructure:"gpg_keys,omitempty"`
	MinimumReleaseAge string   `toml:"minimum_release_age,omitempty" yaml:"minimum_release_age,omitempty" mapstructure:"minimum_release_age,omitempty"`
}
Example

ExampleToolConfig demonstrates tool configuration with version specification.

package main

import (
	"fmt"

	"github.com/snowdreamtech/unirtm/internal/config"
)

func main() {
	tool := config.ToolConfig{
		Version:  "1.20.0",
		Backend:  "github",
		Provider: "go",
	}

	if err := tool.Validate(); err != nil {
		fmt.Printf("Validation failed: %v\n", err)
		return
	}

	fmt.Println("Tool configuration is valid")
}
Output:
Tool configuration is valid

func (*ToolConfig) Validate

func (tc *ToolConfig) Validate() error

type ToolMap

type ToolMap map[string]ToolConfig

func (ToolMap) MarshalTOML

func (tm ToolMap) MarshalTOML() (interface{}, error)

type TrustManager

type TrustManager interface {
	// TrustStatus returns the current trust state of the configuration file.
	TrustStatus(path string) TrustStatus

	// Trust adds the specified configuration file to the trusted list with its current hash.
	Trust(path string) error

	// Untrust removes the specified configuration file from the trusted list.
	Untrust(path string) error

	// List returns all currently trusted configuration files.
	List() (map[string]string, error)
}

TrustManager handles the trust mechanism for local and project configurations. To prevent executing malicious scripts from automatically loaded configuration files in untrusted repositories, we maintain a list of trusted config absolute paths and their content hashes.

func NewTrustManager

func NewTrustManager() TrustManager

NewTrustManager creates a new TrustManager that persists trusted paths to ~/.config/unirtm/trusted_configs.

type TrustStatus

type TrustStatus int

TrustStatus represents the current trust state of a configuration file

const (
	// TrustStatusUntrusted means the file has never been trusted
	TrustStatusUntrusted TrustStatus = iota
	// TrustStatusTrusted means the file is trusted and its hash matches
	TrustStatusTrusted
	// TrustStatusModified means the file was trusted previously but its contents have changed
	TrustStatusModified
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL