diff --git a/analyze/analyze.go b/analyze/analyze.go index 31c64ea..6447b65 100644 --- a/analyze/analyze.go +++ b/analyze/analyze.go @@ -156,6 +156,10 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine } for _, repo := range repoBatch.Repositories { + if a.Config.IsRepoExcluded(repo.GetRepoIdentifier()) { + obs.OnRepoSkipped(repo.GetRepoIdentifier(), "excluded") + continue + } if a.Config.IgnoreForks && repo.GetIsFork() { obs.OnRepoSkipped(repo.GetRepoIdentifier(), "fork") continue diff --git a/cmd/analyzeOrg.go b/cmd/analyzeOrg.go index fb26784..0efbc44 100644 --- a/cmd/analyzeOrg.go +++ b/cmd/analyzeOrg.go @@ -7,7 +7,10 @@ import ( "github.com/spf13/viper" ) -var threads int +var ( + threads int + excludeRepos []string +) // analyzeOrgCmd represents the analyzeOrg command var analyzeOrgCmd = &cobra.Command{ @@ -24,6 +27,9 @@ Note: This command will scan all repositories in the organization except those t Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { Token = viper.GetString("token") + if len(excludeRepos) > 0 { + config.ExcludeRepos = append(config.ExcludeRepos, excludeRepos...) + } ctx := cmd.Context() analyzer, err := GetAnalyzer(ctx, "analyze_org") if err != nil { @@ -52,8 +58,10 @@ func init() { analyzeOrgCmd.Flags().IntVarP(&threads, "threads", "j", 2, "Parallelization factor for scanning organizations") analyzeOrgCmd.Flags().BoolVarP(&config.IgnoreForks, "ignore-forks", "i", false, "Ignore forked repositories in the organization") + analyzeOrgCmd.Flags().StringSliceVarP(&excludeRepos, "exclude-repo", "e", nil, "Exclude specific repositories from organization scan (can be specified multiple times)") viper.BindPFlag("token", analyzeOrgCmd.Flags().Lookup("token")) viper.BindPFlag("ignoreForks", analyzeOrgCmd.Flags().Lookup("ignore-forks")) + _ = viper.BindPFlag("excludeRepos", analyzeOrgCmd.Flags().Lookup("exclude-repo")) viper.BindEnv("token", "GH_TOKEN") } diff --git a/models/config.go b/models/config.go index 5a40b55..23b58c7 100644 --- a/models/config.go +++ b/models/config.go @@ -1,5 +1,7 @@ package models +import "strings" + type ConfigSkip struct { Purl StringList `json:"purl,omitempty"` Path StringList `json:"path,omitempty"` @@ -27,11 +29,26 @@ type Config struct { AllowedRules []string `json:"allowed_rules"` Include []ConfigInclude `json:"include"` IgnoreForks bool `json:"ignore_forks"` + ExcludeRepos []string `json:"exclude_repos,omitempty"` Quiet bool `json:"quiet,omitempty"` RulesConfig map[string]map[string]interface{} `json:"rules_config"` DisableVersionCheck bool `json:"disable_version_check,omitempty"` } +func (c *Config) IsRepoExcluded(repoIdentifier string) bool { + if c == nil || len(c.ExcludeRepos) == 0 { + return false + } + parts := strings.Split(repoIdentifier, "/") + repoName := parts[len(parts)-1] + for _, excluded := range c.ExcludeRepos { + if strings.EqualFold(excluded, repoIdentifier) || strings.EqualFold(excluded, repoName) { + return true + } + } + return false +} + func DefaultConfig() *Config { return &Config{} } diff --git a/models/config_test.go b/models/config_test.go new file mode 100644 index 0000000..70db99e --- /dev/null +++ b/models/config_test.go @@ -0,0 +1,25 @@ +package models_test + +import ( + "testing" + + "github.com/boostsecurityio/poutine/models" + "github.com/stretchr/testify/assert" +) + +func TestConfig_IsRepoExcluded(t *testing.T) { + cfg := &models.Config{ + ExcludeRepos: []string{"org/repo1", "repo2", "org/Repo3"}, + } + + assert.True(t, cfg.IsRepoExcluded("org/repo1")) + assert.True(t, cfg.IsRepoExcluded("org/repo2")) + assert.True(t, cfg.IsRepoExcluded("org/repo3")) + assert.False(t, cfg.IsRepoExcluded("org/repo4")) + + var nilCfg *models.Config + assert.False(t, nilCfg.IsRepoExcluded("org/repo1")) + + emptyCfg := &models.Config{} + assert.False(t, emptyCfg.IsRepoExcluded("org/repo1")) +}