115 lines
3.9 KiB
PHP
115 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\TenantConfiguration;
|
|
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Support\TenantConfiguration\ClaimState;
|
|
use App\Support\TenantConfiguration\EvidenceState;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
use App\Support\TenantConfiguration\SourceClass;
|
|
use InvalidArgumentException;
|
|
|
|
final class CoverageResourceUpserter
|
|
{
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @param array<string, mixed> $sourceMetadata
|
|
*/
|
|
public function upsert(
|
|
ManagedEnvironment $tenant,
|
|
ProviderConnection $providerConnection,
|
|
TenantConfigurationResourceType $resourceType,
|
|
array $payload,
|
|
array $sourceMetadata = [],
|
|
): TenantConfigurationResource {
|
|
$this->assertScoped($tenant, $providerConnection);
|
|
|
|
$sourceResourceId = $this->extractSourceResourceId($payload);
|
|
$canonicalType = (string) $resourceType->canonical_type;
|
|
$canonicalResourceKey = sprintf('%s:%s', $canonicalType, $sourceResourceId);
|
|
$sourceClass = $resourceType->source_class instanceof SourceClass
|
|
? $resourceType->source_class->value
|
|
: (string) $resourceType->source_class;
|
|
|
|
$resource = TenantConfigurationResource::query()->firstOrNew([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $providerConnection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'canonical_resource_key' => $canonicalResourceKey,
|
|
]);
|
|
|
|
$resource->fill([
|
|
'source_class' => $sourceClass,
|
|
'canonical_type' => $canonicalType,
|
|
'source_resource_id' => $sourceResourceId,
|
|
'source_display_name' => $this->extractDisplayName($payload),
|
|
'source_metadata' => $sourceMetadata,
|
|
]);
|
|
|
|
if (! $resource->exists) {
|
|
$resource->forceFill([
|
|
'latest_evidence_state' => EvidenceState::NotCaptured->value,
|
|
'latest_identity_state' => IdentityState::Stable->value,
|
|
'latest_claim_state' => ClaimState::InternalOnly->value,
|
|
]);
|
|
}
|
|
|
|
$resource->save();
|
|
|
|
return $resource;
|
|
}
|
|
|
|
private function assertScoped(ManagedEnvironment $tenant, ProviderConnection $providerConnection): void
|
|
{
|
|
if ((int) $providerConnection->managed_environment_id !== (int) $tenant->getKey()) {
|
|
throw new InvalidArgumentException('Provider connection does not belong to the managed environment.');
|
|
}
|
|
|
|
if ((int) $providerConnection->workspace_id !== (int) $tenant->workspace_id) {
|
|
throw new InvalidArgumentException('Provider connection does not belong to the managed environment workspace.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
private function extractSourceResourceId(array $payload): string
|
|
{
|
|
$id = $payload['id'] ?? $payload['sourceId'] ?? null;
|
|
|
|
if (! is_scalar($id)) {
|
|
throw new InvalidArgumentException('Captured resource payload must include a stable source id.');
|
|
}
|
|
|
|
$id = trim((string) $id);
|
|
|
|
if ($id === '') {
|
|
throw new InvalidArgumentException('Captured resource payload must include a non-empty source id.');
|
|
}
|
|
|
|
return $id;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
private function extractDisplayName(array $payload): ?string
|
|
{
|
|
$displayName = $payload['displayName'] ?? $payload['name'] ?? null;
|
|
|
|
if (! is_scalar($displayName)) {
|
|
return null;
|
|
}
|
|
|
|
$displayName = trim((string) $displayName);
|
|
|
|
return $displayName !== '' ? $displayName : null;
|
|
}
|
|
}
|