Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import boto3
|
||||
import dns.asyncresolver
|
||||
from botocore.config import Config
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
|
||||
from app.contracts import Attachment
|
||||
from app.file_pipeline import DependencyFailure, ObjectChanged
|
||||
from app.url_policy import DnsError, DnsNxDomain
|
||||
|
||||
|
||||
class TrustedDnsResolver:
|
||||
def __init__(self, nameservers: list[str]) -> None:
|
||||
self._resolver = dns.asyncresolver.Resolver(configure=not nameservers)
|
||||
if nameservers:
|
||||
self._resolver.nameservers = nameservers
|
||||
|
||||
async def resolve(
|
||||
self, hostname: str
|
||||
) -> tuple[ipaddress.IPv4Address | ipaddress.IPv6Address, ...]:
|
||||
found: list[ipaddress.IPv4Address | ipaddress.IPv6Address] = []
|
||||
try:
|
||||
for kind in ("A", "AAAA"):
|
||||
try:
|
||||
answer = await self._resolver.resolve(hostname, kind, lifetime=1.0)
|
||||
found.extend(ipaddress.ip_address(item.address) for item in answer)
|
||||
except dns.resolver.NoAnswer:
|
||||
pass
|
||||
except dns.resolver.NXDOMAIN as exc:
|
||||
raise DnsNxDomain from exc
|
||||
except dns.exception.DNSException as exc:
|
||||
raise DnsError from exc
|
||||
if not found:
|
||||
raise DnsNxDomain
|
||||
return tuple(found)
|
||||
|
||||
|
||||
class S3VersionReader:
|
||||
def __init__(self, endpoint_url: str, bucket: str, access_key: str, secret_key: str) -> None:
|
||||
self.bucket = bucket
|
||||
self.client = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=endpoint_url,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
config=Config(s3={"addressing_style": "virtual"}, retries={"max_attempts": 2}),
|
||||
)
|
||||
|
||||
async def stream(self, attachment: Attachment) -> AsyncIterator[bytes]:
|
||||
try:
|
||||
response = await asyncio.to_thread(
|
||||
self.client.get_object,
|
||||
Bucket=self.bucket,
|
||||
Key=attachment.quarantine_object_key,
|
||||
VersionId=attachment.quarantine_version_id,
|
||||
IfMatch=attachment.quarantine_etag,
|
||||
)
|
||||
body = response["Body"]
|
||||
while True:
|
||||
chunk = await asyncio.to_thread(body.read, 65_536)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
except ClientError as exc:
|
||||
code = exc.response.get("Error", {}).get("Code")
|
||||
if code in {"PreconditionFailed", "NoSuchKey", "NoSuchVersion"}:
|
||||
raise ObjectChanged("version or ETag changed") from exc
|
||||
raise DependencyFailure("S3 dependency unavailable") from exc
|
||||
except BotoCoreError as exc:
|
||||
raise DependencyFailure("S3 dependency unavailable") from exc
|
||||
Reference in New Issue
Block a user