226 lines
7.2 KiB
PHP
226 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Jobs\TenantConfiguration;
|
|
|
|
use App\Jobs\Concerns\BridgesFailedOperationRun;
|
|
use App\Jobs\Middleware\EnsureQueuedExecutionLegitimate;
|
|
use App\Jobs\Middleware\TrackOperationRun;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Services\Audit\AuditRecorder;
|
|
use App\Services\OperationRunService;
|
|
use App\Services\TenantConfiguration\GenericContentEvidenceCaptureService;
|
|
use App\Support\Audit\AuditActorSnapshot;
|
|
use App\Support\Audit\AuditOutcome;
|
|
use App\Support\Audit\AuditTargetSnapshot;
|
|
use App\Support\OpsUx\RunFailureSanitizer;
|
|
use App\Support\OperationRunStatus;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
class CaptureTenantConfigurationEvidenceJob implements ShouldQueue
|
|
{
|
|
use BridgesFailedOperationRun;
|
|
use Dispatchable;
|
|
use InteractsWithQueue;
|
|
use Queueable;
|
|
use SerializesModels;
|
|
|
|
public int $timeout = 300;
|
|
|
|
public bool $failOnTimeout = true;
|
|
|
|
public ?OperationRun $operationRun = null;
|
|
|
|
public function __construct(
|
|
public OperationRun $run,
|
|
) {
|
|
$this->operationRun = $run;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, object>
|
|
*/
|
|
public function middleware(): array
|
|
{
|
|
return [
|
|
new EnsureQueuedExecutionLegitimate,
|
|
new TrackOperationRun,
|
|
];
|
|
}
|
|
|
|
public function getOperationRun(): ?OperationRun
|
|
{
|
|
return $this->operationRun;
|
|
}
|
|
|
|
public function getOperationRunId(): int
|
|
{
|
|
return (int) $this->run->getKey();
|
|
}
|
|
|
|
public function handle(
|
|
GenericContentEvidenceCaptureService $captureService,
|
|
OperationRunService $operationRuns,
|
|
AuditRecorder $auditRecorder,
|
|
): void {
|
|
$run = $this->run->fresh(['tenant.workspace', 'user']);
|
|
|
|
if (! $run instanceof OperationRun) {
|
|
throw new RuntimeException('OperationRun context is required for tenant configuration capture.');
|
|
}
|
|
|
|
$this->operationRun = $run;
|
|
|
|
if ($run->status === OperationRunStatus::Completed->value) {
|
|
return;
|
|
}
|
|
|
|
$tenant = $run->tenant;
|
|
$providerConnectionId = (int) data_get($run->context, 'target_scope.provider_connection_id', 0);
|
|
$providerConnection = ProviderConnection::query()
|
|
->whereKey($providerConnectionId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
|
|
if (! $tenant || ! $providerConnection instanceof ProviderConnection) {
|
|
throw new RuntimeException('Tenant configuration capture run is missing its managed environment or same-scope provider connection.');
|
|
}
|
|
|
|
try {
|
|
$result = $captureService->capture(
|
|
tenant: $tenant,
|
|
providerConnection: $providerConnection,
|
|
operationRun: $run,
|
|
canonicalTypes: $this->canonicalTypes($run),
|
|
);
|
|
|
|
$run->forceFill([
|
|
'context' => $this->contextWithCaptureResult($run, $result['outcomes']),
|
|
])->save();
|
|
|
|
$completed = $operationRuns->updateRun(
|
|
run: $run,
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: $result['run_outcome'],
|
|
summaryCounts: $result['summary_counts'],
|
|
failures: $result['failures'],
|
|
);
|
|
|
|
$this->recordTerminalAudit($auditRecorder, $completed, $providerConnection, $result);
|
|
} catch (Throwable $e) {
|
|
$this->recordFailureAudit($auditRecorder, $run, $providerConnection, $e);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return list<string>|null
|
|
*/
|
|
private function canonicalTypes(OperationRun $run): ?array
|
|
{
|
|
$types = data_get($run->context, 'resource_types');
|
|
|
|
if (! is_array($types)) {
|
|
return null;
|
|
}
|
|
|
|
return collect($types)
|
|
->filter(static fn (mixed $type): bool => is_string($type) && trim($type) !== '')
|
|
->map(static fn (string $type): string => trim($type))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $outcomes
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function contextWithCaptureResult(OperationRun $run, array $outcomes): array
|
|
{
|
|
$context = is_array($run->context) ? $run->context : [];
|
|
$context['capture'] = [
|
|
'resource_type_outcomes' => $outcomes,
|
|
'completed_at' => now()->toJSON(),
|
|
];
|
|
|
|
return $context;
|
|
}
|
|
|
|
/**
|
|
* @param array{outcomes: list<array<string, mixed>>, summary_counts: array<string, int>, run_outcome: string, failures: list<array<string, mixed>>} $result
|
|
*/
|
|
private function recordTerminalAudit(
|
|
AuditRecorder $auditRecorder,
|
|
OperationRun $run,
|
|
ProviderConnection $providerConnection,
|
|
array $result,
|
|
): void {
|
|
$tenant = $run->tenant;
|
|
|
|
if (! $tenant) {
|
|
return;
|
|
}
|
|
|
|
$auditRecorder->record(
|
|
action: $result['run_outcome'] === 'failed'
|
|
? 'tenant_configuration.capture.failed'
|
|
: 'tenant_configuration.capture.completed',
|
|
context: [
|
|
'metadata' => [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'provider_connection_id' => (int) $providerConnection->getKey(),
|
|
'resource_type_outcomes' => $result['outcomes'],
|
|
'summary_counts' => $result['summary_counts'],
|
|
],
|
|
],
|
|
workspace: $tenant->workspace,
|
|
tenant: $tenant,
|
|
actor: $run->user ? AuditActorSnapshot::human($run->user) : null,
|
|
target: new AuditTargetSnapshot('operation_run', (int) $run->getKey()),
|
|
outcome: AuditOutcome::normalize($result['run_outcome']),
|
|
operationRunId: (int) $run->getKey(),
|
|
);
|
|
}
|
|
|
|
private function recordFailureAudit(
|
|
AuditRecorder $auditRecorder,
|
|
OperationRun $run,
|
|
ProviderConnection $providerConnection,
|
|
Throwable $e,
|
|
): void {
|
|
$tenant = $run->tenant;
|
|
|
|
if (! $tenant) {
|
|
return;
|
|
}
|
|
|
|
$auditRecorder->record(
|
|
action: 'tenant_configuration.capture.failed',
|
|
context: [
|
|
'metadata' => [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'provider_connection_id' => (int) $providerConnection->getKey(),
|
|
'reason_code' => 'capture_exception',
|
|
'message' => RunFailureSanitizer::sanitizeMessage($e->getMessage()),
|
|
],
|
|
],
|
|
workspace: $tenant->workspace,
|
|
tenant: $tenant,
|
|
actor: $run->user ? AuditActorSnapshot::human($run->user) : null,
|
|
target: new AuditTargetSnapshot('operation_run', (int) $run->getKey()),
|
|
outcome: AuditOutcome::Failed,
|
|
operationRunId: (int) $run->getKey(),
|
|
);
|
|
}
|
|
}
|