config

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package config handles configuration loading and management for FinFocus.

Configuration is loaded from ~/.finfocus/config.yaml with support for:

  • Plugin directories and settings
  • Logging configuration (level, format)
  • Default output format preferences

Configuration Precedence

  1. CLI flags (highest priority)
  2. Environment variables (FINFOCUS_*)
  3. Config file (~/.finfocus/config.yaml)
  4. Built-in defaults (lowest priority)

Strict Mode

Enable strict mode with FINFOCUS_CONFIG_STRICT=true to return errors instead of falling back to defaults when configuration loading fails.

Index

Constants

View Source
const (
	MaxThresholdPercent = 1000.0 // Allow alerts up to 1000% for extreme overspend detection
	MinThresholdPercent = 0.0    // Minimum threshold percentage
)

Budget validation limits.

View Source
const (
	MinExitCode = 0   // Minimum valid exit code
	MaxExitCode = 255 // Maximum valid exit code (Unix standard)
)

Exit code limits (Unix standard).

View Source
const (
	// CacheDefaultTTLSeconds is the default cache TTL (1 hour).
	CacheDefaultTTLSeconds = 3600

	// CacheDefaultMaxSizeMB is the default maximum cache size in MB (0 = unlimited).
	CacheDefaultMaxSizeMB = 100

	// CacheEnvTTLSeconds is the environment variable for overriding TTL.
	CacheEnvTTLSeconds = "FINFOCUS_CACHE_TTL"

	// CacheEnvTTLSecondsLegacy is a backward-compatible alias for CacheEnvTTLSeconds.
	CacheEnvTTLSecondsLegacy = "FINFOCUS_CACHE_TTL_SECONDS"

	// CacheEnvEnabled is the environment variable for enabling/disabling cache.
	CacheEnvEnabled = "FINFOCUS_CACHE_ENABLED"

	// CacheEnvDir is the environment variable for cache directory.
	CacheEnvDir = "FINFOCUS_CACHE_DIR"

	// CacheEnvMaxSize is the environment variable for max cache size in MB.
	CacheEnvMaxSize = "FINFOCUS_CACHE_MAX_SIZE_MB"
)

Cache configuration constants. These live in the config package to avoid a dependency inversion where config would otherwise import internal/engine/cache. The cache package re-exports them for backward compatibility.

View Source
const DefaultBudgetPeriod = "monthly"

DefaultBudgetPeriod is the default period for budget tracking.

View Source
const DismissalStoreVersion = 1

DismissalStoreVersion is the current schema version for the dismissal state file.

View Source
const PatternTypeGlob = "glob"

PatternTypeGlob is the pattern type for glob matching.

View Source
const PatternTypeRegex = "regex"

PatternTypeRegex is the pattern type for regex matching.

Variables

View Source
var (
	ErrBudgetAmountNegative   = errors.New("budget amount cannot be negative")
	ErrBudgetCurrencyRequired = errors.New(
		"currency is required when budget amount is greater than 0",
	)
	ErrUnsupportedBudgetPeriod  = errors.New("budget period must be 'monthly'")
	ErrAlertThresholdOutOfRange = errors.New("alert threshold must be between 0 and 1000")
	ErrAlertTypeInvalid         = errors.New("alert type must be 'actual' or 'forecasted'")
	ErrExitCodeOutOfRange       = errors.New("exit code must be between 0 and 255")
)

Budget validation errors.

View Source
var (
	// ErrGlobalBudgetRequired is returned when scoped budgets are defined but no global budget exists.
	ErrGlobalBudgetRequired = errors.New("global budget is required when scoped budgets are defined")

	// ErrCurrencyMismatch is returned when a scoped budget uses a different currency than global.
	ErrCurrencyMismatch = errors.New("scoped budget currency must match global budget currency")

	// ErrInvalidTagSelector is returned when a tag selector doesn't match the required format.
	ErrInvalidTagSelector = errors.New("invalid tag selector format")

	// ErrDuplicateTagPriority is returned when multiple tag budgets have the same priority.
	// This is a warning condition, not a hard error.
	ErrDuplicateTagPriority = errors.New("duplicate tag budget priority")
)

Scoped budget validation errors.

View Source
var ErrConfigCorrupted = errors.New("configuration file appears corrupted")

ErrConfigCorrupted is returned in strict mode when the config file exists but cannot be parsed.

View Source
var ErrStoreCorrupted = errors.New("dismissal state file corrupted")

ErrStoreCorrupted indicates the dismissal state file exists but contains invalid data. Callers should abort unless the user explicitly forces a reset.

Logger is the global zerolog logger instance.

Functions

func AddInstalledPlugin

func AddInstalledPlugin(plugin InstalledPlugin) error

AddInstalledPlugin adds or updates the given plugin in the installed plugins configuration. If a plugin with the same Name already exists it is replaced; otherwise the plugin is appended. It persists the updated list and returns an error if loading or saving the configuration fails.

func CloseLogFile added in v0.3.0

func CloseLogFile()

CloseLogFile closes the current log file handle, if any, and resets the Logger to a safe console-only writer so subsequent logs are not written to a closed file.

func EnsureConfigDir

func EnsureConfigDir() error

EnsureConfigDir ensures the finfocus configuration directory exists.

func EnsureGitignore added in v0.3.0

func EnsureGitignore(dir string) (bool, error)

EnsureGitignore creates a .gitignore file in the given directory if one does not already exist. Returns true if a new file was created, false if one already existed. Never overwrites an existing .gitignore (FR-007).

func EnsureLogDir

func EnsureLogDir() error

EnsureLogDir ensures the directory for the configured FinFocus log file exists. It reads the global configuration and, if a log file is configured, creates its parent directory with permission 0700. If no log file is configured, it does nothing. It returns any error encountered while creating the directory.

func EnsureSubDirs

func EnsureSubDirs() error

EnsureSubDirs creates the standard configuration subdirectories under the user's config directory and ensures the log directory exists.

