Public Access
refactor(cli): migrate 64 legacy scripts to CliFramework (#235)
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Wrap all CLI tools in cli/, automation/, maintenance/, deploy/, and release/ in classes extending CliFramework. Replaces manual $argv parsing with configure()/addArgument(), moves logic into run(): int, and converts fwrite(STDERR,...) to $this->log(). Two CLIApp subclasses (generate_dolibarr_version_txt, generate_joomla_update_xml) converted to extend CliFramework directly. Every script now gets free --help, --verbose, --quiet, --dry-run, --json, --no-color, banners, coloured logging, and progress bars. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+68
-111
@@ -12,13 +12,17 @@
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* PATH: /cli/client_provision.php
|
||||
* VERSION: 09.21.00
|
||||
* VERSION: 09.21.07
|
||||
* BRIEF: Provision a new client environment end-to-end
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class ClientProvision
|
||||
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
||||
|
||||
use MokoEnterprise\CliFramework;
|
||||
|
||||
class ClientProvisionCli extends CliFramework
|
||||
{
|
||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
||||
private string $giteaToken = '';
|
||||
@@ -26,24 +30,30 @@ final class ClientProvision
|
||||
private string $grafanaToken = '';
|
||||
private string $configFile = '';
|
||||
private string $step = '';
|
||||
private bool $dryRun = false;
|
||||
/** @var array<string, mixed> */
|
||||
private array $config = [];
|
||||
private string $org = '';
|
||||
private string $repoName = '';
|
||||
|
||||
public function run(): int
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->parseArgs();
|
||||
$this->setDescription('Provision a new client environment end-to-end');
|
||||
$this->addArgument('--config', 'Client config JSON', '');
|
||||
$this->addArgument('--step', 'Run one step: repo, variables, secrets, monitoring, summary', '');
|
||||
}
|
||||
|
||||
protected function run(): int
|
||||
{
|
||||
$this->configFile = $this->getArgument('--config');
|
||||
$this->step = $this->getArgument('--step');
|
||||
|
||||
if ($this->configFile === '') {
|
||||
$this->log('ERROR: --config is required.');
|
||||
$this->printUsage();
|
||||
$this->log('ERROR', '--config is required.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!file_exists($this->configFile)) {
|
||||
$this->log("ERROR: Not found: {$this->configFile}");
|
||||
$this->log('ERROR', "Not found: {$this->configFile}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -51,7 +61,7 @@ final class ClientProvision
|
||||
$this->config = json_decode($json, true);
|
||||
|
||||
if (!is_array($this->config)) {
|
||||
$this->log('ERROR: Invalid JSON in config file.');
|
||||
$this->log('ERROR', 'Invalid JSON in config file.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -65,7 +75,7 @@ final class ClientProvision
|
||||
?? $this->giteaUrl;
|
||||
|
||||
if ($this->giteaToken === '') {
|
||||
$this->log('ERROR: gitea_token or MOKOGITEA_TOKEN required.');
|
||||
$this->log('ERROR', 'gitea_token or MOKOGITEA_TOKEN required.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -73,21 +83,21 @@ final class ClientProvision
|
||||
$clientName = $this->config['name'] ?? '';
|
||||
|
||||
if ($this->org === '' || $clientName === '') {
|
||||
$this->log('ERROR: "org" and "name" required in config.');
|
||||
$this->log('ERROR', '"org" and "name" required in config.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->repoName = 'client-waas-' . $clientName;
|
||||
|
||||
$this->log("=== Client Provisioning: {$clientName} ===");
|
||||
$this->log(" Org: {$this->org}");
|
||||
$this->log(" Repo: {$this->repoName}");
|
||||
$this->log('INFO', "=== Client Provisioning: {$clientName} ===");
|
||||
$this->log('INFO', " Org: {$this->org}");
|
||||
$this->log('INFO', " Repo: {$this->repoName}");
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(' Mode: DRY RUN');
|
||||
$this->log('INFO', ' Mode: DRY RUN');
|
||||
}
|
||||
|
||||
$this->log('');
|
||||
echo "\n";
|
||||
|
||||
$steps = [
|
||||
'repo' => 'createRepo',
|
||||
@@ -116,7 +126,7 @@ final class ClientProvision
|
||||
|
||||
private function createRepo(): int
|
||||
{
|
||||
$this->log('[1/5] Creating repository...');
|
||||
$this->log('INFO', '[1/5] Creating repository...');
|
||||
|
||||
$check = $this->giteaApi(
|
||||
'GET',
|
||||
@@ -124,14 +134,12 @@ final class ClientProvision
|
||||
);
|
||||
|
||||
if ($check['code'] === 200) {
|
||||
$this->log(" SKIP: repo already exists");
|
||||
$this->log('INFO', ' SKIP: repo already exists');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(
|
||||
" WOULD CREATE: {$this->org}/{$this->repoName}"
|
||||
);
|
||||
$this->log('INFO', " WOULD CREATE: {$this->org}/{$this->repoName}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -153,11 +161,11 @@ final class ClientProvision
|
||||
);
|
||||
|
||||
if ($resp['code'] < 200 || $resp['code'] >= 300) {
|
||||
$this->log(" ERROR: HTTP {$resp['code']}");
|
||||
$this->log('ERROR', "HTTP {$resp['code']}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->log(' OK: Repo created');
|
||||
$this->log('INFO', ' OK: Repo created');
|
||||
|
||||
$this->giteaApi(
|
||||
'POST',
|
||||
@@ -168,19 +176,19 @@ final class ClientProvision
|
||||
])
|
||||
);
|
||||
|
||||
$this->log(' OK: dev branch created');
|
||||
$this->log('INFO', ' OK: dev branch created');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function setVariables(): int
|
||||
{
|
||||
$this->log('[2/5] Setting repo variables...');
|
||||
$this->log('INFO', '[2/5] Setting repo variables...');
|
||||
|
||||
$vars = $this->config['variables'] ?? [];
|
||||
|
||||
if (empty($vars)) {
|
||||
$this->log(' SKIP: No variables in config');
|
||||
$this->log('INFO', ' SKIP: No variables in config');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -192,16 +200,16 @@ final class ClientProvision
|
||||
if ($this->dryRun) {
|
||||
$display = strlen($value) > 40
|
||||
? substr($value, 0, 37) . '...' : $value;
|
||||
$this->log(" WOULD SET: {$name} = {$display}");
|
||||
$this->log('INFO', " WOULD SET: {$name} = {$display}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$ok = $this->setOrCreateVariable($api, $name, $value);
|
||||
|
||||
if ($ok) {
|
||||
$this->log(" OK: {$name}");
|
||||
$this->log('INFO', " OK: {$name}");
|
||||
} else {
|
||||
$this->log(" ERROR: {$name}");
|
||||
$this->log('ERROR', " {$name}");
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
@@ -211,12 +219,12 @@ final class ClientProvision
|
||||
|
||||
private function setSecrets(): int
|
||||
{
|
||||
$this->log('[3/5] Setting repo secrets...');
|
||||
$this->log('INFO', '[3/5] Setting repo secrets...');
|
||||
|
||||
$secrets = $this->config['secrets'] ?? [];
|
||||
|
||||
if (empty($secrets)) {
|
||||
$this->log(' SKIP: No secrets in config');
|
||||
$this->log('INFO', ' SKIP: No secrets in config');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -229,7 +237,7 @@ final class ClientProvision
|
||||
$keyPath = substr($value, 1);
|
||||
|
||||
if (!file_exists($keyPath)) {
|
||||
$this->log(" ERROR: {$name} file not found: {$keyPath}");
|
||||
$this->log('ERROR', " {$name} file not found: {$keyPath}");
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
@@ -238,7 +246,7 @@ final class ClientProvision
|
||||
}
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(" WOULD SET: {$name} (len: " . strlen($value) . ")");
|
||||
$this->log('INFO', " WOULD SET: {$name} (len: " . strlen($value) . ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -249,9 +257,9 @@ final class ClientProvision
|
||||
);
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300) {
|
||||
$this->log(" OK: {$name}");
|
||||
$this->log('INFO', " OK: {$name}");
|
||||
} else {
|
||||
$this->log(" ERROR: {$name} (HTTP {$resp['code']})");
|
||||
$this->log('ERROR', " {$name} (HTTP {$resp['code']})");
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
@@ -261,12 +269,12 @@ final class ClientProvision
|
||||
|
||||
private function setupMonitoring(): int
|
||||
{
|
||||
$this->log('[4/5] Setting up monitoring...');
|
||||
$this->log('INFO', '[4/5] Setting up monitoring...');
|
||||
|
||||
$mon = $this->config['monitoring'] ?? [];
|
||||
|
||||
if (empty($mon)) {
|
||||
$this->log(' SKIP: No monitoring config');
|
||||
$this->log('INFO', ' SKIP: No monitoring config');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -291,10 +299,10 @@ final class ClientProvision
|
||||
$urlStr = implode("\n", $urls);
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(" WOULD SET: MONITORED_URLS");
|
||||
$this->log('INFO', ' WOULD SET: MONITORED_URLS');
|
||||
} else {
|
||||
$this->setOrCreateVariable($api, 'MONITORED_URLS', $urlStr);
|
||||
$this->log(' OK: MONITORED_URLS');
|
||||
$this->log('INFO', ' OK: MONITORED_URLS');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +310,10 @@ final class ClientProvision
|
||||
$domainStr = implode("\n", $domains);
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(" WOULD SET: MONITORED_DOMAINS");
|
||||
$this->log('INFO', ' WOULD SET: MONITORED_DOMAINS');
|
||||
} else {
|
||||
$this->setOrCreateVariable($api, 'MONITORED_DOMAINS', $domainStr);
|
||||
$this->log(' OK: MONITORED_DOMAINS');
|
||||
$this->log('INFO', ' OK: MONITORED_DOMAINS');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,19 +323,19 @@ final class ClientProvision
|
||||
private function pushGrafanaDashboard(string $file, string $folder): void
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
$this->log(" WARN: Dashboard not found: {$file}");
|
||||
$this->warning("Dashboard not found: {$file}");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log(" WOULD PUSH: dashboard to \"{$folder}\"");
|
||||
$this->log('INFO', " WOULD PUSH: dashboard to \"{$folder}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
$dashboard = json_decode(file_get_contents($file), true);
|
||||
|
||||
if (!is_array($dashboard)) {
|
||||
$this->log(' ERROR: Invalid dashboard JSON');
|
||||
$this->log('ERROR', 'Invalid dashboard JSON');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -346,9 +354,9 @@ final class ClientProvision
|
||||
|
||||
if ($resp['code'] === 200) {
|
||||
$data = json_decode($resp['body'], true);
|
||||
$this->log(" OK: Dashboard (uid: " . ($data['uid'] ?? '?') . ")");
|
||||
$this->log('INFO', " OK: Dashboard (uid: " . ($data['uid'] ?? '?') . ")");
|
||||
} else {
|
||||
$this->log(" ERROR: Dashboard push (HTTP {$resp['code']})");
|
||||
$this->log('ERROR', " Dashboard push (HTTP {$resp['code']})");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,20 +387,19 @@ final class ClientProvision
|
||||
{
|
||||
$vars = $this->config['variables'] ?? [];
|
||||
$secrets = $this->config['secrets'] ?? [];
|
||||
$clientName = $this->config['name'] ?? '';
|
||||
|
||||
$this->log('');
|
||||
$this->log('[5/5] Provisioning summary');
|
||||
$this->log(str_repeat('=', 60));
|
||||
$this->log(" Repo: {$this->giteaUrl}/{$this->org}/{$this->repoName}");
|
||||
$this->log(' Variables: ' . count($vars) . ' set');
|
||||
$this->log(' Secrets: ' . count($secrets) . ' set');
|
||||
$this->log('');
|
||||
$this->log('Next steps:');
|
||||
$this->log(' 1. Clone and customize the Joomla template');
|
||||
$this->log(' 2. Push to dev to trigger dev deployment');
|
||||
$this->log(' 3. Merge dev -> main for production release');
|
||||
$this->log(str_repeat('=', 60));
|
||||
echo "\n";
|
||||
$this->log('INFO', '[5/5] Provisioning summary');
|
||||
echo str_repeat('=', 60) . "\n";
|
||||
echo " Repo: {$this->giteaUrl}/{$this->org}/{$this->repoName}\n";
|
||||
echo ' Variables: ' . count($vars) . " set\n";
|
||||
echo ' Secrets: ' . count($secrets) . " set\n";
|
||||
echo "\n";
|
||||
echo "Next steps:\n";
|
||||
echo " 1. Clone and customize the Joomla template\n";
|
||||
echo " 2. Push to dev to trigger dev deployment\n";
|
||||
echo " 3. Merge dev -> main for production release\n";
|
||||
echo str_repeat('=', 60) . "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -419,51 +426,6 @@ final class ClientProvision
|
||||
return $resp['code'] >= 200 && $resp['code'] < 300;
|
||||
}
|
||||
|
||||
private function parseArgs(): void
|
||||
{
|
||||
$args = $_SERVER['argv'] ?? [];
|
||||
$count = count($args);
|
||||
|
||||
for ($i = 1; $i < $count; $i++) {
|
||||
switch ($args[$i]) {
|
||||
case '--config':
|
||||
$this->configFile = $args[++$i] ?? '';
|
||||
break;
|
||||
case '--step':
|
||||
$this->step = $args[++$i] ?? '';
|
||||
break;
|
||||
case '--dry-run':
|
||||
$this->dryRun = true;
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
$this->printUsage();
|
||||
exit(0);
|
||||
default:
|
||||
$this->log("WARNING: Unknown arg: {$args[$i]}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function printUsage(): void
|
||||
{
|
||||
$this->log('Usage: client_provision.php --config <file.json> [options]');
|
||||
$this->log('');
|
||||
$this->log('Provision a new client environment end-to-end.');
|
||||
$this->log('');
|
||||
$this->log('Options:');
|
||||
$this->log(' --config <file> Client config JSON');
|
||||
$this->log(' --step <name> Run one step: repo, variables, secrets, monitoring, summary');
|
||||
$this->log(' --dry-run Preview without changes');
|
||||
$this->log(' --help, -h Show this help');
|
||||
$this->log('');
|
||||
$this->log('Environment variables:');
|
||||
$this->log(' MOKOGITEA_TOKEN Gitea API token');
|
||||
$this->log(' GRAFANA_URL Grafana instance URL');
|
||||
$this->log(' GRAFANA_TOKEN Grafana API token');
|
||||
}
|
||||
|
||||
private function giteaApi(
|
||||
string $method,
|
||||
string $endpoint,
|
||||
@@ -523,12 +485,7 @@ final class ClientProvision
|
||||
|
||||
return ['code' => $httpCode, 'body' => $responseBody];
|
||||
}
|
||||
|
||||
private function log(string $message): void
|
||||
{
|
||||
fwrite(STDERR, $message . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
$app = new ClientProvision();
|
||||
exit($app->run());
|
||||
$app = new ClientProvisionCli();
|
||||
exit($app->execute());
|
||||
|
||||
Reference in New Issue
Block a user