diff --git a/README.md b/README.md index ea2ccf77..eb3c6998 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,9 @@ Opt-Out strategies. like Allstar to run on. If you do not specify any repositories, Allstar will not run despite being installed. Choose the Opt In strategy if you want to enforce policies on only a small number of your total repositories, or want to try - out Allstar on a single repository before enabling it on more. + out Allstar on a single repository before enabling it on more. Since the + v4.3 release, globs are supported to easily add multiple repositories with + a similar name. - The Opt Out strategy (recommended) enables Allstar on all repositories and allows you to manually select the repositories to opt out of Allstar @@ -104,6 +106,8 @@ Opt-Out strategies. private repos. Choose this option if you want to run Allstar on all repositories in an organization, or want to opt out only a small number of repositories or specific type (i.e., public vs. private) of repository. + Since the v4.3 release, globs are supported to easily add multiple + repositories with a similar name. diff --git a/pkg/config/config.go b/pkg/config/config.go index 7658ea60..a8c989c9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,6 +22,7 @@ import ( "path" "strings" + "github.com/gobwas/glob" "github.com/ossf/allstar/pkg/config/operator" jsonpatch "github.com/evanphx/json-patch/v5" @@ -131,6 +132,8 @@ type ScheduleConfig struct { Days []string `json:"days"` } +type globCache map[string]glob.Glob + const githubConfRepo = ".github" // ConfigLevel is an enum to indicate which level config to retrieve for the @@ -155,6 +158,8 @@ var walkGC func(context.Context, repositories, string, string, string, *github.RepositoryContentGetOptions) (*github.RepositoryContent, []*github.RepositoryContent, *github.Response, error) +var gc = globCache{} + func init() { walkGC = walkGetContents } @@ -298,7 +303,7 @@ func isEnabled(ctx context.Context, o OrgOptConfig, orc, r RepoOptConfig, rep re if o.OptOutStrategy { enabled = true - if contains(o.OptOutRepos, repo) { + if matches(o.OptOutRepos, repo, gc) { enabled = false } if o.OptOutPrivateRepos && gr.GetPrivate() { @@ -321,7 +326,7 @@ func isEnabled(ctx context.Context, o OrgOptConfig, orc, r RepoOptConfig, rep re } } else { enabled = false - if contains(o.OptInRepos, repo) { + if matches(o.OptInRepos, repo, gc) { enabled = true } if orc.OptIn { @@ -404,11 +409,32 @@ func getAppConfigs(ctx context.Context, r repositories, owner, repo string) (*Or return oc, orc, rc } -func contains(s []string, e string) bool { +func matches(s []string, e string, gc globCache) bool { for _, v := range s { - if v == e { + g, err := gc.compileGlob(v) + if err != nil { + log.Warn(). + Str("repo", e). + Str("glob", v). + Err(err). + Msg("Unexpected error compiling the glob.") + } else if g.Match(e) { return true } + } return false } + +// compileGlob returns cached glob if present, otherwise attempts glob.Compile. +func (g globCache) compileGlob(s string) (glob.Glob, error) { + if glob, ok := g[s]; ok { + return glob, nil + } + c, err := glob.Compile(s) + if err != nil { + return nil, err + } + g[s] = c + return c, nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c4452134..587c0ab7 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -205,6 +205,17 @@ func TestIsEnabled(t *testing.T) { IsPrivateRepo: false, Expect: true, }, + { + Name: "OptInOrg", + Org: OrgOptConfig{ + OptOutStrategy: false, + OptInRepos: []string{"this*"}, + }, + OrgRepo: RepoOptConfig{}, + Repo: RepoOptConfig{}, + IsPrivateRepo: false, + Expect: true, + }, { Name: "NoOptInOrg", Org: OrgOptConfig{ @@ -216,6 +227,28 @@ func TestIsEnabled(t *testing.T) { IsPrivateRepo: false, Expect: false, }, + { + Name: "NoOptInOrg", + Org: OrgOptConfig{ + OptOutStrategy: false, + OptInRepos: []string{"other*"}, + }, + OrgRepo: RepoOptConfig{}, + Repo: RepoOptConfig{}, + IsPrivateRepo: false, + Expect: false, + }, + { + Name: "NoOptInOrg", + Org: OrgOptConfig{ + OptOutStrategy: false, + OptInRepos: []string{"this*xyz"}, + }, + OrgRepo: RepoOptConfig{}, + Repo: RepoOptConfig{}, + IsPrivateRepo: false, + Expect: false, + }, { Name: "OptOutOrg", Org: OrgOptConfig{