Skip to content
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
20 changes: 20 additions & 0 deletions .github/workflows/release-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,23 @@ jobs:
if: github.event.pull_request.user.login == 'release-please[bot]'
with:
next-release-label-check: true

# Ensure all repos are in compliance
repo-compliance-check:
name: Repo Compliance Check
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'release-please[bot]'
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "8.1"
- name: "Install dependencies"
run: composer install -d dev
- name: "Check repo compliance"
env:
GH_TOKEN: ${{ secrets.SPLIT_TOKEN }}
run: ./dev/google-cloud repo:compliance --format=ci -t $GH_TOKEN
10 changes: 10 additions & 0 deletions dev/src/Command/ComponentExecuteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ protected function configure()
{
$this->setName('component:execute')
->setDescription('Execute a command for each component')
->setHelp(<<<EOF
Execute PHP code directly (don't forget to escape `$` for bash):

./dev/google-cloud component:execute "copy('SECURITY.md', \\\$component->getPath() . '/SECURITY.md');"

Execute a PHP file:

./dev/google-cloud component:execute copy_file.php

EOF)
->addArgument('code', InputArgument::REQUIRED, 'Path to a file or PHP code to execute')
;
}
Expand Down
108 changes: 45 additions & 63 deletions dev/src/Command/RepoComplianceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use GuzzleHttp\Client;
use InvalidArgumentException;

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

$nextPageQuestion = new ConfirmationQuestion('Next Page (enter), "n" to quit: ', true);
$table = (new Table($output))->setHeaders([
'name' => 'Name',
'repo_config' => 'Repo Config',
'packagist_config' => 'Packagist Config',
'teams' => 'Teams',
'compliant' => 'Compliant?'
]);
if ($componentName = $input->getOption('component')) {
$table->setVertical();
$format = $input->getOption('format');
if (!in_array($format, ['ci', 'table'])) {
throw new InvalidArgumentException('Invalid format "' . $format . '", must be "table" or "ci"');
}
$page = (int) $input->getOption('page');
$resultsPerPage = (int) $input->getOption('results-per-page');

$table = (new Table($output));
$table->setColumnWidths([55, 20, 20, 25, 50]);
$table->setStyle('compact');
$headers = $format == 'ci' ? ['Name', 'Compliance'] : [
'Name',
'Repo Config',
'Packagist Config',
'Teams',
'Compliance'
];
(clone $table)->setHeaders($headers)->render();

$componentName = $input->getOption('component');
$components = $componentName ? [new Component($componentName)] : Component::getComponents();

if (!$input->getOption('token')) {
$output->writeln('<error>No token provided - please provide token to update compliance</error>');
}
$isCompliant = true;
foreach ($components as $i => $component) {
if ($i < (($page-1) * $resultsPerPage)) {
continue;
}
if (0 !== $resultsPerPage && $i >= ($page * $resultsPerPage)) {
$table->render();
if (!$this->getHelper('question')->ask($input, $output, $nextPageQuestion)) {
return 0;
}
$table->setRows([]);
$page++;
}
$details = $this->getRepoDetails($component);
$compliance = [
'settings' => true,
'packagist' => true,
'teams' => true,
];
$settingsCheck = true;
$packagistCheck = true;
$teamCheck = true;

$refreshDetails = false;
if (!$this->checkSettingsCompliance($details)) {
$compliance['settings'] = false;
$settingsCheck = false;
$refreshDetails |= $this->askFixSettingsCompliance($input, $output, $details);
}
if (!$this->checkPackagistCompliance($details)) {
$compliance['packagist'] = false;
$packagistCheck = false;
$refreshDetails |= $this->askFixPackagistCompliance($input, $output, $component->getRepoName());
}
if (!$this->checkTeamCompliance($details)) {
$compliance['teams'] = $this->github->token ? false : null;
$teamCheck = $this->github->token ? false : null;
$refreshDetails |= $this->askFixTeamCompliance($input, $output, $component->getRepoName());
}
if ($refreshDetails) {
Expand All @@ -123,31 +113,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln(sprintf('<comment>%s</comment>: Packagist webhook token updated.', $repoName));
}
}
$isCompliant = true;
$details['compliant'] = '<info>REPO IS COMPLIANT</info>';
foreach ($compliance as $key => $val) {
if ($val === false) {
$isCompliant = false;
$details['compliant'] = '<error>NOT COMPLIANT</error>';
} elseif ($isCompliant && $val === null) {
$details['compliant'] = '<error>???</error> (token required)';
$isCompliant = null;
}
}

if (!$isCompliant) {
$details['compliant'] .= PHP_EOL . implode("\n", array_map(
fn ($k, $v) => $k . ': ' . (is_null($v) ? '???' : var_export($v, true)),
array_keys($compliance),
array_values($compliance)
));
$emoji = fn (?bool $check) => match ($check) { null => '❓', true => '✅', false => '❌'};
$details['compliant'] = implode("\n", [
sprintf('%s Issues, Projects, Wiki, Pages, and Discussion are disabled', $emoji($settingsCheck)),
sprintf('%s Packagist maintainer is "google-cloud"', $emoji($packagistCheck)),
sprintf('%s Github teams permissions are configured correctly', $emoji($teamCheck)),
'',
]);

$isCompliant = $isCompliant && $settingsCheck && $packagistCheck && $teamCheck;
if ($format == 'ci') {
unset($details['repo_config'], $details['packagist_config'], $details['teams']);
}

$table->addRow($details);
(clone $table)->addRow($details)->render();
}
$table->render();

return 0;
return $isCompliant ? Command::SUCCESS : Command::FAILURE;
}

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

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

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

private function askFixTeamCompliance(InputInterface $input, OutputInterface $output, string $repoName)
{
if (!$this->github->token) {
// without a token, don't ask to fix compliance
if (!$this->github->token || $input->getOption('format') == 'ci') {
// without a token, or in CI mode, don't ask to fix compliance
return false;
}
$question = new ConfirmationQuestion(sprintf(
Expand Down
Loading