commit 28b00846319eb6b2d9902e3b1a8acee8494b73f6 Author: Knut Ahlers Date: Wed Feb 7 10:02:13 2018 +0100 Initial version diff --git a/filters.go b/filters.go new file mode 100644 index 0000000..8dc45c4 --- /dev/null +++ b/filters.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + + "github.com/google/go-github/github" + log "github.com/sirupsen/logrus" +) + +type filterFunc func(*github.Repository) bool + +var filters = map[string]filterFunc{ + "fork": filterFork, + "dockerfile": filterDockerfile, + "public": filterPublic, +} + +func filterDockerfile(repo *github.Repository) bool { + ctx := context.Background() + _, _, resp, err := client.Repositories.GetContents(ctx, *repo.Owner.Login, *repo.Name, "Dockerfile", nil) + if err != nil { + if resp.StatusCode == 404 { + return false + } + + log.WithError(err).Error("Error while looking for Dockerfile") + return false + } + + return true +} + +func filterFork(repo *github.Repository) bool { return repo.Fork != nil && *repo.Fork } +func filterPublic(repo *github.Repository) bool { return repo.Private != nil && !*repo.Private } diff --git a/main.go b/main.go new file mode 100644 index 0000000..148ebb9 --- /dev/null +++ b/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "os" + "regexp" + "strings" + "text/template" + + "golang.org/x/oauth2" + + "github.com/Luzifer/rconfig" + "github.com/google/go-github/github" + "github.com/gosimple/slug" + log "github.com/sirupsen/logrus" +) + +var ( + cfg = struct { + Filters []string `flag:"filter,f" default:"" description:"Filters to match the repos against"` + GithubToken string `flag:"token" default:"" env:"GITHUB_TOKEN" description:"Token to access Github API"` + LogLevel string `flag:"log-level" default:"info" description:"Log level for output (debug, info, warn, error, fatal)"` + NameRegex string `flag:"name-regex" default:".*" description:"Regex to match the name against"` + Output string `flag:"out,o" default:"-" description:"File to write to (- = stdout)"` + Template string `flag:"template" default:"" description:"Template file to use for rendering" validate:"nonzero"` + VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"` + }{} + + client *github.Client + + version = "dev" +) + +func init() { + if err := rconfig.ParseAndValidate(&cfg); err != nil { + log.Fatalf("Unable to parse commandline options: %s", err) + } + + if l, err := log.ParseLevel(cfg.LogLevel); err != nil { + log.WithError(err).Fatalf("Could not parse log level") + } else { + log.SetLevel(l) + } + + if cfg.VersionAndExit { + fmt.Printf("repo-template %s\n", version) + os.Exit(0) + } +} + +func main() { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: cfg.GithubToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + client = github.NewClient(tc) + + repos, err := fetchRepos() + if err != nil { + log.WithError(err).Fatal("Error while fetching repos") + } + + for _, repo := range repos { + if !regexp.MustCompile(cfg.NameRegex).MatchString(*repo.FullName) { + continue + } + + skip := false + + for _, f := range cfg.Filters { + if f == "" { + continue + } + + var ( + inverse = false + filter = f + ) + + if strings.HasPrefix(filter, "no-") { + inverse = true + filter = filter[3:] + } + + if filters[filter](repo) == inverse { + log.WithFields(log.Fields{ + "filter": filter, + "repo": *repo.FullName, + }).Debug("Repo was filtered") + skip = true + } + } + + if skip { + continue + } + + log.WithFields(log.Fields{ + "repo": *repo.FullName, + "private": *repo.Private, + "fork": *repo.Fork, + }).Print("Found repo") + + if err := render(repo); err != nil { + log.WithError(err).Error("Unable to render output") + continue + } + } +} + +func fetchRepos() ([]*github.Repository, error) { + var ( + ctx = context.Background() + repos = []*github.Repository{} + ) + + opt := &github.RepositoryListOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + } + + for { + rs, res, err := client.Repositories.List(ctx, "", opt) + + if err != nil { + return nil, err + } + + repos = append(repos, rs...) + + if res.NextPage == 0 { + break + } + opt.Page = res.NextPage + } + + return repos, nil +} + +func render(repo *github.Repository) error { + var outFile io.Writer + if cfg.Output == "-" { + outFile = os.Stdout + } else { + outName, err := getOutfile(repo) + if err != nil { + return err + } + + log.WithFields(log.Fields{ + "repo": *repo.FullName, + "outName": outName, + }).Debug("") + + f, err := os.Create(outName) + if err != nil { + return err + } + defer f.Close() + outFile = f + } + + tplRaw, err := ioutil.ReadFile(cfg.Template) + if err != nil { + return err + } + + tpl, err := template.New("output").Funcs(tplFuncs()).Parse(string(tplRaw)) + if err != nil { + return err + } + + return tpl.Execute(outFile, repo) +} + +func getOutfile(repo *github.Repository) (string, error) { + tpl, err := template.New("outfile").Funcs(tplFuncs()).Parse(cfg.Output) + if err != nil { + return "", err + } + buf := new(bytes.Buffer) + + err = tpl.Execute(buf, repo) + return buf.String(), err +} + +func tplFuncs() template.FuncMap { + return template.FuncMap{ + "chkPtrBool": func(i *bool) bool { return i != nil && *i }, + "slugify": slug.Make, + } +}