It ensures the base config directory exists, creates the "plugins" and "specs" subdirectories with permission 0700, and then ensures the configured log directory exists. It returns an error if the user's home directory cannot be determined or if any directory creation operation fails.

func GetConfigDir

func GetConfigDir() (string, error)

GetConfigDir returns the path to the finfocus configuration directory.

func GetDefaultOutputFormat

func GetDefaultOutputFormat() string

GetDefaultOutputFormat returns the configured default output format.

func GetLogFile

func GetLogFile() string

GetLogFile returns the configured log file path.

func GetLogLevel

func GetLogLevel() string

GetLogLevel returns the configured log level.

func GetLogger

func GetLogger() zerolog.Logger

GetLogger returns the global logger instance.

func GetOutputFormat

func GetOutputFormat(userChoice string) string

GetOutputFormat selects the output format, preferring the provided userChoice over the configured default. It returns userChoice if non-empty; otherwise it returns the DefaultFormat from the global configuration.

func GetOutputPrecision

func GetOutputPrecision() int

GetOutputPrecision returns the configured output precision.

func GetPluginConfiguration

func GetPluginConfiguration(pluginName string) (map[string]interface{}, error)

GetPluginConfiguration returns configuration for a specific plugin.

func GetPluginDir

func GetPluginDir() (string, error)

GetPluginDir returns the path to the plugins subdirectory under the user's configuration directory (for example, ~/.finfocus/plugins). It returns an error if the base configuration directory cannot be determined.

func GetResolvedProjectDir added in v0.3.0

func GetResolvedProjectDir() string

GetResolvedProjectDir returns the stored resolved project directory.

func GetSpecDir

func GetSpecDir() (string, error)

GetSpecDir returns the path to the specs directory under the user's config directory (typically ~/.finfocus/specs). It returns an error if the base config directory cannot be determined.

func GetStrictPluginCompatibility added in v0.2.2

func GetStrictPluginCompatibility() bool

GetStrictPluginCompatibility returns whether strict plugin compatibility mode is enabled. When true, plugins with incompatible spec versions will fail to load. When false (default), a warning is logged but initialization continues. This can also be enabled via FINFOCUS_STRICT_COMPATIBILITY=true environment variable.

func GitignoreContent added in v0.3.0

func GitignoreContent() string

GitignoreContent returns the standard .gitignore content used for project-local .finfocus/ directories. Exported for testing.

func InitGlobalConfig

func InitGlobalConfig()

InitGlobalConfig initializes the global configuration without project context. For project-aware initialization, use InitGlobalConfigWithProject.

func InitGlobalConfigWithProject added in v0.3.0

func InitGlobalConfigWithProject(ctx context.Context, projectDir string)

InitGlobalConfigWithProject initializes the global configuration with optional project directory context. If projectDir is non-empty, the project-local config is shallow-merged over global defaults via NewWithProjectDir.

WARNING: The first call wins. The globalConfigInit guard ensures initialization happens at most once. If GetGlobalConfig() (which calls InitGlobalConfig → InitGlobalConfigWithProject("")) is invoked before a project-aware call, the global singleton will be locked to non-project config. Callers must ensure project-aware InitGlobalConfigWithProject(projectDir) runs before any GetGlobalConfig() usage.

func InitLogger

func InitLogger(level string, logToFile bool) error

InitLogger initializes the package-level Logger with the specified log level and optional file output. It sets the global Logger, configures console output, and—when logToFile is true—ensures the log directory exists and opens the configured log file (falling back to "/tmp/finfocus.log" if none is set).

level is parsed into a zerolog level and defaults to InfoLevel on parse error. logToFile enables writing logs to the configured file in addition to the console.

It returns an error if directory creation or opening the log file fails, otherwise nil.

func RemoveInstalledPlugin

func RemoveInstalledPlugin(name string) error

RemoveInstalledPlugin removes the installed plugin with the given name from the configuration. It returns an error if loading the current configuration or saving the updated configuration fails.

func ResetGlobalConfigForTest

func ResetGlobalConfigForTest()

ResetGlobalConfigForTest resets the global config for testing purposes.

func ResolveConfigDir

func ResolveConfigDir() string

ResolveConfigDir determines the configuration directory based on environment variables. It follows this precedence order: 1. $FINFOCUS_HOME - explicit override. 2. $PULUMI_HOME/finfocus/ - if PULUMI_HOME is set (Pulumi ecosystem integration). 3. $HOME/.finfocus/ - default fallback (standard behavior). 4. ./.finfocus - fallback of last resort if home directory cannot be determined.

func ResolveProjectDir added in v0.3.0

func ResolveProjectDir(ctx context.Context, flagValue, startDir string) string

ResolveProjectDir determines the project-local .finfocus directory path. It checks (in order):

  1. flagValue (--project-dir CLI flag)
  2. FINFOCUS_PROJECT_DIR env var
  3. pulumi.FindProject(startDir) walk-up

Returns the path to $PROJECT/.finfocus/ or empty string if no project found. Does NOT create the directory (read-only operation). Returned path is always absolute (or empty).

func SaveInstalledPlugins

func SaveInstalledPlugins(plugins []InstalledPlugin) error

SaveInstalledPlugins saves the provided list of installed plugins into the user's FinFocus config. It ensures the config directory exists, preserves other top-level config keys, updates the `installed_plugins` entry, and performs an atomic write to the config file. The `plugins` parameter is the full list of plugins to persist. It returns an error if the config path cannot be determined, if marshaling or file operations fail.

func SaveProjectSkeleton added in v0.3.0

func SaveProjectSkeleton(path string) error

SaveProjectSkeleton writes a minimal project-level configuration file at the given path. Unlike Config.Save(), which writes the full merged config, this writes only a commented template showing available override keys so that the project config contains overrides rather than a copy of all global defaults.

func SetGlobalConfig added in v0.2.5

func SetGlobalConfig(cfg *Config)

