Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .shepherd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Example .shepherd.yml

include_user_repo: false
url: ""
dry_run: false
debug: false
organizations:
- orgName: "alfredcubed"
maintainer: "core-maintainers"
protected_branch: "master"
repos:
- name:
maintainer:
templates:
issue_template:
pr_template:
codeowner_template:
pr_msg_template:



88 changes: 34 additions & 54 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@ import (
"flag"
"fmt"
"os"
"strings"

"github.com/google/go-github/github"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/srizzling/shepherd/shepherd"
)

var version = "master"

var (
token string
baseURL string
org string
dryRun bool
maintainer string
pbranch string

vrsn bool
token string
vrsnFlag bool
configuration shepherd.Config
)

const (
Expand All @@ -42,60 +39,42 @@ developed with <3 by Sriram Venkatesh
)

func init() {
// parse flags
flag.StringVar(&token, "token", os.Getenv("GITHUB_TOKEN"), "required: GitHub API token (or env var GITHUB_TOKEN)")
flag.StringVar(&org, "org", "", "required: organization to look through")
flag.StringVar(&pbranch, "branch", "master", "branch to protect")

flag.StringVar(&baseURL, "url", "", "optional: GitHub Enterprise URL")
flag.StringVar(&maintainer, "maintainer", "", "required: team to set as CODEOWNERS")
flag.BoolVar(&dryRun, "dryrun", false, "optional: do not change branch settings just print the changes that would occur")

flag.BoolVar(&vrsn, "version", false, "optional: print version and exit")

// Exit safely when version is used
if vrsn {
fmt.Printf(BANNER, version)
os.Exit(0)
// Intialize Viper
viper.SetConfigName(".shepherd")
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetEnvPrefix("shepherd")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv() // read in environment variables that match
viper.BindEnv("GITHUB_TOKEN")

if err := viper.ReadInConfig(); err != nil {
logrus.Fatalf("Error reading config file, %s", err)
panic(err)
}

flag.Usage = func() {
fmt.Fprint(os.Stderr, fmt.Sprintf(BANNER, version))
flag.PrintDefaults()
if err := viper.Unmarshal(&configuration); err != nil {
logrus.Fatalf("Error marshalling config file, %s", err)
panic(err)
}
flag.Parse()

token = configuration.GithubToken
if token == "" {
usageAndExit("GitHub token cannot be empty.", 1)
}

if org == "" {
usageAndExit("no organization provided", 1)
}

if maintainer == "" {
usageAndExit("no organization provided", 1)
usageAndExit("Error! Github Token is required", 1)
}

}

func main() {
// intialize bot
bot, err := shepherd.NewBot(baseURL, token, maintainer, org)
if err != nil {
logrus.Fatal(err)
panic(err)
}

//Retreive repos that are owned by the org
repos, err := bot.RetreiveRepos()
bot, err := shepherd.NewBot(configuration)
if err != nil {
logrus.Fatal(err)
panic(err)
}

for _, repo := range repos {
err = handleRepo(bot, repo)
for repo, repoConfig := range bot.Repos {
err = handleRepo(bot, repo, repoConfig)
if err != nil {
logrus.Fatal(err)
panic(err)
Expand All @@ -104,8 +83,9 @@ func main() {
}

// a function that will be applied to each repo on an org
func handleRepo(bot *shepherd.ShepardBot, repo *github.Repository) error {
b, err := bot.GetBranch(repo, pbranch)
func handleRepo(bot *shepherd.ShepardBot, repo *github.Repository, repoConfig shepherd.RepoConfig) error {
b, err := bot.GetBranch(repo, repoConfig.ProtectedBranch)
dryRun := configuration.DryRun
if err != nil {
return err
}
Expand All @@ -119,7 +99,7 @@ func handleRepo(bot *shepherd.ShepardBot, repo *github.Repository) error {
fmt.Printf("[UPDATE REQUIRED] %s: A codeowner file was not found, a PR should be created\n", *repo.FullName)

if !dryRun {
pr, err := bot.DoCreateCodeowners(repo, b)
pr, err := bot.DoCreateCodeowners(repo, b, repoConfig.GHMaintainer)
if err != nil {
return err
}
Expand All @@ -134,23 +114,23 @@ func handleRepo(bot *shepherd.ShepardBot, repo *github.Repository) error {
fmt.Printf("[OK] %s: CODEOWNERS file already exists in repo\n", *repo.FullName)

//Need to assign team to the repo even its in the org to be a "maintainer"
repoManagement, err := bot.CheckTeamRepoManagement(repo)
repoManagement, err := bot.CheckTeamRepoManagement(repo, repoConfig.GHMaintainer)

if err != nil {
return err
}

if repoManagement {
fmt.Printf("[OK] %s: is already managed by %s\n", *repo.FullName, maintainer)
fmt.Printf("[OK] %s: is already managed by %s\n", *repo.FullName, repoConfig.Maintainer)
} else {
fmt.Printf("[UPDATE REQUIRED] %s: needs to updated to be managed by %s\n", *repo.FullName, maintainer)
fmt.Printf("[UPDATE REQUIRED] %s: needs to updated to be managed by %s\n", *repo.FullName, repoConfig.Maintainer)

if !dryRun {
err = bot.DoTeamRepoManagement(repo)
err = bot.DoTeamRepoManagement(repo, repoConfig.GHMaintainer)
if err != nil {
return err
}
fmt.Printf("[OK] %s: is now managed by %s\n", *repo.FullName, maintainer)
fmt.Printf("[OK] %s: is now managed by %s\n", *repo.FullName, repoConfig.Maintainer)
}
}

Expand Down
14 changes: 7 additions & 7 deletions shepherd/codeowners.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func (s *ShepardBot) createBranch(repo *github.Repository, refObj *github.Refere
return err
}

func (s *ShepardBot) commitFileToBranch(repo *github.Repository, branchName string) error {
func (s *ShepardBot) commitFileToBranch(repo *github.Repository, branchName string, maintainer *github.Team) error {
content := []byte(
fmt.Sprintf("* @%s/%s", s.org.GetLogin(), s.maintainerTeam.GetName()),
fmt.Sprintf("* @%s/%s", maintainer.GetOrganization().GetLogin(), maintainer.GetName()),
)

_, _, err := s.gClient.Repositories.CreateFile(
Expand All @@ -38,8 +38,8 @@ func (s *ShepardBot) commitFileToBranch(repo *github.Repository, branchName stri
return err
}

func (s *ShepardBot) createPR(repo *github.Repository, branchName string, branch *github.Branch) (*github.PullRequest, error) {
prMessage := fmt.Sprintf("Hi there @%s!,\n\nI'm your helpful shepherd and I've found that you are missing an important CODEOWNERS file which is mandated to be included for repos within this org (this ensures that the maintainers are pinged to review PR as they come in).\n\nThis PR is automatically created by [shepherd](https://github.com/srizzling/shepherd)\n\nThanks,\nShepard Bot", s.maintainerTeam.GetName())
func (s *ShepardBot) createPR(repo *github.Repository, branchName string, branch *github.Branch, maintainer *github.Team) (*github.PullRequest, error) {
prMessage := fmt.Sprintf("Hi there @%s!,\n\nI'm your helpful shepherd and I've found that you are missing an important CODEOWNERS file which is mandated to be included for repos within this org (this ensures that the maintainers are pinged to review PR as they come in).\n\nThis PR is automatically created by [shepherd](https://github.com/srizzling/shepherd)\n\nThanks,\nShepard Bot", maintainer.GetName())

// Create a PR with the branch created
newPR := &github.NewPullRequest{
Expand All @@ -56,7 +56,7 @@ func (s *ShepardBot) createPR(repo *github.Repository, branchName string, branch

// DoCreateCodeowners function will create a CODEOWNERS file in a branch, create a PR against the repo
// and set the reviewer (of the CODEOWNERS PR) as the maintainer team configured
func (s *ShepardBot) DoCreateCodeowners(repo *github.Repository, branch *github.Branch) (*github.PullRequest, error) {
func (s *ShepardBot) DoCreateCodeowners(repo *github.Repository, branch *github.Branch, maintainer *github.Team) (*github.PullRequest, error) {
// Create a branch on the repo, from current master
sRand, err := randomstrings.GenerateRandomString(5)
if err != nil {
Expand All @@ -77,13 +77,13 @@ func (s *ShepardBot) DoCreateCodeowners(repo *github.Repository, branch *github.
}

// Commit CODEOWNERS file to Branch
err = s.commitFileToBranch(repo, branchName)
err = s.commitFileToBranch(repo, branchName, maintainer)
if err != nil {
return nil, err
}

// Create PR with newly created branch
pr, err := s.createPR(repo, branchName, branch)
pr, err := s.createPR(repo, branchName, branch, maintainer)
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions shepherd/repoTeam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
)

// CheckTeamRepoManagement verifies if the team is an admin of the project
func (s *ShepardBot) CheckTeamRepoManagement(repo *github.Repository) (bool, error) {
func (s *ShepardBot) CheckTeamRepoManagement(repo *github.Repository, maintainer *github.Team) (bool, error) {

_, response, err := s.gClient.Organizations.IsTeamRepo(
s.ctx,
*s.maintainerTeam.ID,
*maintainer.ID,
*repo.Owner.Login,
*repo.Name,
)
Expand All @@ -33,11 +33,11 @@ func (s *ShepardBot) CheckTeamRepoManagement(repo *github.Repository) (bool, err
}

// DoTeamRepoManagement sets the team as an admin of the repo
func (s *ShepardBot) DoTeamRepoManagement(repo *github.Repository) error {
func (s *ShepardBot) DoTeamRepoManagement(repo *github.Repository, maintainer *github.Team) error {
opt := &github.OrganizationAddTeamRepoOptions{
Permission: "admin",
}

_, err := s.gClient.Organizations.AddTeamRepo(s.ctx, *s.maintainerTeam.ID, *repo.Owner.Login, *repo.Name, opt)
_, err := s.gClient.Organizations.AddTeamRepo(s.ctx, *maintainer.ID, *repo.Owner.Login, *repo.Name, opt)
return err
}
Loading