diff --git a/cmd/debugTemplate.go b/cmd/debugTemplate.go index 906b51b..0618815 100644 --- a/cmd/debugTemplate.go +++ b/cmd/debugTemplate.go @@ -1,10 +1,12 @@ package cmd import ( - "fmt" "log" + "os" + "path" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/chia-network/repo-content-updater/internal/repo" ) @@ -14,7 +16,7 @@ var debugTemplateCmd = &cobra.Command{ Use: "debug-template", Short: "Renders the given template for debugging", Run: func(cmd *cobra.Command, args []string) { - tmplContent, err := fs.ReadFile(fmt.Sprintf("templates/%s", args[0])) + tmplContent, err := os.ReadFile(path.Join(viper.GetString("templates"), args[0])) if err != nil { log.Fatalln(err.Error()) } diff --git a/cmd/license.go b/cmd/license.go index 975d025..1fd0bc1 100644 --- a/cmd/license.go +++ b/cmd/license.go @@ -14,7 +14,7 @@ var licenseCmd = &cobra.Command{ Use: "license", Short: "Updates licenses in repos with license flag", Run: func(cmd *cobra.Command, args []string) { - content, err := repo.NewContent(&fs, viper.GetString("github-token")) + content, err := repo.NewContent(viper.GetString("templates"), viper.GetString("github-token")) if err != nil { log.Fatalf("Error creating content manager: %s", err.Error()) } diff --git a/cmd/managedFiles.go b/cmd/managedFiles.go index 7438ebb..408b386 100644 --- a/cmd/managedFiles.go +++ b/cmd/managedFiles.go @@ -15,12 +15,12 @@ var managedFilesCmd = &cobra.Command{ Use: "managed-files", Short: "Updates all managed files across the org", Run: func(cmd *cobra.Command, args []string) { - content, err := repo.NewContent(&fs, viper.GetString("github-token")) + content, err := repo.NewContent(viper.GetString("templates"), viper.GetString("github-token")) if err != nil { log.Fatalf("Error creating content manager: %s", err.Error()) } - cfg, err := config.LoadConfig("config.yaml") + cfg, err := config.LoadConfig(viper.GetString("config")) if err != nil { log.Fatalf("error loading config: %s\n", err.Error()) } diff --git a/cmd/root.go b/cmd/root.go index 4d04cc9..839ccad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,6 @@ package cmd import ( - "embed" "fmt" "os" "strings" @@ -10,9 +9,6 @@ import ( "github.com/spf13/viper" ) -var fs embed.FS -var cfgFile string - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "repo-content-updater", @@ -21,8 +17,7 @@ var rootCmd = &cobra.Command{ // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute(_fs embed.FS) { - fs = _fs +func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) @@ -31,18 +26,22 @@ func Execute(_fs embed.FS) { func init() { var ( + cfgFile string + templateDir string githubToken string signCommits bool push bool ) cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.repo-content-updater.yaml)") - + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "config.yaml", "template config file (default is config.yaml)") + rootCmd.PersistentFlags().StringVar(&templateDir, "templates", "templates", "Path to templates defined in the config. Defaults to ./templates") rootCmd.PersistentFlags().StringVar(&githubToken, "github-token", "", "The token to use to auth to GitHub API and Push to Repos") rootCmd.PersistentFlags().BoolVar(&signCommits, "sign-commits", true, "Whether or not to sign commits") rootCmd.PersistentFlags().BoolVar(&push, "push", true, "Whether or not to push and create the pull request") + cobra.CheckErr(viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))) + cobra.CheckErr(viper.BindPFlag("templates", rootCmd.PersistentFlags().Lookup("templates"))) cobra.CheckErr(viper.BindPFlag("github-token", rootCmd.PersistentFlags().Lookup("github-token"))) cobra.CheckErr(viper.BindPFlag("sign-commits", rootCmd.PersistentFlags().Lookup("sign-commits"))) cobra.CheckErr(viper.BindPFlag("push", rootCmd.PersistentFlags().Lookup("push"))) @@ -50,20 +49,14 @@ func init() { // initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".repo-content-updater" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".repo-content-updater") - } + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + // Search config in home directory with name ".repo-content-updater" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".repo-content-updater") viper.SetEnvPrefix("REPO_CONTENT_UPDATER") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() // read in environment variables that match diff --git a/internal/repo/content.go b/internal/repo/content.go index 52e294a..1d026b0 100644 --- a/internal/repo/content.go +++ b/internal/repo/content.go @@ -2,7 +2,6 @@ package repo import ( "context" - "embed" "errors" "fmt" "log" @@ -31,16 +30,16 @@ const ( // Content the content manager object type Content struct { - fs *embed.FS + templates string githubToken string githubClient *github.Client } // NewContent returns new repo content manager -func NewContent(fs *embed.FS, githubToken string) (*Content, error) { +func NewContent(templates, githubToken string) (*Content, error) { client := github.NewClient(nil).WithAuthToken(githubToken) return &Content{ - fs: fs, + templates: templates, githubToken: githubToken, githubClient: client, }, nil diff --git a/internal/repo/files.go b/internal/repo/files.go index 63f25f4..4968bfb 100644 --- a/internal/repo/files.go +++ b/internal/repo/files.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "path" "path/filepath" "strings" @@ -117,7 +118,7 @@ func (c *Content) CheckFiles(repoName string, files []string, cfg *config.Config _ = os.Remove(fmt.Sprintf("%s/%s", repoDir(repoName), form)) } - tmplContent, err := c.fs.ReadFile(fmt.Sprintf("templates/%s", fileinfo.TemplateName)) + tmplContent, err := os.ReadFile(path.Join(c.templates, fileinfo.TemplateName)) if err != nil { return err } diff --git a/internal/repo/license.go b/internal/repo/license.go index 1d8b5ed..511e3fa 100644 --- a/internal/repo/license.go +++ b/internal/repo/license.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "path" "github.com/google/go-github/v59/github" ) @@ -71,7 +72,7 @@ func (c *Content) UpdateLicense(repoName string) error { } } - file, err := c.fs.ReadFile("templates/LICENSE") + file, err := os.ReadFile(path.Join(c.templates, "LICENSE")) if err != nil { return err } diff --git a/internal/repo/template.go b/internal/repo/template.go index dc0034a..0ea7da5 100644 --- a/internal/repo/template.go +++ b/internal/repo/template.go @@ -18,21 +18,21 @@ func ProcessTemplate(templateContent []byte, overrides map[string]string) ([]byt notOverridable := map[string]bool{"CURRENT_YEAR": true} defaultPullRequestLimit := "10" data := map[string]string{ - "CURRENT_YEAR": strconv.Itoa(time.Now().Year()), - "COMPANY_NAME": "Chia Network Inc.", - "CGO_ENABLED": "0", - "DEPENDABOT_GOMOD_PULL_REQUEST_LIMIT": defaultPullRequestLimit, - "DEPENDABOT_GOMOD_DIRECTORY": "/", - "DEPENDABOT_GOMOD_REVIEWERS": "[\"cmmarslender\", \"starttoaster\"]", - "DEPENDABOT_PIP_PULL_REQUEST_LIMIT": defaultPullRequestLimit, - "DEPENDABOT_PIP_DIRECTORY": "/", - "DEPENDABOT_PIP_REVIEWERS": "[\"emlowe\", \"altendky\"]", + "CURRENT_YEAR": strconv.Itoa(time.Now().Year()), + "COMPANY_NAME": "Chia Network Inc.", + "CGO_ENABLED": "0", + "DEPENDABOT_GOMOD_PULL_REQUEST_LIMIT": defaultPullRequestLimit, + "DEPENDABOT_GOMOD_DIRECTORY": "/", + "DEPENDABOT_GOMOD_REVIEWERS": "[\"cmmarslender\", \"starttoaster\"]", + "DEPENDABOT_PIP_PULL_REQUEST_LIMIT": defaultPullRequestLimit, + "DEPENDABOT_PIP_DIRECTORY": "/", + "DEPENDABOT_PIP_REVIEWERS": "[\"emlowe\", \"altendky\"]", "DEPENDABOT_ACTIONS_PULL_REQUEST_LIMIT": defaultPullRequestLimit, - "DEPENDABOT_ACTIONS_DIRECTORY": "/", - "DEPENDABOT_ACTIONS_REVIEWERS": "[\"cmmarslender\", \"altendky\"]", - "DEPENDABOT_NPM_PULL_REQUEST_LIMIT": defaultPullRequestLimit, - "DEPENDABOT_NPM_DIRECTORY": "/", - "DEPENDABOT_NPM_REVIEWERS": "[\"cmmarslender\", \"emlowe\"]", + "DEPENDABOT_ACTIONS_DIRECTORY": "/", + "DEPENDABOT_ACTIONS_REVIEWERS": "[\"cmmarslender\", \"altendky\"]", + "DEPENDABOT_NPM_PULL_REQUEST_LIMIT": defaultPullRequestLimit, + "DEPENDABOT_NPM_DIRECTORY": "/", + "DEPENDABOT_NPM_REVIEWERS": "[\"cmmarslender\", \"emlowe\"]", } // Merge `overrides` into `data`, with `overrides` taking precedence diff --git a/main.go b/main.go index 99eab0c..f3dd67e 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,9 @@ package main import ( - "embed" - "github.com/chia-network/repo-content-updater/cmd" ) -//go:embed templates/* -var fs embed.FS - func main() { - cmd.Execute(fs) + cmd.Execute() } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..56ef40e --- /dev/null +++ b/readme.md @@ -0,0 +1,52 @@ +# Repo Content Updater + +Manages files across repos in a GitHub org based on [custom properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). + +## Manage Licenses + +Applies a `LICENSE` template to all repos with the custom property `manage-license` set to `yes`. This is split out from the generic managed files so that the property can be set to required org wide and specific "yes" and "no" options (only) provided in a drop down, ensuring a repo either opts in or out of the license specifically. + +## Manage Files + +Looks for the `managed-files` custom property on a repo, and parses out a comma separated list of files/groups to include. Files must be referenced by their name in the config file, and groups should be referenced by their group name, prefixed with `group:`. + +For example, the value could be: `group:base,go-test`. This would pull in the base group of files and the go-test file. + +### Config Format + +```yaml +groups: + - name: base + templates: + - dep-review + + - name: go + templates: + - go-makefile + - go-dependabot + +files: + - name: go-makefile + template_name: go-makefile + repo_path: Makefile + + - name: go-dependabot + template_name: go-dependabot.yml + repo_path: .github/dependabot.yml + alternate_paths: + - .github/dependabot.yaml + + - name: dep-review + template_name: dependency-review.yml + repo_path: .github/workflows/dependency-review.yml + alternate_paths: + - .github/workflows/dependency-review.yaml + ``` + +`groups` allows combining multiple items from `files` into a single group, making it easier to reference in the custom property + +`files` is where every supported template must be listed. +* `name` is the name to reference the file by in groups or in the custom property. +* `template_name` is the name of the template to use from the supplied templates directory +* `repo_path` is the path within the repo to place the file +* `alternate_paths` is a list of alternate/equivalent paths this template might have been named before being managed. These files will be renamed and updated to the latest version of the template, if present