SetGlobalConfig sets the global configuration for testing purposes. If cfg is nil, it resets the global config state.

func SetLogLevel

func SetLogLevel(level string)

SetLogLevel sets the package global Logger's level to the value parsed from level. If the provided level cannot be parsed, the logger level is set to zerolog.InfoLevel.

func SetResolvedProjectDir added in v0.3.0

func SetResolvedProjectDir(dir string)

SetResolvedProjectDir stores the resolved project directory for use by other config functions.

func ShallowMergeYAML added in v0.3.0

func ShallowMergeYAML(target *Config, overlayPath string) error

ShallowMergeYAML loads a YAML file and merges its top-level keys onto the target Config. Keys present in the overlay replace entire sections in the target. Keys absent in the overlay are left unchanged.

func UpdateInstalledPluginVersion

func UpdateInstalledPluginVersion(name, version string) error

UpdateInstalledPluginVersion updates the version of the installed plugin with the given name to the provided version. It returns an error if the installed-plugins configuration cannot be loaded, if no plugin with the given name exists, or if saving the updated configuration fails.

Types

type AlertConfig added in v0.2.4

type AlertConfig struct {
	// Threshold is the percentage of budget consumed that triggers this alert (e.g., 80.0 for 80%).
	Threshold float64 `yaml:"threshold" json:"threshold"`
	// Type is the evaluation type: "actual" or "forecasted".
	Type AlertType `yaml:"type" json:"type"`
}

AlertConfig defines a specific threshold that triggers a notification. It represents a point in the budget consumption where the user should be alerted.

func (AlertConfig) Validate added in v0.2.4

func (a AlertConfig) Validate() error

Validate checks if the alert configuration is valid.

type AlertType added in v0.2.4

type AlertType string

AlertType represents the type of budget alert evaluation.

const (
	// AlertTypeActual triggers when actual spend exceeds threshold.
	AlertTypeActual AlertType = "actual"
	// AlertTypeForecasted triggers when forecasted spend exceeds threshold.
	AlertTypeForecasted AlertType = "forecasted"
)

Valid alert types for budget threshold evaluation.

type AnalyzerConfig

type AnalyzerConfig struct {
	Timeout        AnalyzerTimeout           `yaml:"timeout"          json:"timeout"`
	Plugins        map[string]AnalyzerPlugin `yaml:"plugins"          json:"plugins"`
	MaxMonthlyCost float64                   `yaml:"max_monthly_cost" json:"max_monthly_cost"`
	Enforcement    string                    `yaml:"enforcement"      json:"enforcement"`
}

AnalyzerConfig defines analyzer-specific configuration for the Pulumi Analyzer plugin.

type AnalyzerPlugin

type AnalyzerPlugin struct {
	Path    string            `yaml:"path"    json:"path"`    // Path to plugin binary
	Enabled bool              `yaml:"enabled" json:"enabled"` // Whether plugin is enabled
	Env     map[string]string `yaml:"env"     json:"env"`     // Environment variables for plugin
}

AnalyzerPlugin defines a cost plugin configuration for the analyzer.

type AnalyzerTimeout

type AnalyzerTimeout struct {
	PerResource   Duration `yaml:"per_resource"   json:"per_resource"`   // Per-resource timeout (default: 5s)
	Total         Duration `yaml:"total"          json:"total"`          // Overall analysis timeout (default: 60s)
	WarnThreshold Duration `yaml:"warn_threshold" json:"warn_threshold"` // Warning threshold (default: 30s)
}

AnalyzerTimeout defines timeout settings for cost analysis operations.

type AuditConfig

type AuditConfig struct {
	Enabled bool   `yaml:"enabled" json:"enabled"` // Enable audit logging
	File    string `yaml:"file"    json:"file"`    // Separate audit file (optional, empty = main log)
}

AuditConfig defines audit logging settings for cost query operations.

type BudgetConfig added in v0.2.4

type BudgetConfig struct {
	// Amount is the total spend limit for the period. Use 0 to disable the budget.
	Amount float64 `yaml:"amount" json:"amount"`
	// Currency is the ISO 4217 currency code (e.g., "USD", "EUR").
	Currency string `yaml:"currency" json:"currency"`
	// Period is the time period for the budget (e.g., "monthly"). Defaults to "monthly".
	Period string `yaml:"period,omitempty" json:"period,omitempty"`
	// Alerts is a list of thresholds that trigger notifications.
	Alerts []AlertConfig `yaml:"alerts,omitempty" json:"alerts,omitempty"`

	// ExitOnThreshold enables non-zero exit codes when budget thresholds are exceeded.
	// When true, the CLI will exit with the configured exit code on threshold violation.
	ExitOnThreshold bool `yaml:"exit_on_threshold,omitempty" json:"exit_on_threshold,omitempty"`
	// ExitCode is the exit code to use when a threshold is exceeded.
	// Only validated when ExitOnThreshold is true. Defaults to 1 if not set.
	// Must be in range 0-255 (Unix standard).
	ExitCode int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}

BudgetConfig represents the spending limit and associated alerts for a period. It defines a budget with an amount, currency, time period, and threshold alerts.

func (BudgetConfig) GetActualAlerts added in v0.2.4

func (b BudgetConfig) GetActualAlerts() []AlertConfig

GetActualAlerts returns only alerts of type "actual".

func (BudgetConfig) GetExitCode added in v0.2.5

func (b BudgetConfig) GetExitCode() int

GetExitCode returns the configured exit code, defaulting to 1 if not set. This method provides the raw exit code defined in the configuration. Callers should typically check ShouldExitOnThreshold() (or ExitOnThreshold) to determine if they should act on this exit code.

Note: Exit code 0 is valid and explicitly allowed (for warning-only mode). When ExitOnThreshold is true and GetExitCode() returns 0, the CLI should log a warning instead of terminating with a non-zero error.

func (BudgetConfig) GetForecastedAlerts added in v0.2.4

