Repolinter is a tool designed to automatically find and fix open source policy issues in a repository. At New Relic, we have deployed Repolinter to all of our public repositories to allow greater consistency in policy enforcement.
Repolinter is deployed through an instance of Repolinter Action either registered on the target repository or this repository. Each deployment of Repolinter action is given a link to the policy it should apply, allowing us to centrally manage policies in this repository. At the moment policies are split by category: this means that a repository making a category change requires modification of the Repolinter policy URL (see deploying repolinter), but changes to the policies enforced by each category only require updating the policy file (see managing policies).
Instructions are provided below to deploy and maintain this system. Additionally, the architecture overview provides more technical details on how this system is constructed.
Repolinter Action can be added to a repository by opening a pull request against the target repository with the action workflow file. An alternate method is also available for repositories that find PRs or GitHub actions difficult to work with.
- Fork (or create a branch) on the target repository, and clone it locally so you can modify it.
- Copy
repolinter-action-template.yml
into.github/workflows/repolinter.yml
in the cloned repository. - Edit the copied
repolinter.yml
and replace<config url>
with a link to the desired policy.- You can get this link by navigating to the policy file on GitHub and clicking the
raw
button in the file view (make sure you havemain
branch selected). The link will be formatted ashttps://raw.githubusercontent.com/newrelic/.github/main/{path to policy}
. For example:https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/new-relic-one-catalog-project.json
forrepolinter-rulesets/new-relic-catalog-project.json
.
- You can get this link by navigating to the policy file on GitHub and clicking the
- Save the file, commit it, and push the changes. Commit message I typically use is
ci: add repolinter action workflow
. - Open a pull request with your changes. Use
ci: Add Open Source Policy Workflow
for the title and the text inpr-body.md
for the body text.
A centralized deployment of Repolinter exists in this repository under .github/workflows/repolinter-apply.yml. This deployment serves as an alternative to the distributed workflow for teams that find it difficult to work with. Note that for this deployment to work properly nr-opensource-bot
must have write permissions to the target repository.
- Fork (or create a branch) on this repository, and clone it locally so you can modify it.
- Edit
.github/workflows/repolinter-apply.yml
, adding the target repository and relative path to the policy underjobs.apply-repolinter.strategy.matrix.include
. Save, commit, and push this file. - Contact a team member who can grant write permissions for the target repository to
nr-opensource-bot
. - Create a pull request with your changes to this repository. Make sure the checks pass before merging.
Adding Repolinter to many repositories requires opening a large number of identical pull requests. I recommend Google's github-repo-automation to automate this process. Instructions to run this tool are below:
- Make sure you have a GitHub account (or a token) with write access to every repository that you would like to add Repolinter to. This tool creates a branch on the target repository, and will fail if it cannot write.
- In addition to a personal access token, your local
git
instance will need to be setup with an SSH key to allow cloning over SSH. The SSH key used must have the same permissions as the token.
- In addition to a personal access token, your local
- Use
npm i -g @google/repo
to install the tool. You'll need NodeJS installed if it isn't already. - Find or create an empty workspace folder. I'll use
{work}
as the relative path to that workspace folder and{work abs}
as the absolute path. - Copy
repolinter-action-template.yml
to{work}/repolinter.yml
andpr-body.md
to{work}/pr-body.md
. - Edit the copied
{work}/repolinter.yml
and replace<config url>
with a link to the desired policy.- You can get this link by navigating to the policy file on GitHub and clicking the
raw
button in the file view (make sure you havemain
branch selected). The link will be formatted ashttps://raw.githubusercontent.com/newrelic/.github/main/{path to policy}
. For example:https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/new-relic-one-catalog-project.json
forrepolinter-rulesets/new-relic-catalog-project.json
.
- You can get this link by navigating to the policy file on GitHub and clicking the
- Create a
.repo.yml
file in{work}
, and paste in the following contents:githubToken: <token> clonePath: ./.work repos: - org: <org> name: <name> # more repositories here
- Generate a GitHub personal access token using the account you would like to make the PRs as. Check the
public_repo
scope when creating this token. When complete, replace<token>
in.repo.yml
with the generated value. - Replace the template under
repos
with the list of repositories you would like to add Repolinter to. You may want to use a scripting language to generate the YAML formatting for this list, as it can get rather long. An example complete configuration is shown below:githubToken: df51c00f76bcd3a0e586d02d40bcd314a2456213 clonePath: ./.work repos: - org: newrelic name: repolinter-action - org: newrelic name: .github # ...
- Replace
{work abs}
with the absolute path to your working directory in the following command, then run it to start automatically creating PRs:REPO_CONFIG_PATH=./.repo.yml repo apply --silent --branch feature/repolinter-action --message "ci: Add Open Source Policy Workflow" --comment "$(< ./pr-body.md)" "mkdir -p .github/workflows && cp /{work abs}/repolinter.yml .github/workflows/repolinter.yml"
- Watch the output for errors. If all goes well, you should see text like this for every repository:
Some common problems:
Loaded 1 repositories from GitHub. Total 1 unique repositories loaded. companion-cube Executing command: mkdir -p .github/workflows && cp /Users/nkoontz/Documents/code/github-repo-automation-rollout/repolinter.yml .github/workflows/repolinter.yml success! https://github.com/aperture-science-incorporated/companion-cube/pull/54
git@github.com: Permission denied (publickey).
- Git is not able to authenticate to GitHub over SSH. Is your git instance setup with an SSH key?cannot create branch feature/repolinter-action, skipping this repository: Error: Request failed with status code 404
- The Personal Access Token does not have permissions to write to this repository. Check that thepublic_repo
scope and granted and the user has write permissions to the repository.cannot create branch feature/repolinter-action, skipping this repository: Error: Request failed with status code 422
- A branch namedfeature/repolinter-action
already exists on this repository. Is there already a PR for Repolinter Action open?GaxiosError: Request failed with status code 401
- Check the Personal Access Token. Is the token pasted correctly? Does it have the permissions needed?Config file not found
- Check that you are running the command in{work}
, and that the.repo.yml
file is formatted correctly (use www.yamllint.com to check syntax).Action returned empty array
- The repolinter workflow file is already present on this repository, did you already apply Repolinter?Callback function threw an exception
- Check that the absolute path in the command is correct, and that{work}/repolinter.yml
is readable by the tool.
Note: If you are changing the policy for repository, make sure any also change the category for that repository in the Opensource Website.
The process to change the Repolinter policy will depend on how Repolinter is deployed on that repository. If there is a .github/workflows/repolinter.yml
workflow file in the repository, use the steps below. If the repository is listed in this repository's .github/workflows/repolinter-apply.yml
, use the steps listed under change policy for a central deployment. If neither of these conditions are met, Repolinter has not yet been deployed on the repository--follow the steps under add Repolinter to a single repository to setup Repolinter for the first time.
- Fork (or create a branch) on the target repository, and clone it locally so you can modify it.
- Use GitHub to find the new policy file you would like to use, and get a link to the raw file.
- You can get this link by navigating to the policy file on GitHub and clicking the
raw
button in the file view (make sure you havemain
branch selected). The link will be formatted ashttps://raw.githubusercontent.com/newrelic/.github/main/{path to policy}
. For example:https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/new-relic-one-catalog-project.json
forrepolinter-rulesets/new-relic-catalog-project.json
.
- You can get this link by navigating to the policy file on GitHub and clicking the
- Edit the target repositories
.github/workflows/repolinter.yml
, replacing thewith.config_url
in theRun Repolinter
step with the URL to the policy you found earlier. - Commit, push, and create a pull request with your changes.
- Fork (or create a branch) on this repository, and clone it locally so you can modify it.
- Find the target repository in
.github/workflows/repolinter-apply.yml
. Replace theconfig
key for the target repository with the relative path to the desired policy (ex.repolinter-rulesets/community-plus.yml
). - Commit, push, and create a pull request with your changes.
Policies for this system are managed centrally in this repository under repolinter-rulesets
. More specifically, Repolinter action pulls a new copy of the policy from this repository every time it is invoked. Policies are split into separate files by OSS category--as changing which file Repolinter action pulls requires modifying the target repository, this means that a category change will typically require help from the OSPO.
All policies are structured according to the Repolinter documentation. YAML is preferred for readability. Some tips when creating or modifying rulesets:
- Start with an existing policy, and modify it as needed. You can use either one in this repository or the one built into Repolinter.
- If you go this route, make sure you edit out all of the references to the previous policy. I will typically use my IDE to find all the instances of the previous category name so I don't miss anything.
- Use a YAML linter to catch common syntax issues. IDE's will typically have a built-in syntax checker, but I also use www.yamllint.com or codebeautify.org/yaml-validator to validate my YAML.
- Attempt to be as generic as possible when writing rules. For instance, instead of checking for a specific
README.md
subsection called### security
, simply search anyREADME*
file for a substring that the security section should contain. New Relic has a very wide variety of repository structures and contents, and checking for the spirit of the rule will result in the minimum number of rewrites.- On that note, ensure that
README*
rules support both Markdown and RST. Example of an RST README.
- On that note, ensure that
- As of writing repolinter action does not support using the language or license axioms.
- Fill the
policyInfo
field with as much information as you can. It is not only helpful to the person receiving the error but also serves as a reminder for the reason the rule was put in place. - Remember to double check the links!
- Fork (or create a branch) on the target repository, and clone it locally so you can modify it.
- Create a YAML file in
repolinter-rulesets
with the desired policy name. I typically use the OSS category names as specified in the opensource website. - Edit the new policy with your desired checks. Check out the Repolinter documentation and policy editing tips to get started.
- Add your policy file to the
.github/workflows/test.yml
workflow to allow GitHub actions to test it. - Commit, push, and create a pull request with your new policy file. Wait for the pull request checks to pass before merging.
- Fork (or create a branch) on the target repository, and clone it locally so you can modify it.
- Make the desired changes to a policy file. Check out the Repolinter documentation and policy editing tips for tips on editing.
- Commit, push, and create a pull request with your changes. Wait until the pull request checks pass before merging.
- Policy changes will rollout immediately, but Repolinter Action will not pull the changes until the next push to the default branch of the target repository.
- To force Repolinter Action to pull immediately: navigate to the target repository's GitHub Actions, find the latest
Repolinter Action
run on the default branch, and clickRe-run jobs
. Note that you will need write access to the target repository to perform this action.
- To force Repolinter Action to pull immediately: navigate to the target repository's GitHub Actions, find the latest
Repolinter is a maintained by the TODOGroup and was created to "Lint open source repositories for common issues.". When Repolinter was first evaluated for this project, it was missing key functionality that prevented it from being deployment ready: primarily the ability to automatically fix problems it found, but additionally readability issues with the output and configuration. As a result, a large chunk of the work of deploying Repolinter went towards modifications to Repolinter itself. These modifications were made on a forked version of Repolinter, and then later PRed back into the TODOGroup repository after the first round of Repolinter action deployments went well. Because of this, Repolinter Action is based on the newrelic-forks version of Repolinter and not on the TODOGroup repository. This decision stems from the need to deploy changes to Repolinter quickly if a problem occurs. Since we maintain our own fork, we can proceed with relative autonomy—as good open source citizens, however, I encourage you to always attempt to bring changes from our fork into the TODOGroup version and vice versa.
At NR Repolinter is deployed via Repolinter Action. Repolinter action is a GitHub Action that runs Repolinter with a target policy against a target repository—in other words, Repolinter action packages Repolinter into a module that can be directly invoked in a GitHub Actions CI pipeline.
There are two methods we created for deploying repolinter action: decentralized and centralized. The decentralized method is preferred, however the centralized method is a suitable alternative for repositories that prefer to not deal with GitHub Actions.
In the decentralized method, each repository has a GitHub actions workflow file that runs Repolinter action on push to the default branch. By default, the workflow file is configured to open an issue if the policy does not pass, but will not "break the build" in any other manner. This workflow file also specifies a URL to a centralized policy file stored in the newrelic/.github repository, which is pulled fresh every time Repolinter action is run. There are some consequences of this architecture that are worth pointing out:
- Deployment to a large number of repositories is a highly involved task, but does not require write permissions to any of the repositories (assuming a fork PR deployment). In practice this advantage cannot be realized due to limitations of the tooling.
- Results aggregation (how many repolinter issues) must be done using GitHub's search API or otherwise.
- The developers of the target repository have ownership over the Repolinter workflow file. This was an intentional choice, and the workflow file is meant to be helpful rather than required.
- As the policy is centralized but the policy url is not, changing the policy contents requires changing the .github repository but changing which policy is being applied to a repository requires changing the workflow file in the repository itself.
In the centralized method, the newrelic/.github repository has a single repolinter workflow file which uses Repolinter action to apply Repolinter to a list of other external repositories. A policy is specified with each repository in the workflow file. This method was created for repositories that prefer not to have GitHub actions workflows. Some consequences of this method:
- The
nr-opensource-bot
machine user must have write permissions to every target repository, creating a security issue if the list is large. - Both policy content and type changes are performed in the .github repository.
Below is a list of ideas for improvement of this system, in no particular order:
- TODOGroup has indicated to me [@prototypicalpro] that they would eventually like to absorb Repolinter Action into their organization, which would (amoung other things) remove the need for the New Relic Repolinter fork. A key stumbling block with this transition, however, is that every workflow file currently deployed targets
newrelic/repolinter-action@v1
--this means that if we plan to transfer ownership to TODOGroup, we will either need to update every workflow file to targettodogroup/repolinter-action@v1
or ensure thatnewrelic/repolinter-action@v1
still references a valid action. - Repolinter has a concept called axioms which allow a rule in a policy to only run if certain conditions (such as a language or license) are satisfied. This feature could allow for more specific policies to be made in the future, or for merging all of the current policy into one policy with many conditionals.
- Repolinter also has the ability to perform automatic fixes based on policy outcomes. It was originally suggested that Repolinter Action open a PR with these fixes—this feature was backlogged in favor of simply opening an issue to prevent the possibility of erroneous PRs causing confusion.
- The tool used for the deployment of repolinter action to many repositories (@google/repo) requires write access to all the repositories it is deploying too. This problem could be fixed by adding fork-PR support to the tool.