Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Update rulesets #53

Merged
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,22 @@ MS_GITHUB_TOKEN=abc123 php run.php labels --dry-run --only=silverstripe-config,s
| --exclude=[modules] | Exclude the specified modules (without account prefix) separated by commas e.g. `silverstripe-mfa,silverstripe-totp` |
| --dry-run | Do not update labels in GitHub, output to terminal only |
| --no-delete | Do not delete `_data` directory before running |

## Usage - Standardising GitHub repository branch and tag rulesets

```bash
MS_GITHUB_TOKEN=<token> php run.php rulesets <options>
```

**Example usage:**
```bash
MS_GITHUB_TOKEN=abc123 php run.php rulesets --dry-run --only=silverstripe-config,silverstripe-assets
```

### Command line options:

| Flag | Description |
| ---- | ------------|
| --only=[modules] | Only include the specified modules (without account prefix) separated by commas e.g. `silverstripe-config,silverstripe-assets` |
| --exclude=[modules] | Exclude the specified modules (without account prefix) separated by commas e.g. `silverstripe-mfa,silverstripe-totp` |
| --dry-run | Do not update rulesets in GitHub, output to terminal only |
41 changes: 40 additions & 1 deletion funcs_utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ function github_api($url, $data = [], $httpMethod = '')
{
// silverstripe-themes has a kind of weird redirect only for api requests
$url = str_replace('/silverstripe-themes/silverstripe-simple', '/silverstripe/silverstripe-simple', $url);
info("Making curl request to $url");
$method = $httpMethod ? strtoupper($httpMethod) : 'GET';
info("Making $method curl request to $url");
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
$token = github_token();
$jsonStr = empty($data) ? '' : json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ch = curl_init($url);
Expand Down Expand Up @@ -244,6 +245,44 @@ function output_repos_with_labels_updated()
$io->writeln('');
}

/**
* Outputs a list of repos that that had rulesets updated
* If there was an error with a run (probably a secondary rate limit), this can be
* copy pasted into the --exclude option for the next run to continue from where you left off
*/
function output_repos_with_rulesets_created_or_updated()
{
if (running_unit_tests()) {
return;
}
global $REPOS_WITH_RULESETS_UPDATED;
$io = io();
$io->writeln('');
$io->writeln('Repos with rulesets created/updated (add to --exclude if you need to re-run):');
$io->writeln(implode(',', $REPOS_WITH_RULESETS_UPDATED));
$io->writeln('');
}

function create_ruleset($type, $additionalBranchConditions = [])
{
$ruleset = file_get_contents("rulesets/$type-ruleset.json");
if (!$ruleset) {
error("Could not read ruleset for $type");
}
$json = json_decode($ruleset, true);
if ($type == 'branch') {
$json['name'] = BRANCH_RULESET_NAME;
} elseif ($type === 'tag') {
$json['name'] = TAG_RULESET_NAME;
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
} else {
error("Invalid ruleset type: $type");
}
foreach ($additionalBranchConditions as $value) {
$json['conditions']['ref_name']['include'][] = $value;
}
return $json;
}

