Skip to content
Draft
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
38 changes: 38 additions & 0 deletions .github/scripts/bin/dependencies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use DockerBase\Command\Process;
use DockerBase\Dependency\Application;
use DockerBase\Dependency\Console;
use DockerBase\Dependency\Fetcher\HTTP;
use DockerBase\Dependency\Program;
use DockerBase\Dependency\Reporter;
use DockerBase\Dependency\Updater;

$root = dirname(__DIR__, 3);

require $root . '/vendor/autoload.php';

$result = (new Program(
new Console(
new Updater(
Application::create(
new Process($root),
new HTTP(),
),
),
new Reporter(),
$root . '/Dockerfile',
),
))->execute(array_slice($argv, 1));

if ($result->output !== '') {
fwrite(STDOUT, $result->output);
}
if ($result->error !== '') {
fwrite(STDERR, $result->error);
}

exit($result->code);
49 changes: 49 additions & 0 deletions .github/scripts/bin/orchestrator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use DockerBase\Automation\Application;
use DockerBase\Automation\Clock\System as Clock;
use DockerBase\Automation\Orchestrator;
use DockerBase\Automation\Repository\GitHub;
use DockerBase\Automation\Sleeper\System as Sleeper;
use DockerBase\Automation\WorkflowOutput;
use DockerBase\Command\Process;

$root = dirname(__DIR__, 3);

require $root . '/vendor/autoload.php';

$repository = getenv('GITHUB_REPOSITORY')
?: throw new RuntimeException('GITHUB_REPOSITORY is required');
$version = getenv('GITHUB_API_VERSION')
?: throw new RuntimeException('GITHUB_API_VERSION is required');
$arguments = array_slice($argv, 1);
$input = '';
if (($arguments[0] ?? null) === 'validate-pull') {
$input = stream_get_contents(STDIN);
if ($input === false) {
throw new RuntimeException('Unable to read standard input');
}
}

$application = new Application(
new Orchestrator(
new GitHub(new Process($root), $repository, $version),
new Clock(),
new Sleeper(),
),
);
$output = (new WorkflowOutput(
$application->execute($arguments, $input),
))->render();

if ($output !== '') {
$path = getenv('GITHUB_OUTPUT')
?: throw new RuntimeException('GITHUB_OUTPUT is required');
$written = file_put_contents($path, $output, FILE_APPEND | LOCK_EX);
if ($written !== strlen($output)) {
throw new RuntimeException('Unable to write workflow outputs');
}
}
43 changes: 43 additions & 0 deletions .github/scripts/bin/parity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use DockerBase\Parity\Verifier;

$root = dirname(__DIR__, 3);

require $root . '/vendor/autoload.php';

$arguments = array_slice($argv, 1);
if (count($arguments) < 1 || count($arguments) > 2) {
fwrite(STDERR, 'Usage: parity.php RESULTS [MANIFEST]' . PHP_EOL);

exit(2);
}

$results = $arguments[0];
$manifest = $arguments[1]
?? $root . '/.github/scripts/tests/equivalence.json';

try {
$count = (new Verifier($manifest, $results))->verify();
} catch (\Throwable $exception) {
$detail = preg_replace(
'/\s+/',
' ',
trim($exception->getMessage()),
);
fwrite(
STDERR,
'Error: '
. ($detail === null || $detail === ''
? 'Parity verification failed'
: $detail)
. PHP_EOL,
);

exit(1);
}

fwrite(STDOUT, "Verified {$count} parity contracts." . PHP_EOL);
163 changes: 163 additions & 0 deletions .github/scripts/src/Automation/Application.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation;

use DockerBase\Automation\Pull\Payload;
use InvalidArgumentException;

final readonly class Application
{
public function __construct(
private Orchestrator $orchestrator,
) {
}

/**
* @param list<string> $arguments
*
* @return array<string, string>
*/
public function execute(array $arguments, string $input = ''): array
{
if ($arguments === []) {
throw new InvalidArgumentException(
'An orchestration operation is required',
);
}

[$operation, $values] = [
array_shift($arguments),
$arguments,
];

return match ([$operation, count($values)]) {
['recover', 0] => $this->recover(),
['validate-pull', 3] => Payload::validate(
$input,
$values[0],
$values[1],
$values[2],
),
['wait-checks', 3] => $this->checks(
$values[0],
$values[1],
$values[2],
),
['merge', 3] => [
'head' => $this->orchestrator->merge(
$this->integer($values[0]),
$values[1],
$values[2],
),
],
['prepare', 4] => $this->preparation(
$this->orchestrator->prepare(
$values[0] === '' ? null : $values[0],
$values[1],
$this->integer($values[2]),
$values[3] === '' ? null : $this->integer($values[3]),
),
),
['wait', 2] => $this->wait($values[0], $values[1]),
['publish', 4] => $this->publish(
$values[0],
$values[1],
$this->integer($values[2]),
$this->integer($values[3]),
),
default => throw new InvalidArgumentException(
"Invalid '{$operation}' arguments",
),
};
}

/**
* @return array<string, string>
*/
private function recover(): array
{
$candidate = $this->orchestrator->recover();
if ($candidate === null) {
return ['pending' => 'false'];
}

return [
'pending' => 'true',
'tag' => $candidate->tag ?? '',
'head' => $candidate->target,
'pull' => (string) $candidate->pull,
'draft' => $candidate->draft === null
? ''
: (string) $candidate->draft,
];
}

/**
* @return array<string, string>
*/
private function preparation(Preparation $preparation): array
{
return [
'tag' => $preparation->tag,
'head' => $preparation->target,
'pull' => (string) $preparation->pull,
'draft' => (string) $preparation->draft,
];
}

/**
* @return array<string, string>
*/
private function wait(string $tag, string $target): array
{
$this->orchestrator->wait($tag, $target);

return [];
}

/**
* @return array<string, string>
*/
private function checks(
string $branch,
string $head,
string $created,
): array {
$this->orchestrator->checks($branch, $head, $created);

return [];
}

/**
* @return array<string, string>
*/
private function publish(
string $tag,
string $target,
int $pull,
int $draft,
): array {
$this->orchestrator->publish($tag, $target, $pull, $draft);

return [];
}

private function integer(string $value): int
{
if (
filter_var(
$value,
FILTER_VALIDATE_INT,
FILTER_NULL_ON_FAILURE,
) === null
) {
throw new InvalidArgumentException(
"Invalid integer '{$value}'",
);
}

return (int) $value;
}
}
9 changes: 9 additions & 0 deletions .github/scripts/src/Automation/ApprovalMissingException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation;

final class ApprovalMissingException extends AutomationException
{
}
11 changes: 11 additions & 0 deletions .github/scripts/src/Automation/AutomationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation;

use RuntimeException;

class AutomationException extends RuntimeException
{
}
16 changes: 16 additions & 0 deletions .github/scripts/src/Automation/Candidate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation;

final readonly class Candidate
{
public function __construct(
public ?string $tag,
public string $target,
public int $pull,
public ?int $draft,
) {
}
}
12 changes: 12 additions & 0 deletions .github/scripts/src/Automation/Clock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation;

use DateTimeImmutable;

interface Clock
{
public function now(): DateTimeImmutable;
}
19 changes: 19 additions & 0 deletions .github/scripts/src/Automation/Clock/System.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace DockerBase\Automation\Clock;

use DateTimeImmutable;
use DateTimeZone;
use DockerBase\Automation\Clock;
use Override;

final readonly class System implements Clock
{
#[Override]
public function now(): DateTimeImmutable
{
return new DateTimeImmutable('now', new DateTimeZone('UTC'));
}
}
Loading
Loading