Spec 432: Exchange PowerShell production runner boundary and runtime gate. Validation: php artisan test --filter=Spec432 --compact; ./vendor/bin/pint --dirty --test --format agent; git diff --check. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #499
138 lines
5.0 KiB
PHP
138 lines
5.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\TenantConfiguration;
|
|
|
|
use App\Support\Providers\ProviderReasonCodes;
|
|
use Throwable;
|
|
|
|
final class ExchangePowerShellRuntimeReadinessChecker
|
|
{
|
|
public function __construct(
|
|
private readonly ExchangePowerShellProcessExecutor $executor,
|
|
) {}
|
|
|
|
public function check(?ExchangePowerShellRuntimePolicy $policy = null): ExchangePowerShellGateResult
|
|
{
|
|
$policy ??= ExchangePowerShellRuntimePolicy::fromConfig();
|
|
|
|
try {
|
|
if (! $policy->invocationEnabled) {
|
|
return $this->blocked('runtime_blocked_feature_disabled', [
|
|
'runtime_state' => 'blocked_feature_disabled',
|
|
]);
|
|
}
|
|
|
|
if (! $policy->productionRunnerEnabled) {
|
|
return $this->blocked('runtime_blocked_runner_disabled', [
|
|
'runtime_state' => 'blocked_runner_disabled',
|
|
]);
|
|
}
|
|
|
|
if (! $policy->environmentAllowed()) {
|
|
return $this->blocked('runtime_blocked_environment_not_allowed', [
|
|
'runtime_state' => 'environment_not_allowed',
|
|
'runtime_environment' => $policy->currentEnvironment,
|
|
]);
|
|
}
|
|
|
|
if (! $policy->timeoutPolicyValid()) {
|
|
return $this->blocked('runtime_blocked_timeout_policy_missing', [
|
|
'runtime_state' => 'timeout_policy_missing',
|
|
]);
|
|
}
|
|
|
|
if (! $policy->tempPolicySafe()) {
|
|
return $this->blocked('runtime_blocked_temp_policy_unsafe', [
|
|
'runtime_state' => 'temp_policy_unsafe',
|
|
]);
|
|
}
|
|
|
|
if (! $this->executor->available()) {
|
|
return $this->blocked('runtime_blocked_process_executor_missing', [
|
|
'runtime_state' => 'process_executor_missing',
|
|
]);
|
|
}
|
|
|
|
$powershell = $this->executor->run(new ExchangePowerShellProcessCommand(
|
|
arguments: [
|
|
$policy->powershellBinary,
|
|
'-NoLogo',
|
|
'-NoProfile',
|
|
'-NonInteractive',
|
|
'-Command',
|
|
'$PSVersionTable.PSVersion.ToString()',
|
|
],
|
|
timeoutSeconds: $policy->checkTimeoutSeconds,
|
|
stdoutMaxBytes: 4096,
|
|
stderrMaxBytes: 4096,
|
|
));
|
|
|
|
if ($powershell->timedOut || $powershell->exitCode !== 0 || trim($powershell->stdout) === '') {
|
|
return $this->blocked('runtime_blocked_powershell_missing', [
|
|
'runtime_state' => 'powershell_missing',
|
|
]);
|
|
}
|
|
|
|
if (! $policy->moduleCheckEnabled) {
|
|
return ExchangePowerShellGateResult::allowed([
|
|
'runtime_state' => 'ready',
|
|
'module_state' => 'unchecked',
|
|
]);
|
|
}
|
|
|
|
$module = $this->executor->run(new ExchangePowerShellProcessCommand(
|
|
arguments: [
|
|
$policy->powershellBinary,
|
|
'-NoLogo',
|
|
'-NoProfile',
|
|
'-NonInteractive',
|
|
'-Command',
|
|
'Get-Module -ListAvailable -Name ExchangeOnlineManagement',
|
|
],
|
|
timeoutSeconds: $policy->checkTimeoutSeconds,
|
|
stdoutMaxBytes: 4096,
|
|
stderrMaxBytes: 4096,
|
|
));
|
|
|
|
if ($module->timedOut || $module->exitCode !== 0) {
|
|
return $this->blocked('runtime_blocked_exchange_module_unknown', [
|
|
'runtime_state' => 'exchange_module_unknown',
|
|
]);
|
|
}
|
|
|
|
if (! str_contains(trim($module->stdout), 'ExchangeOnlineManagement')) {
|
|
return $this->blocked('runtime_blocked_exchange_module_missing', [
|
|
'runtime_state' => 'exchange_module_missing',
|
|
]);
|
|
}
|
|
|
|
return ExchangePowerShellGateResult::allowed([
|
|
'runtime_state' => 'ready',
|
|
'module_state' => 'available',
|
|
]);
|
|
} catch (Throwable) {
|
|
return ExchangePowerShellGateResult::blocked(
|
|
reasonCode: ProviderReasonCodes::UnknownError,
|
|
failureCode: 'runtime_failed_unexpected',
|
|
message: 'Exchange PowerShell runtime readiness failed safely.',
|
|
context: ['runtime_state' => 'unexpected_failure'],
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $context
|
|
*/
|
|
private function blocked(string $failureCode, array $context): ExchangePowerShellGateResult
|
|
{
|
|
return ExchangePowerShellGateResult::blocked(
|
|
reasonCode: ProviderReasonCodes::ProviderBindingUnsupported,
|
|
failureCode: $failureCode,
|
|
message: 'Exchange PowerShell runtime readiness is not satisfied.',
|
|
context: $context,
|
|
);
|
|
}
|
|
}
|