func (b BudgetConfig) GetForecastedAlerts() []AlertConfig

GetForecastedAlerts returns only alerts of type "forecasted".

func (BudgetConfig) GetPeriod added in v0.2.4

func (b BudgetConfig) GetPeriod() string

GetPeriod returns the budget period, defaulting to "monthly" if not set.

func (BudgetConfig) IsDisabled added in v0.2.4

func (b BudgetConfig) IsDisabled() bool

IsDisabled returns true if the budget is disabled (Amount == 0).

func (BudgetConfig) IsEnabled added in v0.2.4

func (b BudgetConfig) IsEnabled() bool

IsEnabled returns true if the budget is configured and enabled (Amount > 0).

func (BudgetConfig) ShouldExitOnThreshold added in v0.2.5

func (b BudgetConfig) ShouldExitOnThreshold() bool

ShouldExitOnThreshold returns true if the CLI should exit with a non-zero code when budget thresholds are exceeded.

func (BudgetConfig) Validate added in v0.2.4

func (b BudgetConfig) Validate() error

Validate checks if the budget configuration is valid. Returns nil if the budget is disabled (Amount == 0) or if all validations pass.

type BudgetsConfig added in v0.2.6

type BudgetsConfig struct {
	// Global is the fallback budget that applies to all resources.
	// Required if any scoped budgets are defined.
	Global *ScopedBudget `yaml:"global,omitempty" json:"global,omitempty"`

	// Providers maps cloud provider names (aws, gcp, azure) to their budgets.
	// Provider names are case-insensitive during matching.
	Providers map[string]*ScopedBudget `yaml:"providers,omitempty" json:"providers,omitempty"`

	// Tags defines budgets scoped by resource tags with priority ordering.
	// Higher priority values take precedence when a resource matches multiple tags.
	Tags []TagBudget `yaml:"tags,omitempty" json:"tags,omitempty"`

	// Types maps resource type patterns to their budgets.
	// Patterns use exact matching (e.g., "aws:ec2/instance").
	Types map[string]*ScopedBudget `yaml:"types,omitempty" json:"types,omitempty"`

	// ExitOnThreshold applies to all scopes unless overridden.
	ExitOnThreshold bool `yaml:"exit_on_threshold,omitempty" json:"exit_on_threshold,omitempty"`

	// ExitCode is the default exit code when thresholds are exceeded.
	// Nil means not set (defaults to 1). Zero is valid (warning-only mode).
	ExitCode *int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}

BudgetsConfig holds all budget scope configurations. It supports a global fallback budget and optional provider, tag, and type scopes.

func (*BudgetsConfig) GetEffectiveExitCode added in v0.2.6

func (b *BudgetsConfig) GetEffectiveExitCode(scopeOverride *int) int

GetEffectiveExitCode returns the exit code for a given scope. It checks the scope's setting first, then falls back to the global setting. Returns 1 as the default if nothing is configured.

func (*BudgetsConfig) GetEffectiveExitOnThreshold added in v0.2.6

func (b *BudgetsConfig) GetEffectiveExitOnThreshold(scopeOverride *bool) bool

GetEffectiveExitOnThreshold returns the exit_on_threshold setting for a given scope. It checks the scope's setting first, then falls back to the global setting.

func (*BudgetsConfig) GetGlobalCurrency added in v0.2.6

func (b *BudgetsConfig) GetGlobalCurrency() string

GetGlobalCurrency returns the global budget's currency, or empty string if not set.

func (*BudgetsConfig) HasGlobalBudget added in v0.2.6

func (b *BudgetsConfig) HasGlobalBudget() bool

HasGlobalBudget returns true if a global budget is configured and enabled.

func (*BudgetsConfig) HasScopedBudgets added in v0.2.6

func (b *BudgetsConfig) HasScopedBudgets() bool

HasScopedBudgets returns true if any provider, tag, or type budgets are defined.

func (*BudgetsConfig) IsEnabled added in v0.2.6

func (b *BudgetsConfig) IsEnabled() bool

IsEnabled returns true if any budget (global or scoped) is enabled.

func (*BudgetsConfig) Validate added in v0.2.6

func (b *BudgetsConfig) Validate() ([]string, error)

Validate checks if the budgets configuration is valid. Returns a list of warnings (non-fatal issues) and an error for fatal issues.

type CacheConfig added in v0.2.5

type CacheConfig struct {
	// Enabled controls whether caching is enabled (default: true).
	Enabled bool `yaml:"enabled" json:"enabled"`

	// TTLSeconds is the time-to-live for cached entries in seconds (default: 3600 = 1 hour).
	TTLSeconds int `yaml:"ttl_seconds" json:"ttl_seconds"`

	// Directory is the cache directory path (default: ~/.finfocus/cache).
	Directory string `yaml:"directory,omitempty" json:"directory,omitempty"`

	// MaxSizeMB is the maximum cache size in megabytes (default: 100, 0 = unlimited).
	MaxSizeMB int `yaml:"max_size_mb" json:"max_size_mb"`
}

CacheConfig defines caching behavior for query results.

type Config

type Config struct {
	// Legacy fields for backward compatibility
	PluginDir string `yaml:"-" json:"-"`
	SpecDir   string `yaml:"-" json:"-"`

	// PluginDirOverride overrides the computed PluginDir when set via the plugin_dir: config key.
	// Applied before the FINFOCUS_PLUGIN_DIR env var override, which takes higher precedence.
	PluginDirOverride string `yaml:"plugin_dir,omitempty" json:"plugin_dir,omitempty"`

	// New comprehensive configuration
	Output           OutputConfig            `yaml:"output"      json:"output"`
	Plugins          map[string]PluginConfig `yaml:"plugins"     json:"plugins"`
	Logging          LoggingConfig           `yaml:"logging"     json:"logging"`
	Analyzer         AnalyzerConfig          `yaml:"analyzer"    json:"analyzer"`
	PluginHostConfig PluginHostConfig        `yaml:"plugin_host" json:"plugin_host"`
	Cost             CostConfig              `yaml:"cost"        json:"cost"`

	// Routing configures plugin routing behavior.
	// If nil, automatic provider-based routing is used (FR-023 backward compatibility).
	Routing *RoutingConfig `yaml:"routing,omitempty" json:"routing,omitempty"`
	// contains filtered or unexported fields
}

