Skip to content

Commit bafee0f

Browse files
authored
chore(ci): add repo:complaince check to release PRs (#8877)
1 parent 4ee4389 commit bafee0f

3 files changed

Lines changed: 75 additions & 63 deletions

File tree

.github/workflows/release-checks.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,23 @@ jobs:
133133
if: github.event.pull_request.user.login == 'release-please[bot]'
134134
with:
135135
next-release-label-check: true
136+
137+
# Ensure all repos are in compliance
138+
repo-compliance-check:
139+
name: Repo Compliance Check
140+
runs-on: ubuntu-latest
141+
if: github.event.pull_request.user.login == 'release-please[bot]'
142+
steps:
143+
- uses: actions/checkout@v6
144+
with:
145+
fetch-depth: 0
146+
- name: "Install PHP"
147+
uses: shivammathur/setup-php@v2
148+
with:
149+
php-version: "8.1"
150+
- name: "Install dependencies"
151+
run: composer install -d dev
152+
- name: "Check repo compliance"
153+
env:
154+
GH_TOKEN: ${{ secrets.SPLIT_TOKEN }}
155+
run: ./dev/google-cloud repo:compliance --format=ci -t $GH_TOKEN

dev/src/Command/ComponentExecuteCommand.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ protected function configure()
4141
{
4242
$this->setName('component:execute')
4343
->setDescription('Execute a command for each component')
44+
->setHelp(<<<EOF
45+
Execute PHP code directly (don't forget to escape `$` for bash):
46+
47+
./dev/google-cloud component:execute "copy('SECURITY.md', \\\$component->getPath() . '/SECURITY.md');"
48+
49+
Execute a PHP file:
50+
51+
./dev/google-cloud component:execute copy_file.php
52+
53+
EOF)
4454
->addArgument('code', InputArgument::REQUIRED, 'Path to a file or PHP code to execute')
4555
;
4656
}

dev/src/Command/RepoComplianceCommand.php

Lines changed: 45 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\Component\Console\Output\OutputInterface;
3030
use Symfony\Component\Console\Question\ConfirmationQuestion;
3131
use GuzzleHttp\Client;
32+
use InvalidArgumentException;
3233

3334
/**
3435
* List repo details
@@ -46,8 +47,7 @@ protected function configure()
4647
->setDescription('ensure all github repositories meet compliance')
4748
->addOption('component', 'c', InputOption::VALUE_REQUIRED, 'If specified, display repo info for this component only', '')
4849
->addOption('token', 't', InputOption::VALUE_REQUIRED, 'Github token to use for authentication', '')
49-
->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'page to start from', '1')
50-
->addOption('results-per-page', 'r', InputOption::VALUE_REQUIRED, 'results to display per page (0 for all)', '10')
50+
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'can be "ci" or "table"', 'table')
5151
->addOption('new-packagist-token', '', InputOption::VALUE_REQUIRED, 'update the packagist token')
5252
;
5353
}
@@ -59,54 +59,44 @@ protected function execute(InputInterface $input, OutputInterface $output)
5959
$this->github = new GitHub(new RunShell(), $http, $input->getOption('token'), $output);
6060
$this->packagist = new Packagist($http, self::PACKAGIST_USERNAME, $input->getOption('new-packagist-token') ?? '');
6161

62-
$nextPageQuestion = new ConfirmationQuestion('Next Page (enter), "n" to quit: ', true);
63-
$table = (new Table($output))->setHeaders([
64-
'name' => 'Name',
65-
'repo_config' => 'Repo Config',
66-
'packagist_config' => 'Packagist Config',
67-
'teams' => 'Teams',
68-
'compliant' => 'Compliant?'
69-
]);
70-
if ($componentName = $input->getOption('component')) {
71-
$table->setVertical();
62+
$format = $input->getOption('format');
63+
if (!in_array($format, ['ci', 'table'])) {
64+
throw new InvalidArgumentException('Invalid format "' . $format . '", must be "table" or "ci"');
7265
}
73-
$page = (int) $input->getOption('page');
74-
$resultsPerPage = (int) $input->getOption('results-per-page');
66+
67+
$table = (new Table($output));
68+
$table->setColumnWidths([55, 20, 20, 25, 50]);
69+
$table->setStyle('compact');
70+
$headers = $format == 'ci' ? ['Name', 'Compliance'] : [
71+
'Name',
72+
'Repo Config',
73+
'Packagist Config',
74+
'Teams',
75+
'Compliance'
76+
];
77+
(clone $table)->setHeaders($headers)->render();
78+
79+
$componentName = $input->getOption('component');
7580
$components = $componentName ? [new Component($componentName)] : Component::getComponents();
7681

77-
if (!$input->getOption('token')) {
78-
$output->writeln('<error>No token provided - please provide token to update compliance</error>');
79-
}
82+
$isCompliant = true;
8083
foreach ($components as $i => $component) {
81-
if ($i < (($page-1) * $resultsPerPage)) {
82-
continue;
83-
}
84-
if (0 !== $resultsPerPage && $i >= ($page * $resultsPerPage)) {
85-
$table->render();
86-
if (!$this->getHelper('question')->ask($input, $output, $nextPageQuestion)) {
87-
return 0;
88-
}
89-
$table->setRows([]);
90-
$page++;
91-
}
9284
$details = $this->getRepoDetails($component);
93-
$compliance = [
94-
'settings' => true,
95-
'packagist' => true,
96-
'teams' => true,
97-
];
85+
$settingsCheck = true;
86+
$packagistCheck = true;
87+
$teamCheck = true;
9888

9989
$refreshDetails = false;
10090
if (!$this->checkSettingsCompliance($details)) {
101-
$compliance['settings'] = false;
91+
$settingsCheck = false;
10292
$refreshDetails |= $this->askFixSettingsCompliance($input, $output, $details);
10393
}
10494
if (!$this->checkPackagistCompliance($details)) {
105-
$compliance['packagist'] = false;
95+
$packagistCheck = false;
10696
$refreshDetails |= $this->askFixPackagistCompliance($input, $output, $component->getRepoName());
10797
}
10898
if (!$this->checkTeamCompliance($details)) {
109-
$compliance['teams'] = $this->github->token ? false : null;
99+
$teamCheck = $this->github->token ? false : null;
110100
$refreshDetails |= $this->askFixTeamCompliance($input, $output, $component->getRepoName());
111101
}
112102
if ($refreshDetails) {
@@ -123,31 +113,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
123113
$output->writeln(sprintf('<comment>%s</comment>: Packagist webhook token updated.', $repoName));
124114
}
125115
}
126-
$isCompliant = true;
127-
$details['compliant'] = '<info>REPO IS COMPLIANT</info>';
128-
foreach ($compliance as $key => $val) {
129-
if ($val === false) {
130-
$isCompliant = false;
131-
$details['compliant'] = '<error>NOT COMPLIANT</error>';
132-
} elseif ($isCompliant && $val === null) {
133-
$details['compliant'] = '<error>???</error> (token required)';
134-
$isCompliant = null;
135-
}
136-
}
137116

138-
if (!$isCompliant) {
139-
$details['compliant'] .= PHP_EOL . implode("\n", array_map(
140-
fn ($k, $v) => $k . ': ' . (is_null($v) ? '???' : var_export($v, true)),
141-
array_keys($compliance),
142-
array_values($compliance)
143-
));
117+
$emoji = fn (?bool $check) => match ($check) { null => '', true => '', false => ''};
118+
$details['compliant'] = implode("\n", [
119+
sprintf('%s Issues, Projects, Wiki, Pages, and Discussion are disabled', $emoji($settingsCheck)),
120+
sprintf('%s Packagist maintainer is "google-cloud"', $emoji($packagistCheck)),
121+
sprintf('%s Github teams permissions are configured correctly', $emoji($teamCheck)),
122+
'',
123+
]);
124+
125+
$isCompliant = $isCompliant && $settingsCheck && $packagistCheck && $teamCheck;
126+
if ($format == 'ci') {
127+
unset($details['repo_config'], $details['packagist_config'], $details['teams']);
144128
}
145-
146-
$table->addRow($details);
129+
(clone $table)->addRow($details)->render();
147130
}
148-
$table->render();
149131

150-
return 0;
132+
return $isCompliant ? Command::SUCCESS : Command::FAILURE;
151133
}
152134

153135
private function checkSettingsCompliance(array $details)
@@ -169,8 +151,8 @@ private function checkTeamCompliance(array $details)
169151

170152
private function askFixSettingsCompliance(InputInterface $input, OutputInterface $output, array $details)
171153
{
172-
if (!$this->github->token) {
173-
// without a token, don't ask to fix compliance
154+
if (!$this->github->token || $input->getOption('format') == 'ci') {
155+
// without a token, or in CI mode, don't ask to fix compliance
174156
return false;
175157
}
176158
$explodedConfig = array_map(fn ($line) => explode(': ', $line), explode("\n", $details['repo_config']));
@@ -204,17 +186,17 @@ private function checkPackagistCompliance(array $details)
204186

205187
private function askFixPackagistCompliance(InputInterface $input, OutputInterface $output, array $details)
206188
{
207-
if (!$this->github->token) {
208-
// without a token, don't ask to fix compliance
189+
if (!$this->github->token || $input->getOption('format') == 'ci') {
190+
// without a token, or in CI mode, don't ask to fix compliance
209191
return false;
210192
}
211193
throw new \Exception('not implemented');
212194
}
213195

214196
private function askFixTeamCompliance(InputInterface $input, OutputInterface $output, string $repoName)
215197
{
216-
if (!$this->github->token) {
217-
// without a token, don't ask to fix compliance
198+
if (!$this->github->token || $input->getOption('format') == 'ci') {
199+
// without a token, or in CI mode, don't ask to fix compliance
218200
return false;
219201
}
220202
$question = new ConfirmationQuestion(sprintf(

0 commit comments

Comments
 (0)