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 ¶
- CLI flags (highest priority)
- Environment variables (FINFOCUS_*)
- Config file (~/.finfocus/config.yaml)
- 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
- Variables
- func AddInstalledPlugin(plugin InstalledPlugin) error
- func CloseLogFile()
- func EnsureConfigDir() error
- func EnsureGitignore(dir string) (bool, error)
- func EnsureLogDir() error
- func EnsureSubDirs() error
- func GetConfigDir() (string, error)
- func GetDefaultOutputFormat() string
- func GetLogFile() string
- func GetLogLevel() string
- func GetLogger() zerolog.Logger
- func GetOutputFormat(userChoice string) string
- func GetOutputPrecision() int
- func GetPluginConfiguration(pluginName string) (map[string]interface{}, error)
- func GetPluginDir() (string, error)
- func GetResolvedProjectDir() string
- func GetSpecDir() (string, error)
- func GetStrictPluginCompatibility() bool
- func GitignoreContent() string
- func InitGlobalConfig()
- func InitGlobalConfigWithProject(ctx context.Context, projectDir string)
- func InitLogger(level string, logToFile bool) error
- func RemoveInstalledPlugin(name string) error
- func ResetGlobalConfigForTest()
- func ResolveConfigDir() string
- func ResolveProjectDir(ctx context.Context, flagValue, startDir string) string
- func SaveInstalledPlugins(plugins []InstalledPlugin) error
- func SaveProjectSkeleton(path string) error
- func SetGlobalConfig(cfg *Config)
- func SetLogLevel(level string)
- func SetResolvedProjectDir(dir string)
- func ShallowMergeYAML(target *Config, overlayPath string) error
- func UpdateInstalledPluginVersion(name, version string) error
- type AlertConfig
- type AlertType
- type AnalyzerConfig
- type AnalyzerPlugin
- type AnalyzerTimeout
- type AuditConfig
- type BudgetConfig
- func (b BudgetConfig) GetActualAlerts() []AlertConfig
- func (b BudgetConfig) GetExitCode() int
- func (b BudgetConfig) GetForecastedAlerts() []AlertConfig
- func (b BudgetConfig) GetPeriod() string
- func (b BudgetConfig) IsDisabled() bool
- func (b BudgetConfig) IsEnabled() bool
- func (b BudgetConfig) ShouldExitOnThreshold() bool
- func (b BudgetConfig) Validate() error
- type BudgetsConfig
- func (b *BudgetsConfig) GetEffectiveExitCode(scopeOverride *int) int
- func (b *BudgetsConfig) GetEffectiveExitOnThreshold(scopeOverride *bool) bool
- func (b *BudgetsConfig) GetGlobalCurrency() string
- func (b *BudgetsConfig) HasGlobalBudget() bool
- func (b *BudgetsConfig) HasScopedBudgets() bool
- func (b *BudgetsConfig) IsEnabled() bool
- func (b *BudgetsConfig) Validate() ([]string, error)
- type CacheConfig
- type Config
- func (c *Config) ConfigPath() string
- func (c *Config) Get(key string) (interface{}, error)
- func (c *Config) GetPluginConfig(pluginName string) (map[string]interface{}, error)
- func (c *Config) List() map[string]interface{}
- func (c *Config) Load() error
- func (c *Config) PluginPath(name, version string) string
- func (c *Config) Save() error
- func (c *Config) Set(key, value string) error
- func (c *Config) SetConfigPath(path string)
- func (c *Config) SetPluginConfig(pluginName string, config map[string]interface{})
- func (c *Config) Validate() error
- type CostConfig
- type DismissalRecord
- type DismissalStatus
- type DismissalStore
- func (s *DismissalStore) CleanExpiredSnoozes() (int, error)
- func (s *DismissalStore) Count() int
- func (s *DismissalStore) Delete(recommendationID string) error
- func (s *DismissalStore) FilePath() string
- func (s *DismissalStore) Get(recommendationID string) (*DismissalRecord, bool)
- func (s *DismissalStore) GetAllRecords() map[string]*DismissalRecord
- func (s *DismissalStore) GetDismissedIDs() []string
- func (s *DismissalStore) GetExpiredSnoozes() []*DismissalRecord
- func (s *DismissalStore) Load() error
- func (s *DismissalStore) Save() error
- func (s *DismissalStore) Set(record *DismissalRecord) error
- type Duration
- type InstalledPlugin
- type InstalledPluginsConfig
- type LastKnownRecommendation
- type LifecycleAction
- type LifecycleEvent
- type LogOutput
- type LoggingConfig
- type OutputConfig
- type ParsedTagSelector
- type PluginConfig
- type PluginHostConfig
- type PluginRouting
- type ResourcePattern
- type RoutingConfig
- type ScopedBudget
- func (s *ScopedBudget) GetCurrency() string
- func (s *ScopedBudget) GetExitCode() *int
- func (s *ScopedBudget) GetPeriod() string
- func (s *ScopedBudget) IsDisabled() bool
- func (s *ScopedBudget) IsEnabled() bool
- func (s *ScopedBudget) ShouldExitOnThreshold() *bool
- func (s *ScopedBudget) Validate(globalCurrency string) error
- type TagBudget
Constants ¶
const ( MaxThresholdPercent = 1000.0 // Allow alerts up to 1000% for extreme overspend detection MinThresholdPercent = 0.0 // Minimum threshold percentage )
Budget validation limits.
const ( MinExitCode = 0 // Minimum valid exit code MaxExitCode = 255 // Maximum valid exit code (Unix standard) )
Exit code limits (Unix standard).
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.
const DefaultBudgetPeriod = "monthly"
DefaultBudgetPeriod is the default period for budget tracking.
const DismissalStoreVersion = 1
DismissalStoreVersion is the current schema version for the dismissal state file.
const PatternTypeGlob = "glob"
PatternTypeGlob is the pattern type for glob matching.
const PatternTypeRegex = "regex"
PatternTypeRegex is the pattern type for regex matching.
Variables ¶
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.
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.
var ErrConfigCorrupted = errors.New("configuration file appears corrupted")
ErrConfigCorrupted is returned in strict mode when the config file exists but cannot be parsed.
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.
var Logger zerolog.Logger
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
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 ¶
GetConfigDir returns the path to the finfocus configuration directory.
func GetDefaultOutputFormat ¶
func GetDefaultOutputFormat() string
GetDefaultOutputFormat returns the configured default output format.
func GetOutputFormat ¶
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 ¶
GetPluginConfiguration returns configuration for a specific plugin.
func GetPluginDir ¶
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 ¶
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
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 ¶
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 ¶
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
ResolveProjectDir determines the project-local .finfocus directory path. It checks (in order):
- flagValue (--project-dir CLI flag)
- FINFOCUS_PROJECT_DIR env var
- 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
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
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 ¶
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.
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 ¶
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
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
ConfigPath returns the config file path.
func (*Config) GetPluginConfig ¶
GetPluginConfig returns configuration for a specific plugin.
func (*Config) PluginPath ¶
PluginPath returns the path for a specific plugin version (backward compatibility).
func (*Config) SetConfigPath ¶ added in v0.3.0
SetConfigPath overrides the config file path used by Load and Save.
func (*Config) SetPluginConfig ¶
SetPluginConfig sets configuration for a specific plugin.
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:
- If a project directory is set (via SetResolvedProjectDir), uses $PROJECT_DIR/dismissed.json.
- 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 ¶
Duration is a wrapper around time.Duration that supports YAML/JSON parsing.
func (Duration) MarshalYAML ¶
MarshalYAML implements yaml.Marshaler 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:*".
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.