Config represents the complete configuration structure.

var GlobalConfig *Config //nolint:gochecknoglobals // Singleton pattern for configuration

GlobalConfig holds the global configuration instance.

func GetGlobalConfig

func GetGlobalConfig() *Config

GetGlobalConfig returns the global configuration, initializing it if needed.

func New

func New() *Config

New creates a new configuration with defaults. New creates and returns a Config populated with sensible defaults and, if a config file exists, values loaded from that file. It falls back to defaults when the file is missing, applies environment variable overrides, and normalizes analyzer threshold values (e.g., MaxMonthlyCost and Enforcement).

In strict mode (when FINFOCUS_CONFIG_STRICT is "true" or "1"), permission errors reading the config file or detection of a corrupted config file cause a panic; otherwise such conditions emit warnings and the defaults are used.

func NewStrict

func NewStrict() (*Config, error)

NewStrict creates a new configuration with strict error handling. It returns an error instead of using defaults if the config file exists but cannot be parsed. validation failure is returned.

func NewWithProjectDir added in v0.3.0

func NewWithProjectDir(ctx context.Context, projectDir string) *Config

NewWithProjectDir creates a Config by loading global config then shallow-merging project-local config on top. If projectDir is empty, behaves identically to New().

func (*Config) ConfigPath added in v0.3.0

func (c *Config) ConfigPath() string

ConfigPath returns the config file path.

func (*Config) Get

func (c *Config) Get(key string) (interface{}, error)

Get gets a configuration value using dot notation.

func (*Config) GetPluginConfig

func (c *Config) GetPluginConfig(pluginName string) (map[string]interface{}, error)

GetPluginConfig returns configuration for a specific plugin.

func (*Config) List

func (c *Config) List() map[string]interface{}

List returns all configuration as a map.

func (*Config) Load

func (c *Config) Load() error

Load loads configuration from the config file.

func (*Config) PluginPath

func (c *Config) PluginPath(name, version string) string

PluginPath returns the path for a specific plugin version (backward compatibility).

func (*Config) Save

func (c *Config) Save() error

Save saves the current configuration to the config file.

func (*Config) Set

func (c *Config) Set(key, value string) error

Set sets a configuration value using dot notation.

func (*Config) SetConfigPath added in v0.3.0

func (c *Config) SetConfigPath(path string)

SetConfigPath overrides the config file path used by Load and Save.

func (*Config) SetPluginConfig

func (c *Config) SetPluginConfig(pluginName string, config map[string]interface{})

SetPluginConfig sets configuration for a specific plugin.

func (*Config) Validate

func (c *Config) Validate() error

Validate validates the configuration.

type CostConfig added in v0.2.4

type CostConfig struct {
	// Budgets contains the hierarchical budget configuration with
	// global, provider, tag, and resource type scopes.
	Budgets *BudgetsConfig `yaml:"budgets,omitempty" json:"budgets,omitempty"`

	// Cache contains the cache configuration for query result caching.
	Cache CacheConfig `yaml:"cache,omitempty" json:"cache,omitempty"`
}

CostConfig holds cost-related configuration settings including budgets and caching. It groups all cost management features under a single configuration section.

func (CostConfig) GetBudgetsWarnings added in v0.2.6

func (c CostConfig) GetBudgetsWarnings() []string

GetBudgetsWarnings validates the budgets and returns any warnings. Returns nil if no budgets are configured or no warnings exist. Validation errors are included as warnings to avoid silent failures.

func (CostConfig) HasBudget added in v0.2.4

func (c CostConfig) HasBudget() bool

HasBudget returns true if a budget is configured and enabled.

func (CostConfig) Validate added in v0.2.4

func (c CostConfig) Validate() error

Validate validates the cost configuration. Returns an error for fatal validation issues. Non-fatal warnings (like duplicate tag budget priorities) can be retrieved via GetBudgetsWarnings().

type DismissalRecord added in v0.3.0

type DismissalRecord struct {
	RecommendationID string                   `json:"recommendation_id"`
	Status           DismissalStatus          `json:"status"`
	Reason           string                   `json:"reason"`
	CustomReason     string                   `json:"custom_reason,omitempty"`
	DismissedAt      time.Time                `json:"dismissed_at"`
	DismissedBy      string                   `json:"dismissed_by,omitempty"`
	ExpiresAt        *time.Time               `json:"expires_at"`
	LastKnown        *LastKnownRecommendation `json:"last_known,omitempty"`
	History          []LifecycleEvent         `json:"history"`
}

DismissalRecord represents a single recommendation's dismissal state.

type DismissalStatus added in v0.3.0

type DismissalStatus string

DismissalStatus represents the lifecycle state of a dismissal record.

const (
	// StatusDismissed indicates a permanent dismissal (no expiry).
	StatusDismissed DismissalStatus = "dismissed"
	// StatusSnoozed indicates a temporary dismissal with an expiry date.
	StatusSnoozed DismissalStatus = "snoozed"
	// StatusActive indicates a previously dismissed recommendation that was re-enabled.
	// The record is preserved for audit trail / history purposes.
	StatusActive DismissalStatus = "active"
)

type DismissalStore added in v0.3.0

type DismissalStore struct {
	// contains filtered or unexported fields
}

DismissalStore manages dismissal state persisted as a JSON file.

func NewDismissalStore added in v0.3.0

func NewDismissalStore(filePath string) (*DismissalStore, error)

