From 3de5719aeb0c673a7cac71720ab68c3b22fd60f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Wed, 14 Oct 2026 11:02:04 +0300 Subject: [PATCH 1/2] runner: report skipped auto-discovery providers --- internal/runner/autodiscovery.go | 20 ++++++++++++++------ internal/runner/autodiscovery_test.go | 21 ++++++++++++++++++--- internal/runner/options.go | 4 +++- internal/runner/runner_test.go | 13 +++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/internal/runner/autodiscovery.go b/internal/runner/autodiscovery.go index 0cf481ec..6c5d57e7 100644 --- a/internal/runner/autodiscovery.go +++ b/internal/runner/autodiscovery.go @@ -2,6 +2,8 @@ package runner import ( "errors" + "fmt" + "strings" "github.com/projectdiscovery/cloudlist/pkg/inventory" "github.com/projectdiscovery/cloudlist/pkg/schema" @@ -9,8 +11,7 @@ import ( ) // autoDiscoveryProviders resolve credentials without config through their SDK -// default chains: environment variables, CLI logins, instance metadata and -// in-cluster service accounts. +// default credential chains. var autoDiscoveryProviders = []string{"aws", "gcp", "azure", "kubernetes"} func autoDiscoveryConfig() schema.Options { @@ -25,19 +26,26 @@ func (r *Runner) newInventory(config schema.Options) (*inventory.Inventory, erro if !r.options.AutoDiscovery { return inventory.New(config) } - // A provider fails to initialize when the environment has no credentials - // for it, which is expected here, so skip it instead of aborting. + if len(config) == 0 { + return nil, fmt.Errorf("no auto-discovery provider matches the -p/-id filters, supported: %s", strings.Join(autoDiscoveryProviders, ",")) + } + // Init fails for missing credentials but also for denied, expired or + // unreachable ones, so skip failed providers and report why. discovered := &inventory.Inventory{} + var errs []error for _, block := range config { inv, err := inventory.New(schema.Options{block}) if err != nil { - gologger.Verbose().Msgf("Skipping %s: %s", block["provider"], err) + errs = append(errs, err) continue } discovered.Providers = append(discovered.Providers, inv.Providers...) } if len(discovered.Providers) == 0 { - return nil, errors.New("no provider credentials found in the environment") + return nil, fmt.Errorf("no provider could be initialized: %w", errors.Join(errs...)) + } + for _, err := range errs { + gologger.Warning().Msgf("Skipping provider: %s", err) } return discovered, nil } diff --git a/internal/runner/autodiscovery_test.go b/internal/runner/autodiscovery_test.go index d2b78444..2f9e7e21 100644 --- a/internal/runner/autodiscovery_test.go +++ b/internal/runner/autodiscovery_test.go @@ -9,7 +9,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewInventorySkipsProvidersWithoutCredentials(t *testing.T) { +func writeKubeconfig(t *testing.T) string { + t.Helper() kubeconfig := filepath.Join(t.TempDir(), "config") require.NoError(t, os.WriteFile(kubeconfig, []byte(`apiVersion: v1 kind: Config @@ -23,6 +24,11 @@ contexts: cluster: test current-context: test `), 0o600)) + return kubeconfig +} + +func TestNewInventorySkipsProvidersWithoutCredentials(t *testing.T) { + kubeconfig := writeKubeconfig(t) r := &Runner{options: &Options{AutoDiscovery: true}} inv, err := r.newInventory(schema.Options{ @@ -35,7 +41,15 @@ current-context: test _, err = r.newInventory(schema.Options{ {"provider": "kubernetes", "kubeconfig_file": filepath.Join(t.TempDir(), "missing")}, }) - require.EqualError(t, err, "no provider credentials found in the environment") + require.ErrorContains(t, err, "no provider could be initialized") + require.ErrorContains(t, err, "could not create provider kubernetes") +} + +func TestNewInventoryWithoutMatchingProviders(t *testing.T) { + r := &Runner{options: &Options{AutoDiscovery: true}} + + _, err := r.newInventory(schema.Options{}) + require.ErrorContains(t, err, "no auto-discovery provider matches the -p/-id filters") } func TestNewInventoryFailsFastWithoutAutoDiscovery(t *testing.T) { @@ -43,6 +57,7 @@ func TestNewInventoryFailsFastWithoutAutoDiscovery(t *testing.T) { _, err := r.newInventory(schema.Options{ {"provider": "kubernetes", "kubeconfig_file": filepath.Join(t.TempDir(), "missing")}, + {"provider": "kubernetes", "kubeconfig_file": writeKubeconfig(t)}, }) - require.Error(t, err) + require.ErrorContains(t, err, "could not create provider kubernetes") } diff --git a/internal/runner/options.go b/internal/runner/options.go index 3d93efeb..d3887d68 100644 --- a/internal/runner/options.go +++ b/internal/runner/options.go @@ -123,7 +123,9 @@ func ParseOptions() *Options { } } - checkAndCreateProviderConfigFile(options) + if !options.AutoDiscovery { + checkAndCreateProviderConfigFile(options) + } return options } diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 463e1c34..6eac0ebc 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -1,9 +1,11 @@ package runner import ( + "path/filepath" "testing" "github.com/projectdiscovery/cloudlist/pkg/schema" + "github.com/stretchr/testify/require" ) func TestSanitizePrivateIPs(t *testing.T) { @@ -56,3 +58,14 @@ func TestSanitizePrivateIPsOnMisclassifiedFields(t *testing.T) { t.Fatalf("expected public addresses to remain when exclusion enabled") } } + +func TestNewWithAutoDiscoveryIgnoresProviderConfig(t *testing.T) { + r, err := New(&Options{AutoDiscovery: true, ProviderConfig: filepath.Join(t.TempDir(), "missing.yaml")}) + require.NoError(t, err) + + var providers []string + for _, block := range r.config { + providers = append(providers, block["provider"]) + } + require.Equal(t, autoDiscoveryProviders, providers) +} From a1537233e7226ea1d5f586a6f51408fc296ac165 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Wed, 14 Oct 2026 23:16:53 +0200 Subject: [PATCH 2/2] log skips --- internal/runner/autodiscovery.go | 3 +- internal/runner/autodiscovery_test.go | 43 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/internal/runner/autodiscovery.go b/internal/runner/autodiscovery.go index 6c5d57e7..57765d07 100644 --- a/internal/runner/autodiscovery.go +++ b/internal/runner/autodiscovery.go @@ -45,7 +45,8 @@ func (r *Runner) newInventory(config schema.Options) (*inventory.Inventory, erro return nil, fmt.Errorf("no provider could be initialized: %w", errors.Join(errs...)) } for _, err := range errs { - gologger.Warning().Msgf("Skipping provider: %s", err) + // Info stays visible at the default log level. Warning is dropped unless -v is set. + gologger.Info().Msgf("Skipping provider: %s", err) } return discovered, nil } diff --git a/internal/runner/autodiscovery_test.go b/internal/runner/autodiscovery_test.go index 2f9e7e21..8587c281 100644 --- a/internal/runner/autodiscovery_test.go +++ b/internal/runner/autodiscovery_test.go @@ -3,12 +3,50 @@ package runner import ( "os" "path/filepath" + "strings" "testing" "github.com/projectdiscovery/cloudlist/pkg/schema" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" + "github.com/projectdiscovery/gologger/writer" "github.com/stretchr/testify/require" ) +type logRecord struct { + message string + level levels.Level +} + +type captureWriter struct { + records []logRecord +} + +func (w *captureWriter) Write(data []byte, level levels.Level) { + w.records = append(w.records, logRecord{message: string(data), level: level}) +} + +func captureDefaultLog(t *testing.T) *captureWriter { + t.Helper() + captured := &captureWriter{} + gologger.DefaultLogger.SetMaxLevel(levels.LevelInfo) + gologger.DefaultLogger.SetWriter(captured) + t.Cleanup(func() { + gologger.DefaultLogger.SetMaxLevel(levels.LevelInfo) + gologger.DefaultLogger.SetWriter(writer.NewCLI()) + }) + return captured +} + +func skipMessage(records []logRecord) string { + for _, rec := range records { + if rec.level == levels.LevelInfo && strings.Contains(rec.message, "Skipping provider") { + return rec.message + } + } + return "" +} + func writeKubeconfig(t *testing.T) string { t.Helper() kubeconfig := filepath.Join(t.TempDir(), "config") @@ -30,6 +68,7 @@ current-context: test func TestNewInventorySkipsProvidersWithoutCredentials(t *testing.T) { kubeconfig := writeKubeconfig(t) r := &Runner{options: &Options{AutoDiscovery: true}} + logs := captureDefaultLog(t) inv, err := r.newInventory(schema.Options{ {"provider": "kubernetes", "kubeconfig_file": filepath.Join(t.TempDir(), "missing")}, @@ -37,12 +76,16 @@ func TestNewInventorySkipsProvidersWithoutCredentials(t *testing.T) { }) require.NoError(t, err) require.Len(t, inv.Providers, 1) + skip := skipMessage(logs.records) + require.Contains(t, skip, "could not create provider kubernetes") + logs.records = nil _, err = r.newInventory(schema.Options{ {"provider": "kubernetes", "kubeconfig_file": filepath.Join(t.TempDir(), "missing")}, }) require.ErrorContains(t, err, "no provider could be initialized") require.ErrorContains(t, err, "could not create provider kubernetes") + require.Empty(t, skipMessage(logs.records)) } func TestNewInventoryWithoutMatchingProviders(t *testing.T) {