"""Processes a single file for the AttachAndDetach JobType""" import csv import logging import os from uuid import UUID from backfill.connectors.ows_pdp.models.attach_detach_roles_request import ( AttachDetachRolesRequest, ) from backfill.connectors.ows_pdp.models.role import Role from backfill.connectors.ows_pdp.ows_pdp import OwsPdpClient from backfill.constants import OPERATION_ATTACH from backfill.job_processors import MissingFileException from backfill.models import AttachAndDetachRow logger = logging.getLogger(__name__) class AttachAndDetachProcessor: """Loads and applies a file for an AttachAndDetach JobType.""" def __init__(self) -> None: """Init method.""" pass def process( self, csv_filepath: str, ows_pdp_client: OwsPdpClient, backfill_uuid: str, ) -> None: """Load the csv file and apply desired changes.""" identities = self._load_csv(csv_filepath, backfill_uuid=backfill_uuid) self._apply_changes( identities, csv_filepath, ows_pdp_client, backfill_uuid=backfill_uuid ) def _load_csv( self, csv_filepath: str, backfill_uuid: str, ) -> dict[str, dict[str, AttachDetachRolesRequest]]: if not os.path.isfile(csv_filepath): logger.error( "Missing file", extra={ "resources": { "csv_file": csv_filepath, }, "correlation_id": backfill_uuid, }, ) raise MissingFileException("missing file %s", csv_filepath) identities: dict[str, dict[str, AttachDetachRolesRequest]] = {} with open(csv_filepath, newline="", encoding="utf-8-sig") as csvfile: reader = csv.DictReader(csvfile) for row in reader: validated_row = AttachAndDetachRow.model_validate(row) identity_uuid = str(validated_row.identity_uuid) tenant_uuid = str(validated_row.tenant_uuid) identity_requests = identities.get(identity_uuid, {}) attach_detach_roles_request = identity_requests.get( tenant_uuid, AttachDetachRolesRequest( tenant_uuid=tenant_uuid, tenant_type=validated_row.tenant_type, roles_to_attach=[], roles_to_detach=[], ), ) if validated_row.operation == OPERATION_ATTACH: attach_detach_roles_request.roles_to_attach.append( Role(role=validated_row.role) ) else: attach_detach_roles_request.roles_to_detach.append( Role(role=validated_row.role) ) # Update the Identities dictionary for the Identity's Tenant Uuid if identity_uuid not in identities: identities[identity_uuid] = {} identities[identity_uuid][tenant_uuid] = attach_detach_roles_request logger.info( "Loaded identity from csv", extra={ "resources": { "csv_file": csv_filepath, "identity_uuid": identity_uuid, "tenant_uuid": tenant_uuid, "role": validated_row.role, "operation": validated_row.operation.name, }, "correlation_id": backfill_uuid, }, ) return identities def _apply_changes( self, identities: dict[str, dict[str, AttachDetachRolesRequest]], csv_filepath: str, ows_pdp_client: OwsPdpClient, backfill_uuid: str, ) -> None: """Apply the changes based from loading the csv.""" if not identities: logger.info( "No changes to apply.", extra={ "resources": { "csv_file": csv_filepath, }, "correlation_id": backfill_uuid, }, ) return for identity_uuid, tenants in identities.items(): success = 0 fail = 0 for attach_detach_roles_request in tenants.values(): try: ows_pdp_client.attach_detach_roles_by_identity_tenant( UUID(identity_uuid), attach_detach_roles_request, ) except Exception as e: fail += 1 logger.error( "Changes not applied for identity+tenant. Reason: %s", e, extra={ "resources": { "csv_file": csv_filepath, "identity_uuid": identity_uuid, "tenant_uuid": str( attach_detach_roles_request.tenant_uuid ), }, "correlation_id": backfill_uuid, }, ) else: success += 1 logger.debug( "Changes applied for identity+tenant", extra={ "resources": { "csv_file": csv_filepath, "identity_uuid": identity_uuid, "tenant_uuid": str( attach_detach_roles_request.tenant_uuid ), }, "correlation_id": backfill_uuid, }, ) logger.info( "Changes applied for identity", extra={ "resources": { "csv_file": csv_filepath, "identity_uuid": identity_uuid, "summary": { "success": success, "fail": fail, }, }, "correlation_id": backfill_uuid, }, )