From 2cbdf3005a78e8d1bd19a4f43bf5edfa303060d7 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:38:16 +1300 Subject: [PATCH 1/3] refactor(config): move manager code into a dedicated file --- internal/config/config.go | 127 ----------------------------------- internal/config/manager.go | 133 +++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 127 deletions(-) create mode 100644 internal/config/manager.go diff --git a/internal/config/config.go b/internal/config/config.go index 4ed0f4c5078..503daa96b80 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,30 +2,15 @@ package config import ( - "errors" - "fmt" - "os" - "path/filepath" "slices" - "strings" "time" - "github.com/BurntSushi/toml" "github.com/google/osv-scanner/v2/internal/cmdlogger" "github.com/google/osv-scanner/v2/internal/imodels" ) var OSVScannerConfigName = "osv-scanner.toml" -type Manager struct { - // Override to replace all other configs - OverrideConfig *Config - // Config to use if no config file is found alongside manifests - DefaultConfig Config - // Cache to store loaded configs - ConfigMap map[string]Config -} - type Config struct { IgnoredVulns []*IgnoreEntry `toml:"IgnoredVulns"` PackageOverrides []PackageOverrideEntry `toml:"PackageOverrides"` @@ -156,118 +141,6 @@ func shouldIgnoreTimestamp(ignoreUntil time.Time) bool { return ignoreUntil.After(time.Now()) } -// UseOverride updates the Manager to use the config at the given path in place -// of any other config files that would be loaded when calling Get -func (c *Manager) UseOverride(configPath string) error { - config, configErr := tryLoadConfig(configPath) - if configErr != nil { - return configErr - } - c.OverrideConfig = &config - - return nil -} - -// Get returns the appropriate config to use based on the targetPath -func (c *Manager) Get(targetPath string) Config { - if c.OverrideConfig != nil { - return *c.OverrideConfig - } - - configPath, err := normalizeConfigLoadPath(targetPath) - if err != nil { - // TODO: This can happen when target is not a file (e.g. Docker container, git hash...etc.) - // Figure out a more robust way to load config from non files - // r.PrintErrorf("Can't find config path: %s\n", err) - return Config{} - } - - config, alreadyExists := c.ConfigMap[configPath] - if alreadyExists { - return config - } - - config, configErr := tryLoadConfig(configPath) - if configErr == nil { - cmdlogger.Infof("Loaded filter from: %s", config.LoadPath) - } else { - // anything other than the config file not existing is most likely due to an invalid config file - if !errors.Is(configErr, os.ErrNotExist) { - cmdlogger.Errorf("Ignored invalid config file at %s because: %v", configPath, configErr) - } - // If config doesn't exist, use the default config - config = c.DefaultConfig - } - c.ConfigMap[configPath] = config - - return config -} - -func (c *Manager) GetUnusedIgnoreEntries() map[string][]*IgnoreEntry { - m := make(map[string][]*IgnoreEntry) - - for _, config := range c.ConfigMap { - unusedEntries := config.UnusedIgnoredVulns() - - if len(unusedEntries) > 0 { - m[config.LoadPath] = unusedEntries - } - } - - if c.OverrideConfig != nil { - unusedEntries := c.OverrideConfig.UnusedIgnoredVulns() - - if len(unusedEntries) > 0 { - m[c.OverrideConfig.LoadPath] = unusedEntries - } - } - - return m -} - -// Finds the containing folder of `target`, then appends osvScannerConfigName -func normalizeConfigLoadPath(target string) (string, error) { - stat, err := os.Stat(target) - if err != nil { - return "", fmt.Errorf("failed to stat target: %w", err) - } - - var containingFolder string - if !stat.IsDir() { - containingFolder = filepath.Dir(target) - } else { - containingFolder = target - } - configPath := filepath.Join(containingFolder, OSVScannerConfigName) - - return configPath, nil -} - -// tryLoadConfig attempts to parse the config file at the given path as TOML, -// returning the Config object if successful or otherwise the error -func tryLoadConfig(configPath string) (Config, error) { - config := Config{} - m, err := toml.DecodeFile(configPath, &config) - if err == nil { - unknownKeys := m.Undecoded() - - if len(unknownKeys) > 0 { - keys := make([]string, 0, len(unknownKeys)) - - for _, key := range unknownKeys { - keys = append(keys, key.String()) - } - - return Config{}, fmt.Errorf("unknown keys in config file: %s", strings.Join(keys, ", ")) - } - - config.LoadPath = configPath - config.warnAboutDuplicates() - } - - return config, err -} - func (c *Config) warnAboutDuplicates() { seen := make(map[string]struct{}) diff --git a/internal/config/manager.go b/internal/config/manager.go new file mode 100644 index 00000000000..1514038b22d --- /dev/null +++ b/internal/config/manager.go @@ -0,0 +1,133 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "github.com/google/osv-scanner/v2/internal/cmdlogger" +) + +type Manager struct { + // Override to replace all other configs + OverrideConfig *Config + // Config to use if no config file is found alongside manifests + DefaultConfig Config + // Cache to store loaded configs + ConfigMap map[string]Config +} + +// UseOverride updates the Manager to use the config at the given path in place +// of any other config files that would be loaded when calling Get +func (c *Manager) UseOverride(configPath string) error { + config, configErr := tryLoadConfig(configPath) + if configErr != nil { + return configErr + } + c.OverrideConfig = &config + + return nil +} + +// Get returns the appropriate config to use based on the targetPath +func (c *Manager) Get(targetPath string) Config { + if c.OverrideConfig != nil { + return *c.OverrideConfig + } + + configPath, err := normalizeConfigLoadPath(targetPath) + if err != nil { + // TODO: This can happen when target is not a file (e.g. Docker container, git hash...etc.) + // Figure out a more robust way to load config from non files + // r.PrintErrorf("Can't find config path: %s\n", err) + return Config{} + } + + config, alreadyExists := c.ConfigMap[configPath] + if alreadyExists { + return config + } + + config, configErr := tryLoadConfig(configPath) + if configErr == nil { + cmdlogger.Infof("Loaded filter from: %s", config.LoadPath) + } else { + // anything other than the config file not existing is most likely due to an invalid config file + if !errors.Is(configErr, os.ErrNotExist) { + cmdlogger.Errorf("Ignored invalid config file at %s because: %v", configPath, configErr) + } + // If config doesn't exist, use the default config + config = c.DefaultConfig + } + c.ConfigMap[configPath] = config + + return config +} + +func (c *Manager) GetUnusedIgnoreEntries() map[string][]*IgnoreEntry { + m := make(map[string][]*IgnoreEntry) + + for _, config := range c.ConfigMap { + unusedEntries := config.UnusedIgnoredVulns() + + if len(unusedEntries) > 0 { + m[config.LoadPath] = unusedEntries + } + } + + if c.OverrideConfig != nil { + unusedEntries := c.OverrideConfig.UnusedIgnoredVulns() + + if len(unusedEntries) > 0 { + m[c.OverrideConfig.LoadPath] = unusedEntries + } + } + + return m +} + +// Finds the containing folder of `target`, then appends osvScannerConfigName +func normalizeConfigLoadPath(target string) (string, error) { + stat, err := os.Stat(target) + if err != nil { + return "", fmt.Errorf("failed to stat target: %w", err) + } + + var containingFolder string + if !stat.IsDir() { + containingFolder = filepath.Dir(target) + } else { + containingFolder = target + } + configPath := filepath.Join(containingFolder, OSVScannerConfigName) + + return configPath, nil +} + +// tryLoadConfig attempts to parse the config file at the given path as TOML, +// returning the Config object if successful or otherwise the error +func tryLoadConfig(configPath string) (Config, error) { + config := Config{} + m, err := toml.DecodeFile(configPath, &config) + if err == nil { + unknownKeys := m.Undecoded() + + if len(unknownKeys) > 0 { + keys := make([]string, 0, len(unknownKeys)) + + for _, key := range unknownKeys { + keys = append(keys, key.String()) + } + + return Config{}, fmt.Errorf("unknown keys in config file: %s", strings.Join(keys, ", ")) + } + + config.LoadPath = configPath + config.warnAboutDuplicates() + } + + return config, err +} From 23d2121399c1c16f343b112837f83c25a60a3c97 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:42:48 +1300 Subject: [PATCH 2/3] refactor: move function to be after struct definitions --- internal/config/config.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 503daa96b80..3ea7dcecbab 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,18 +20,6 @@ type Config struct { LoadPath string `toml:"-"` } -func (c *Config) UnusedIgnoredVulns() []*IgnoreEntry { - unused := make([]*IgnoreEntry, 0, len(c.IgnoredVulns)) - - for _, entry := range c.IgnoredVulns { - if !entry.Used { - unused = append(unused, entry) - } - } - - return unused -} - type IgnoreEntry struct { ID string `toml:"id"` IgnoreUntil time.Time `toml:"ignoreUntil"` @@ -86,6 +74,18 @@ type License struct { Ignore bool `toml:"ignore"` } +func (c *Config) UnusedIgnoredVulns() []*IgnoreEntry { + unused := make([]*IgnoreEntry, 0, len(c.IgnoredVulns)) + + for _, entry := range c.IgnoredVulns { + if !entry.Used { + unused = append(unused, entry) + } + } + + return unused +} + func (c *Config) ShouldIgnore(vulnID string) (bool, *IgnoreEntry) { index := slices.IndexFunc(c.IgnoredVulns, func(e *IgnoreEntry) bool { return e.ID == vulnID }) if index == -1 { From 6484d198cd8671c4242d04eb999d6a8c611f8dde Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:47:20 +1300 Subject: [PATCH 3/3] refactor: rename pointer receiver name --- internal/config/manager.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/config/manager.go b/internal/config/manager.go index 1514038b22d..6b5e0acfc5d 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -22,20 +22,20 @@ type Manager struct { // UseOverride updates the Manager to use the config at the given path in place // of any other config files that would be loaded when calling Get -func (c *Manager) UseOverride(configPath string) error { +func (m *Manager) UseOverride(configPath string) error { config, configErr := tryLoadConfig(configPath) if configErr != nil { return configErr } - c.OverrideConfig = &config + m.OverrideConfig = &config return nil } // Get returns the appropriate config to use based on the targetPath -func (c *Manager) Get(targetPath string) Config { - if c.OverrideConfig != nil { - return *c.OverrideConfig +func (m *Manager) Get(targetPath string) Config { + if m.OverrideConfig != nil { + return *m.OverrideConfig } configPath, err := normalizeConfigLoadPath(targetPath) @@ -46,7 +46,7 @@ func (c *Manager) Get(targetPath string) Config { return Config{} } - config, alreadyExists := c.ConfigMap[configPath] + config, alreadyExists := m.ConfigMap[configPath] if alreadyExists { return config } @@ -60,33 +60,33 @@ func (c *Manager) Get(targetPath string) Config { cmdlogger.Errorf("Ignored invalid config file at %s because: %v", configPath, configErr) } // If config doesn't exist, use the default config - config = c.DefaultConfig + config = m.DefaultConfig } - c.ConfigMap[configPath] = config + m.ConfigMap[configPath] = config return config } -func (c *Manager) GetUnusedIgnoreEntries() map[string][]*IgnoreEntry { - m := make(map[string][]*IgnoreEntry) +func (m *Manager) GetUnusedIgnoreEntries() map[string][]*IgnoreEntry { + entries := make(map[string][]*IgnoreEntry) - for _, config := range c.ConfigMap { + for _, config := range m.ConfigMap { unusedEntries := config.UnusedIgnoredVulns() if len(unusedEntries) > 0 { - m[config.LoadPath] = unusedEntries + entries[config.LoadPath] = unusedEntries } } - if c.OverrideConfig != nil { - unusedEntries := c.OverrideConfig.UnusedIgnoredVulns() + if m.OverrideConfig != nil { + unusedEntries := m.OverrideConfig.UnusedIgnoredVulns() if len(unusedEntries) > 0 { - m[c.OverrideConfig.LoadPath] = unusedEntries + entries[m.OverrideConfig.LoadPath] = unusedEntries } } - return m + return entries } // Finds the containing folder of `target`, then appends osvScannerConfigName @@ -111,9 +111,9 @@ func normalizeConfigLoadPath(target string) (string, error) { // returning the Config object if successful or otherwise the error func tryLoadConfig(configPath string) (Config, error) { config := Config{} - m, err := toml.DecodeFile(configPath, &config) + c, err := toml.DecodeFile(configPath, &config) if err == nil { - unknownKeys := m.Undecoded() + unknownKeys := c.Undecoded() if len(unknownKeys) > 0 { keys := make([]string, 0, len(unknownKeys))