130 lines
4.0 KiB
PHP
130 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Baselines;
|
|
|
|
use App\Models\Policy;
|
|
use App\Models\Tenant;
|
|
use App\Services\Intune\PolicyCaptureOrchestrator;
|
|
use App\Support\Baselines\BaselineEvidenceResumeToken;
|
|
use App\Support\Baselines\PolicyVersionCapturePurpose;
|
|
|
|
final class BaselineContentCapturePhase
|
|
{
|
|
public function __construct(
|
|
private readonly PolicyCaptureOrchestrator $captureOrchestrator,
|
|
) {}
|
|
|
|
/**
|
|
* Capture baseline-purpose policy versions (content + assignments + scope tags) within a run budget.
|
|
*
|
|
* @param list<array{policy_type: string, subject_external_id: string}> $subjects
|
|
* @param array{max_items_per_run: int, max_concurrency: int, max_retries: int} $budgets
|
|
* @return array{
|
|
* stats: array{requested: int, succeeded: int, skipped: int, failed: int, throttled: int},
|
|
* gaps: array<string, int>,
|
|
* resume_token: ?string
|
|
* }
|
|
*/
|
|
public function capture(
|
|
Tenant $tenant,
|
|
array $subjects,
|
|
PolicyVersionCapturePurpose $purpose,
|
|
array $budgets,
|
|
?string $resumeToken = null,
|
|
?int $operationRunId = null,
|
|
?int $baselineProfileId = null,
|
|
?string $createdBy = null,
|
|
): array {
|
|
$maxItemsPerRun = max(0, (int) ($budgets['max_items_per_run'] ?? 0));
|
|
|
|
$offset = 0;
|
|
|
|
if (is_string($resumeToken) && $resumeToken !== '') {
|
|
$state = BaselineEvidenceResumeToken::decode($resumeToken) ?? [];
|
|
$offset = is_numeric($state['offset'] ?? null) ? max(0, (int) $state['offset']) : 0;
|
|
}
|
|
|
|
$remaining = array_slice($subjects, $offset);
|
|
$batch = $maxItemsPerRun > 0 ? array_slice($remaining, 0, $maxItemsPerRun) : [];
|
|
|
|
$stats = [
|
|
'requested' => count($batch),
|
|
'succeeded' => 0,
|
|
'skipped' => 0,
|
|
'failed' => 0,
|
|
'throttled' => 0,
|
|
];
|
|
|
|
/** @var array<string, int> $gaps */
|
|
$gaps = [];
|
|
|
|
foreach ($batch as $subject) {
|
|
$policyType = trim((string) ($subject['policy_type'] ?? ''));
|
|
$externalId = trim((string) ($subject['subject_external_id'] ?? ''));
|
|
|
|
if ($policyType === '' || $externalId === '') {
|
|
$gaps['invalid_subject'] = ($gaps['invalid_subject'] ?? 0) + 1;
|
|
$stats['failed']++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$policy = Policy::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('policy_type', $policyType)
|
|
->where('external_id', $externalId)
|
|
->first();
|
|
|
|
if (! $policy instanceof Policy) {
|
|
$gaps['policy_not_found'] = ($gaps['policy_not_found'] ?? 0) + 1;
|
|
$stats['failed']++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$result = $this->captureOrchestrator->capture(
|
|
policy: $policy,
|
|
tenant: $tenant,
|
|
includeAssignments: true,
|
|
includeScopeTags: true,
|
|
createdBy: $createdBy,
|
|
metadata: [
|
|
'capture_source' => 'baseline_evidence',
|
|
],
|
|
capturePurpose: $purpose,
|
|
operationRunId: $operationRunId,
|
|
baselineProfileId: $baselineProfileId,
|
|
);
|
|
|
|
if (is_array($result) && array_key_exists('failure', $result)) {
|
|
$gaps['capture_failed'] = ($gaps['capture_failed'] ?? 0) + 1;
|
|
$stats['failed']++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$stats['succeeded']++;
|
|
}
|
|
|
|
$processed = $offset + count($batch);
|
|
$resumeTokenOut = null;
|
|
|
|
if ($processed < count($subjects)) {
|
|
$resumeTokenOut = BaselineEvidenceResumeToken::encode([
|
|
'offset' => $processed,
|
|
]);
|
|
}
|
|
|
|
ksort($gaps);
|
|
|
|
return [
|
|
'stats' => $stats,
|
|
'gaps' => $gaps,
|
|
'resume_token' => $resumeTokenOut,
|
|
];
|
|
}
|
|
}
|
|
|