NewDismissalStore creates a new DismissalStore backed by the given file path. If filePath is empty, it resolves using project context:

  1. If a project directory is set (via SetResolvedProjectDir), uses $PROJECT_DIR/dismissed.json.
  2. Otherwise falls back to ResolveConfigDir()/dismissed.json.

func (*DismissalStore) CleanExpiredSnoozes added in v0.3.0

func (s *DismissalStore) CleanExpiredSnoozes() (int, error)

CleanExpiredSnoozes transitions snoozed records whose ExpiresAt has passed to active status. Returns the number of snoozes that were cleaned.

func (*DismissalStore) Count added in v0.3.0

func (s *DismissalStore) Count() int

Count returns the number of dismissal records.

func (*DismissalStore) Delete added in v0.3.0

func (s *DismissalStore) Delete(recommendationID string) error

Delete removes a dismissal record by recommendation ID.

func (*DismissalStore) FilePath added in v0.3.0

func (s *DismissalStore) FilePath() string

FilePath returns the file path of the dismissal store.

func (*DismissalStore) Get added in v0.3.0

func (s *DismissalStore) Get(recommendationID string) (*DismissalRecord, bool)

Get retrieves a dismissal record by recommendation ID. Returns a copy of the record to prevent callers from mutating internal state. Returns nil and false if the ID is not found.

func (*DismissalStore) GetAllRecords added in v0.3.0

func (s *DismissalStore) GetAllRecords() map[string]*DismissalRecord

GetAllRecords returns all dismissal records (including expired snoozes). Returns a deep copy to prevent concurrent modification of internal state.

func (*DismissalStore) GetDismissedIDs added in v0.3.0

func (s *DismissalStore) GetDismissedIDs() []string

GetDismissedIDs returns all recommendation IDs that are currently dismissed or snoozed (excluding expired snoozes). This is used to populate ExcludedRecommendationIds.

func (*DismissalStore) GetExpiredSnoozes added in v0.3.0

func (s *DismissalStore) GetExpiredSnoozes() []*DismissalRecord

GetExpiredSnoozes returns records that have snoozed status with an expired ExpiresAt.

func (*DismissalStore) Load added in v0.3.0

func (s *DismissalStore) Load() error

Load reads the dismissal state from the JSON file. If the file does not exist, the store starts empty. If the file is corrupted, ErrStoreCorrupted is returned.

func (*DismissalStore) Save added in v0.3.0

func (s *DismissalStore) Save() error

Save writes the dismissal state to the JSON file atomically.

func (*DismissalStore) Set added in v0.3.0

func (s *DismissalStore) Set(record *DismissalRecord) error

Set adds or updates a dismissal record.

type Duration

type Duration time.Duration

Duration is a wrapper around time.Duration that supports YAML/JSON parsing.

func (Duration) Duration

func (d Duration) Duration() time.Duration

Duration returns the underlying time.Duration value.

func (Duration) MarshalYAML

func (d Duration) MarshalYAML() (interface{}, error)

MarshalYAML implements yaml.Marshaler for Duration.

func (*Duration) UnmarshalYAML

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

UnmarshalYAML implements yaml.Unmarshaler for Duration.

type InstalledPlugin

type InstalledPlugin struct {
	Name    string `yaml:"name"    json:"name"`
	URL     string `yaml:"url"     json:"url"`
	Version string `yaml:"version" json:"version"`
}

InstalledPlugin represents an installed plugin entry in config.yaml.

func GetInstalledPlugin

func GetInstalledPlugin(name string) (*InstalledPlugin, error)

GetInstalledPlugin retrieves the installed plugin with the given name. It returns a pointer to the InstalledPlugin if found, or an error if the plugin is not present in the config or if the installed plugins cannot be loaded.

func GetMissingPlugins

func GetMissingPlugins() ([]InstalledPlugin, error)

GetMissingPlugins returns plugins that are in config but not installed on disk.

func LoadInstalledPlugins

func LoadInstalledPlugins() ([]InstalledPlugin, error)

LoadInstalledPlugins loads the list of installed plugins from the config file. It returns an empty list if the file does not exist, or an error if the YAML cannot be parsed.

type InstalledPluginsConfig

type InstalledPluginsConfig struct {
	InstalledPlugins []InstalledPlugin `yaml:"installed_plugins" json:"installed_plugins"`
}

InstalledPluginsConfig holds the installed plugins list.

type LastKnownRecommendation added in v0.3.0

type LastKnownRecommendation struct {
	Description      string  `json:"description"`
	EstimatedSavings float64 `json:"estimated_savings"`
	Currency         string  `json:"currency"`
	Type             string  `json:"type"`
	ResourceID       string  `json:"resource_id"`
}

LastKnownRecommendation captures recommendation details at the time of dismissal.

type LifecycleAction added in v0.3.0

type LifecycleAction string

LifecycleAction represents the type of lifecycle event.

const (
	// ActionDismissed indicates the recommendation was permanently dismissed.
	ActionDismissed LifecycleAction = "dismissed"
	// ActionSnoozed indicates the recommendation was snoozed with an expiry.
	ActionSnoozed LifecycleAction = "snoozed"
	// ActionUndismissed indicates the recommendation was re-enabled.
	ActionUndismissed LifecycleAction = "undismissed"
)

type LifecycleEvent added in v0.3.0

type LifecycleEvent struct {
	Action       LifecycleAction `json:"action"`
	Reason       string          `json:"reason"`
	CustomReason string          `json:"custom_reason,omitempty"`
	Timestamp    time.Time       `json:"timestamp"`
	ExpiresAt    *time.Time      `json:"expires_at,omitempty"`
}

LifecycleEvent is a timestamped action in a recommendation's history.

type LogOutput