/**
* Works out which branch in a module to checkout before running scripts on it
*
Expand Down
44 changes: 44 additions & 0 deletions rulesets/branch-ruleset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "<RULESET_NAME>",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"exclude": [],
"include": [
"refs/heads/[0-9]*"
]
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "creation"
},
{
"type": "update"
},
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 2,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": true,
"required_review_thread_resolution": false
}
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
34 changes: 34 additions & 0 deletions rulesets/tag-ruleset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "<RULESET_NAME>",
"target": "tag",
"enforcement": "active",
"conditions": {
"ref_name": {
"exclude": [],
"include": [
"~ALL"
]
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "creation"
},
{
"type": "update"
}
],
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "always"
}
]
}
120 changes: 120 additions & 0 deletions rulesets_command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

use SilverStripe\SupportedModules\MetaData;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

$rulesetsCommand = function(InputInterface $input, OutputInterface $output): int {
// This is the code that is executed when running the 'rulesets' command

// Variables
global $OUT, $REPOS_WITH_RULESETS_UPDATED;
$OUT = $output;

// Validate system is ready
validate_system();

// Modules
$modules = [];
$modulesCurrentMajor = filtered_modules(MetaData::HIGHEST_STABLE_CMS_MAJOR, $input);
$modulesPreviousMajor = filtered_modules(MetaData::HIGHEST_STABLE_CMS_MAJOR - 1, $input);
foreach ([$modulesCurrentMajor, $modulesPreviousMajor] as $modulesList) {
foreach ($modulesList as $module) {
// Important! Only include modules on the "silverstripe" account
if ($module['account'] !== 'silverstripe') {
continue;
}
$modules[$module['ghrepo']] = $module;
}
}

// Update rulesets
foreach ($modules as $module) {
$account = $module['account'];
$repo = $module['repo'];

// Fetch existing rulesets
// https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#get-all-repository-rulesets
$rulesets = github_api("https://api.github.com/repos/$account/$repo/rulesets");
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
$branchRulesetID = null;
$tagRulesetID = null;
foreach ($rulesets as $ruleset) {
$id = $ruleset['id'];
$name = $ruleset['name'];
if ($name === BRANCH_RULESET_NAME) {
$branchRulesetID = $id;
}
if ($name === TAG_RULESET_NAME) {
$tagRulesetID = $id;
}
}

// Get any additional branches to add
// Assumption is that if the default branch is main/master, then the repo uses
// a non-numeric style branching system (e.g. main, master) and that needs to be protected
// [0-9]* branch protection will still be applied, on the chance that the repo is converted
// to uses a numeric style branch system in the future and we would want branch protection
// to start immediately on the new branches
$additionalBranchConditions = [];
// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository
$defaultBranch = github_api("https://api.github.com/repos/$account/$repo")['default_branch'];
if (in_array($defaultBranch, ['main', 'master'])) {
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
$additionalBranchConditions[] = "refs/heads/$defaultBranch";
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
}

// Create rulesets
// Note: This will read from the "rulesets" directory
// In each of those json rulesets there is "bypass_actors"."actor_id" = 5
// This translates to the "Repository admin" role
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
// It has been confirmed that the github-action user is able to bypass the ruleset as
// it has the "Organisation admin" role which is one level above the "Repository admin" role
$branchRuleset = create_ruleset('branch', $additionalBranchConditions);
$tagRuleset = create_ruleset('tag');

$apiCalls = [];

// Create new rulesets
if (is_null($branchRulesetID)) {
// https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#create-a-repository-ruleset
$url = "https://api.github.com/repos/$account/$repo/rulesets";
$apiCalls[] = [$url, $branchRuleset, 'POST'];
}
if (is_null($tagRulesetID)) {
// https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#create-a-repository-ruleset
$url = "https://api.github.com/repos/$account/$repo/rulesets";
$apiCalls[] = [$url, $tagRuleset, 'POST'];
}

// Update existing rulesets
// Don't bother to check if the ruleset is already correct
// This is a very quick update so no need to optimise this
if (!is_null($branchRulesetID)) {
// https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#update-a-repository-ruleset
$url = "https://api.github.com/repos/$account/$repo/rulesets/$branchRulesetID";
$apiCalls[] = [$url, $branchRuleset, 'PUT'];
}
if (!is_null($tagRulesetID)) {
// https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28#update-a-repository-ruleset
$url = "https://api.github.com/repos/$account/$repo/rulesets/$tagRulesetID";
$apiCalls[] = [$url, $tagRuleset, 'PUT'];
}

if ($input->getOption('dry-run')) {
info('Not updating rulesets on GitHub because --dry-run option is set');
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
info('There API calls would have been made:');
foreach ($apiCalls as $apiCall) {
info($apiCall[2] . ' ' . $apiCall[0]);
}
} else {
foreach ($apiCalls as $apiCall) {
github_api($apiCall[0], $apiCall[1], $apiCall[2]);
}
}

$REPOS_WITH_RULESETS_UPDATED[] = $repo;
}

output_repos_with_rulesets_created_or_updated();
return Command::SUCCESS;
};
11 changes: 11 additions & 0 deletions run.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
include 'funcs_utils.php';
include 'update_command.php';
include 'labels_command.php';
include 'rulesets_command.php';

use SilverStripe\SupportedModules\MetaData;
use Symfony\Component\Console\Application;
Expand All @@ -19,13 +20,16 @@
const TOOL_URL = 'https://github.com/silverstripe/module-standardiser';
const PR_TITLE = 'MNT Run module-standardiser';
const PR_DESCRIPTION = 'This pull-request was created automatically by [module-standardiser](' . TOOL_URL . ')';
const BRANCH_RULESET_NAME = 'Silverstripe CMS branch ruleset';
const TAG_RULESET_NAME = 'Silverstripe CMS tag ruleset';

// global variables
$MODULE_DIR = '';
$GITHUB_REF = '';
$PRS_CREATED = [];
$REPOS_WITH_PRS_CREATED = [];
$REPOS_WITH_LABELS_UPDATED = [];
$REPOS_WITH_RULESETS_UPDATED = [];
$OUT = null;

// options
Expand Down Expand Up @@ -102,6 +106,13 @@
->addOption(...$optionNoDelete)
->setCode($labelsCommand);

$app->register('rulesets')
->setDescription('Script to set rulesets on all repos only on the silverstripe account')
->addOption(...$optionOnly)
->addOption(...$optionExclude)
->addOption(...$optionDryRun)
->setCode($rulesetsCommand);

try {
$app->run();
} catch (Error|Exception $e) {
Expand Down
Loading