Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 61 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
MonitorBackupCommand | |
0.00% |
0 / 61 |
|
0.00% |
0 / 3 |
552 | |
0.00% |
0 / 1 |
getAllowedAppEnvs | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
handle | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
90 | |||
checkWorkflowRun | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
182 |
1 | <?php |
2 | |
3 | namespace Olz\Command; |
4 | |
5 | use Olz\Command\Common\OlzCommand; |
6 | use Symfony\Component\Console\Attribute\AsCommand; |
7 | use Symfony\Component\Console\Command\Command; |
8 | use Symfony\Component\Console\Input\InputInterface; |
9 | use Symfony\Component\Console\Output\OutputInterface; |
10 | |
11 | #[AsCommand(name: 'olz:monitor-backup')] |
12 | class MonitorBackupCommand extends OlzCommand { |
13 | /** @var non-empty-string */ |
14 | protected static string $user_agent_string = "Mozilla/5.0 (compatible; backup_monitoring/2.1; +https://github.com/olzimmerberg/olz-website/blob/main/src/Command/MonitorBackupCommand.php)"; |
15 | |
16 | /** @return array<string> */ |
17 | protected function getAllowedAppEnvs(): array { |
18 | return ['dev', 'test', 'staging', 'prod']; |
19 | } |
20 | |
21 | protected function handle(InputInterface $input, OutputInterface $output): int { |
22 | $ch = curl_init(); |
23 | |
24 | curl_setopt($ch, CURLOPT_URL, "https://api.github.com/repos/olzimmerberg/olz-website/actions/workflows/ci-scheduled.yml/runs?page=1&per_page=3&status=completed"); |
25 | curl_setopt($ch, CURLOPT_USERAGENT, self::$user_agent_string); |
26 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
27 | $completed_runs_raw = curl_exec($ch) ?: ''; |
28 | |
29 | $completed_runs = json_decode(!is_bool($completed_runs_raw) ? $completed_runs_raw : '', true); |
30 | if (!$completed_runs) { |
31 | throw new \Exception("No completed runs JSON"); |
32 | } |
33 | $workflow_runs = $completed_runs['workflow_runs'] ?? null; |
34 | if (!$workflow_runs) { |
35 | throw new \Exception("No workflow_runs"); |
36 | } |
37 | if (count($workflow_runs) !== 3) { |
38 | throw new \Exception("Expected exactly 3 workflow runs"); |
39 | } |
40 | $has_successful = false; |
41 | $errors = ''; |
42 | foreach ($workflow_runs as $workflow_run) { |
43 | try { |
44 | $this->checkWorkflowRun($workflow_run); |
45 | $has_successful = true; |
46 | } catch (\Throwable $th) { |
47 | $errors .= " ".$th->getMessage()."\n"; |
48 | } |
49 | } |
50 | if ($has_successful) { |
51 | $this->logAndOutput("OK:"); |
52 | return Command::SUCCESS; |
53 | } |
54 | $this->logAndOutput("All 3 backup runs have problems:\n {$errors}"); |
55 | return Command::FAILURE; |
56 | } |
57 | |
58 | /** @param array<string, mixed> $workflow_run */ |
59 | protected function checkWorkflowRun(array $workflow_run): void { |
60 | if ($workflow_run['name'] !== 'CI:scheduled') { |
61 | throw new \Exception("Expected workflow_run name to be CI:scheduled"); |
62 | } |
63 | if ($workflow_run['head_branch'] !== 'main') { |
64 | throw new \Exception("Expected workflow_run head_branch to be main"); |
65 | } |
66 | if ($workflow_run['status'] !== 'completed') { |
67 | throw new \Exception("Expected workflow_run status to be completed"); |
68 | } |
69 | $now = new \DateTime(); |
70 | $minus_two_days = \DateInterval::createFromDateString("-2 days"); |
71 | $two_days_ago = $now->add($minus_two_days); |
72 | $created_at = new \DateTime($workflow_run['created_at']); |
73 | if ($created_at->getTimestamp() < $two_days_ago->getTimestamp()) { |
74 | throw new \Exception("Expected workflow_run created_at ({$created_at->format('Y-m-d H:i:s')}) to be in the last 2 days ({$two_days_ago->format('Y-m-d H:i:s')})"); |
75 | } |
76 | if ($workflow_run['conclusion'] !== 'success') { |
77 | throw new \Exception("Expected workflow_run conclusion to be success"); |
78 | } |
79 | if (!$workflow_run['artifacts_url']) { |
80 | throw new \Exception("Expected workflow_run artifacts_url"); |
81 | } |
82 | |
83 | $ch = curl_init(); |
84 | |
85 | curl_setopt($ch, CURLOPT_URL, $workflow_run['artifacts_url']); |
86 | curl_setopt($ch, CURLOPT_USERAGENT, self::$user_agent_string); |
87 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
88 | $artifacts_raw = curl_exec($ch); |
89 | |
90 | curl_close($ch); |
91 | |
92 | $artifacts_dict = json_decode(!is_bool($artifacts_raw) ? $artifacts_raw : '', true); |
93 | if (!$artifacts_dict) { |
94 | throw new \Exception("No artifacts JSON"); |
95 | } |
96 | $artifacts = $artifacts_dict['artifacts'] ?? null; |
97 | if (!$artifacts) { |
98 | throw new \Exception("No artifacts"); |
99 | } |
100 | if (count($artifacts) !== 1) { |
101 | throw new \Exception("Expected exactly 1 artifact"); |
102 | } |
103 | $artifact = $artifacts[0]; |
104 | if ($artifact['name'] !== 'backup') { |
105 | throw new \Exception("Expected artifact name to be backup"); |
106 | } |
107 | if ($artifact['expired'] !== false) { |
108 | throw new \Exception("Expected artifact expired to be false"); |
109 | } |
110 | } |
111 | } |