type LogOutput struct {
	Type      string `yaml:"type"                  json:"type"`                  // "console", "file", "syslog"
	Level     string `yaml:"level,omitempty"       json:"level,omitempty"`       // Optional: override global level
	Path      string `yaml:"path,omitempty"        json:"path,omitempty"`        // For file type
	Format    string `yaml:"format,omitempty"      json:"format,omitempty"`      // Optional: override global format
	MaxSizeMB int    `yaml:"max_size_mb,omitempty" json:"max_size_mb,omitempty"` // File rotation
	MaxFiles  int    `yaml:"max_files,omitempty"   json:"max_files,omitempty"`   // File rotation
}

LogOutput defines a logging output destination.

type LoggingConfig

type LoggingConfig struct {
	Level   string      `yaml:"level"   json:"level"`
	Format  string      `yaml:"format"  json:"format"`  // "json" or "text"
	Outputs []LogOutput `yaml:"outputs" json:"outputs"` // Multiple output destinations
	File    string      `yaml:"file"    json:"file"`    // Legacy: single file output
	Audit   AuditConfig `yaml:"audit"   json:"audit"`   // Audit logging configuration
}

LoggingConfig defines logging preferences.

func GetLoggingConfig

func GetLoggingConfig() LoggingConfig

GetLoggingConfig returns the Logging section of the global configuration. The returned value is a copy of the current global config's Logging settings. Any environment-level overrides (for example a --debug flag) are expected to be applied by the caller after retrieving this value.

func (*LoggingConfig) ToLoggingConfig

func (lc *LoggingConfig) ToLoggingConfig() logging.Config

ToLoggingConfig converts config.LoggingConfig to logging.Config for use with the internal/logging package. This bridges the configuration system to the logging infrastructure.

The conversion applies these rules:

  • Level, Format are copied directly
  • If File is set, Output becomes "file" and File is passed through
  • If File is empty, Output defaults to "stderr"

type OutputConfig

type OutputConfig struct {
	DefaultFormat string `yaml:"default_format" json:"default_format"`
	Precision     int    `yaml:"precision"      json:"precision"`
}

OutputConfig defines output formatting preferences.

type ParsedTagSelector added in v0.2.6

type ParsedTagSelector struct {
	// Key is the tag key to match.
	Key string
	// Value is the tag value to match, or "*" for wildcard.
	Value string
	// IsWildcard is true if the selector matches any value for the key.
	IsWildcard bool
}

ParsedTagSelector represents a parsed tag selector with key and value components.

func ParseTagSelector added in v0.2.6

func ParseTagSelector(selector string) (*ParsedTagSelector, error)

ParseTagSelector parses a tag selector string into its components. Valid formats: "key:value" or "key:*".

func (*ParsedTagSelector) Matches added in v0.2.6

func (p *ParsedTagSelector) Matches(tags map[string]string) bool

Matches returns true if the selector matches the given tags map.

type PluginConfig

type PluginConfig struct {
	Config map[string]interface{} `yaml:",inline" json:",inline"`
}

PluginConfig defines plugin-specific configuration.

type PluginHostConfig added in v0.2.2

type PluginHostConfig struct {
	// StrictCompatibility blocks plugin initialization on spec version mismatch.
	// When true, plugins with incompatible spec versions will fail to load.
	// When false (default), a warning is logged but initialization continues.
	StrictCompatibility bool `yaml:"strict_compatibility" json:"strict_compatibility"`
}

PluginHostConfig defines plugin host behavior settings.

type PluginRouting added in v0.2.6

