import json from datetime import datetime from typing import Optional, List, Union, Any, Dict from tldextract import extract from werkzeug.datastructures import FileStorage from app.brm.brn import BRN from app.brm.utils import thumbnail_uploaded_image, create_data_uri, normalize_color from app.extensions import db from app.models import AbstractConfiguration, AbstractResource from app.models.onions import Onion class Origin(AbstractConfiguration): group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=False) domain_name = db.Column(db.String(255), unique=True, nullable=False) auto_rotation = db.Column(db.Boolean, nullable=False) smart = db.Column(db.Boolean(), nullable=False) assets = db.Column(db.Boolean(), nullable=False) group = db.relationship("Group", back_populates="origins") proxies = db.relationship("Proxy", back_populates="origin") @property def brn(self) -> BRN: return BRN( group_id=self.group_id, product="mirror", provider="conf", resource_type="origin", resource_id=self.domain_name ) @classmethod def csv_header(cls) -> List[str]: return super().csv_header() + [ "group_id", "domain_name", "auto_rotation", "smart" ] def destroy(self) -> None: super().destroy() for proxy in self.proxies: proxy.destroy() def onion(self) -> Optional[str]: tld = extract(self.domain_name).registered_domain onion = Onion.query.filter(Onion.domain_name == tld).first() if not onion: return None domain_name: str = self.domain_name return f"https://{domain_name.replace(tld, onion.onion_name)}.onion" class StaticOrigin(AbstractConfiguration): group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=False) storage_cloud_account_id = db.Column(db.Integer(), db.ForeignKey("cloud_account.id"), nullable=False) source_cloud_account_id = db.Column(db.Integer(), db.ForeignKey("cloud_account.id"), nullable=False) source_project = db.Column(db.String(255), nullable=False) auto_rotate = db.Column(db.Boolean, nullable=False) matrix_homeserver = db.Column(db.String(255), nullable=True) keanu_convene_path = db.Column(db.String(255), nullable=True) keanu_convene_config = db.Column(db.String(), nullable=True) clean_insights_backend = db.Column(db.String(255), nullable=True) origin_domain_name = db.Column(db.String(255), nullable=True) @property def brn(self) -> BRN: return BRN( group_id=self.group_id, product="mirror", provider="aws", resource_type="static", resource_id=self.domain_name ) group = db.relationship("Group", back_populates="statics") storage_cloud_account = db.relationship("CloudAccount", back_populates="statics", foreign_keys=[storage_cloud_account_id]) source_cloud_account = db.relationship("CloudAccount", back_populates="statics", foreign_keys=[source_cloud_account_id]) def destroy(self) -> None: # TODO: The StaticMetaAutomation will clean up for now, but it should probably happen here for consistency super().destroy() def update( self, source_project: str, description: str, auto_rotate: bool, matrix_homeserver: Optional[str], keanu_convene_path: Optional[str], keanu_convene_logo: Optional[FileStorage], keanu_convene_color: Optional[str], clean_insights_backend: Optional[Union[str, bool]], db_session_commit: bool, ) -> None: if isinstance(source_project, str): self.source_project = source_project else: raise ValueError("source project must be a str") if isinstance(description, str): self.description = description else: raise ValueError("description must be a str") if isinstance(auto_rotate, bool): self.auto_rotate = auto_rotate else: raise ValueError("auto_rotate must be a bool") if isinstance(matrix_homeserver, str): self.matrix_homeserver = matrix_homeserver else: raise ValueError("matrix_homeserver must be a str") if isinstance(keanu_convene_path, str): self.keanu_convene_path = keanu_convene_path else: raise ValueError("keanu_convene_path must be a str") if self.keanu_convene_config is None: self.keanu_convene_config = "{}" keanu_convene_config: Dict[str, Any] = json.loads(self.keanu_convene_config) if keanu_convene_logo is None: pass elif isinstance(keanu_convene_logo, FileStorage): if keanu_convene_logo.filename: # if False, no file was uploaded keanu_convene_config["logo"] = create_data_uri( thumbnail_uploaded_image(keanu_convene_logo), keanu_convene_logo.filename) else: raise ValueError("keanu_convene_logo must be a FileStorage") try: if isinstance(keanu_convene_color, str): keanu_convene_config["color"] = normalize_color(keanu_convene_color) # can raise ValueError else: raise ValueError() # re-raised below with message except ValueError: raise ValueError("keanu_convene_path must be a str containing an HTML color (CSS name or hex)") self.keanu_convene_config = json.dumps(keanu_convene_config, separators=(',', ':')) del keanu_convene_config # done with this temporary variable if clean_insights_backend is None or (isinstance(clean_insights_backend, bool) and not clean_insights_backend): self.clean_insights_backend = None elif isinstance(clean_insights_backend, bool) and clean_insights_backend: self.clean_insights_backend = "metrics.cleaninsights.org" elif isinstance(clean_insights_backend, str): self.clean_insights_backend = clean_insights_backend else: raise ValueError("clean_insights_backend must be a str, bool, or None") if db_session_commit: db.session.commit() self.updated = datetime.utcnow() class Proxy(AbstractResource): origin_id = db.Column(db.Integer, db.ForeignKey("origin.id"), nullable=False) pool_id = db.Column(db.Integer, db.ForeignKey("pool.id")) provider = db.Column(db.String(20), nullable=False) psg = db.Column(db.Integer, nullable=True) slug = db.Column(db.String(20), nullable=True) terraform_updated = db.Column(db.DateTime(), nullable=True) url = db.Column(db.String(255), nullable=True) origin = db.relationship("Origin", back_populates="proxies") pool = db.relationship("Pool", back_populates="proxies") @property def brn(self) -> BRN: return BRN( group_id=self.origin.group_id, product="mirror", provider=self.provider, resource_type="proxy", resource_id=str(self.id) ) @classmethod def csv_header(cls) -> List[str]: return super().csv_header() + [ "origin_id", "provider", "psg", "slug", "terraform_updated", "url" ] class SmartProxy(AbstractResource): group_id = db.Column(db.Integer(), db.ForeignKey("group.id"), nullable=False) instance_id = db.Column(db.String(100), nullable=True) provider = db.Column(db.String(20), nullable=False) region = db.Column(db.String(20), nullable=False) group = db.relationship("Group", back_populates="smart_proxies") @property def brn(self) -> BRN: return BRN( group_id=self.group_id, product="mirror", provider=self.provider, resource_type="smart_proxy", resource_id=str(1) )