type PluginRouting struct {
	// Name is the plugin identifier.
	// Must match an installed plugin name from ~/.finfocus/plugins/<name>/.
	// Required.
	Name string `yaml:"name" json:"name"`

	// Features limits which capabilities this plugin handles.
	// If empty, all features the plugin reports are enabled.
	//
	// Valid values:
	//   - ProjectedCosts: Cost estimation from infrastructure specs
	//   - ActualCosts: Historical cost data from cloud APIs
	//   - Recommendations: Cost optimization suggestions
	//   - Carbon: Carbon footprint estimation
	//   - DryRun: Dry run simulation
	//   - Budgets: Budget tracking and alerts
	//
	// Invalid feature names generate a validation warning (non-blocking).
	Features []string `yaml:"features,omitempty" json:"features,omitempty"`

	// Patterns defines resource type patterns this plugin handles.
	// If empty, automatic provider-based routing is used.
	// Patterns take precedence over automatic routing.
	Patterns []ResourcePattern `yaml:"patterns,omitempty" json:"patterns,omitempty"`

	// Priority determines selection order.
	// Higher values = higher priority (preferred).
	// Default is 0.
	//
	// Behavior:
	//   - Different priorities: Highest priority plugin is tried first
	//   - Equal priority (0): All matching plugins queried in parallel
	//   - Fallback: If enabled and plugin fails, next priority is tried
	Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`

	// Fallback enables trying the next plugin if this one fails.
	// Default is true if not specified.
	//
	// Failure conditions that trigger fallback:
	//   - Connection timeout
	//   - Plugin crash (EOF, connection reset)
	//   - Empty result (no cost data)
	//   - gRPC error (Unavailable, Internal)
	//
	// Conditions that do NOT trigger fallback:
	//   - InvalidArgument (plugin explicitly rejected request)
	//   - $0 cost result (valid result, not a failure)
	Fallback *bool `yaml:"fallback,omitempty" json:"fallback,omitempty"`
}

PluginRouting defines how a specific plugin should be used.

func (PluginRouting) FallbackEnabled added in v0.2.6

func (p PluginRouting) FallbackEnabled() bool

FallbackEnabled returns whether fallback is enabled for this plugin. Returns true if Fallback is nil (default behavior).

type ResourcePattern added in v0.2.6

type ResourcePattern struct {
	// Type is the pattern type.
	// Required. Must be "glob" or "regex".
	//
	// "glob": Uses Go's filepath.Match semantics
	//   - "*" matches any sequence of non-separator characters
	//   - "?" matches any single non-separator character
	//   - "[...]" matches character class
	//   - Example: "aws:ec2:*" matches "aws:ec2:Instance"
	//
	// "regex": Uses Go's regexp package (RE2 syntax)
	//   - Full regular expression support
	//   - Must be valid RE2 syntax (no backreferences)
	//   - Example: "aws:(ec2|rds)/.*" matches "aws:ec2/instance:Instance"
	Type string `yaml:"type" json:"type"`

	// Pattern is the pattern string.
	// Required. Must be non-empty.
	// Validated at config load time.
	Pattern string `yaml:"pattern" json:"pattern"`
}

ResourcePattern defines a pattern for matching resource types.

func (ResourcePattern) IsGlob added in v0.2.6

func (p ResourcePattern) IsGlob() bool

IsGlob returns true if this is a glob pattern.

func (ResourcePattern) IsRegex added in v0.2.6

func (p ResourcePattern) IsRegex() bool

IsRegex returns true if this is a regex pattern.

type RoutingConfig added in v0.2.6

type RoutingConfig struct {
	// Plugins contains the ordered list of plugin routing rules.
	// Order matters for tie-breaking when priorities are equal.
	// May be empty (uses automatic routing only).
	Plugins []PluginRouting `yaml:"plugins" json:"plugins"`
}

RoutingConfig defines the complete routing strategy for plugins.

YAML Location: ~/.finfocus/config.yaml under "routing" key

Example:

routing:
  plugins:
    - name: aws-public
      priority: 10

func (*RoutingConfig) Validate added in v0.2.6

func (r *RoutingConfig) Validate() error

Validate performs lightweight structural validation of the routing configuration. It checks that plugin names are non-empty, patterns have valid types and non-empty strings, and priority values are non-negative.

type ScopedBudget added in v0.2.6

type ScopedBudget struct {
	// Amount is the budget limit in the specified currency.
	// Must be non-negative (zero disables the budget).
	Amount float64 `yaml:"amount" json:"amount"`

	// Currency is the ISO 4217 currency code (e.g., "USD", "EUR").
	// If empty, inherits from global budget.
	Currency string `yaml:"currency,omitempty" json:"currency,omitempty"`

	// Period defines the budget time window. Only "monthly" is supported.
	// If empty, defaults to "monthly".
	Period string `yaml:"period,omitempty" json:"period,omitempty"`

	// Alerts defines threshold percentages and their types.
	// If empty, uses default thresholds (50%, 80%, 100% actual).
	Alerts []AlertConfig `yaml:"alerts,omitempty" json:"alerts,omitempty"`

	// ExitOnThreshold overrides the global setting for this scope.
	// If nil, inherits from BudgetsConfig.ExitOnThreshold.
	ExitOnThreshold *bool `yaml:"exit_on_threshold,omitempty" json:"exit_on_threshold,omitempty"`

	// ExitCode overrides the global exit code for this scope.
	// If nil, inherits from BudgetsConfig.ExitCode.
	ExitCode *int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}

ScopedBudget defines a budget limit with alert thresholds. It can be used for global, provider, and resource type scopes.

func (*ScopedBudget) GetCurrency added in v0.2.6

func (s *ScopedBudget) GetCurrency() string

GetCurrency returns the configured currency or empty string if not set.

func (*ScopedBudget) GetExitCode added in v0.2.6

func (s *ScopedBudget) GetExitCode() *int

GetExitCode returns the configured exit code or nil if not set. Returns nil if not explicitly set (caller should check parent/global setting).

func (*ScopedBudget) GetPeriod added in v0.2.6

func (s *ScopedBudget) GetPeriod() string

GetPeriod returns the budget period, defaulting to "monthly" if not set.

func (*ScopedBudget) IsDisabled added in v0.2.6

func (s *ScopedBudget) IsDisabled() bool

IsDisabled returns true if the scoped budget is disabled (nil or Amount == 0). Nil-receiver behavior: ScopedBudget.IsDisabled() treats a nil receiver as disabled/not configured and returns true. This semantic difference from IsEnabled is deliberate: "nil == not enabled" vs "nil == disabled/not configured" allows callers to check absence of configuration explicitly.

func (*ScopedBudget) IsEnabled added in v0.2.6

func (s *ScopedBudget) IsEnabled() bool

IsEnabled returns true if the scoped budget is configured and enabled (Amount > 0). Nil-receiver behavior: ScopedBudget.IsEnabled() treats a nil receiver as not enabled and returns false. This allows safe nil-checking without explicit nil guards.

func (*ScopedBudget) ShouldExitOnThreshold added in v0.2.6

func (s *ScopedBudget) ShouldExitOnThreshold() *bool

ShouldExitOnThreshold returns true if the CLI should exit with a non-zero code when this scope's budget thresholds are exceeded. Returns nil if not explicitly set (caller should check parent/global setting).

func (*ScopedBudget) Validate added in v0.2.6

func (s *ScopedBudget) Validate(globalCurrency string) error

Validate checks if the scoped budget configuration is valid. The globalCurrency parameter is used to validate currency inheritance. Pass empty string for global budget validation (no inheritance check).

type TagBudget added in v0.2.6

type TagBudget struct {
	// ScopedBudget embeds the budget configuration.
	// Embedded fields must be listed before regular fields per Go convention.
	ScopedBudget `yaml:",inline" json:",inline"`

	// Selector is the tag pattern in "key:value" or "key:*" format.
	// "key:value" matches exact tag values.
	// "key:*" matches any resource with the specified tag key.
	Selector string `yaml:"selector" json:"selector"`

	// Priority determines which tag budget receives cost when a resource
	// matches multiple tag selectors. Higher values take precedence.
	// If multiple budgets have the same priority, a warning is emitted
	// and the first alphabetically wins.
	Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
}

TagBudget defines a budget scoped by a tag selector with priority ordering.

func (*TagBudget) Validate added in v0.2.6

func (t *TagBudget) Validate(globalCurrency string) error

Validate checks if the tag budget configuration is valid. The globalCurrency parameter is used to validate currency inheritance.

Jump to

Keyboard shortcuts

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