majuna/app/terraform/bridge/__init__.py

118 lines
4.2 KiB
Python

import os
import sys
from datetime import datetime, timedelta, timezone
from typing import Any, List, Optional, Sequence, Tuple
from sqlalchemy import Row, select
from app import app
from app.extensions import db
from app.models import AbstractResource
from app.models.bridges import Bridge, BridgeConf
from app.models.cloud import CloudAccount, CloudProvider
from app.terraform.terraform import TerraformAutomation
BridgeResourceRow = Row[Tuple[AbstractResource, BridgeConf, CloudAccount]]
def active_bridges_by_provider(provider: CloudProvider) -> Sequence[BridgeResourceRow]:
stmt = (
select(Bridge, BridgeConf, CloudAccount)
.join_from(Bridge, BridgeConf)
.join_from(Bridge, CloudAccount)
.where(
CloudAccount.provider == provider,
Bridge.destroyed.is_(None),
)
)
bridges: Sequence[BridgeResourceRow] = db.session.execute(stmt).all()
return bridges
def recently_destroyed_bridges_by_provider(
provider: CloudProvider,
) -> Sequence[BridgeResourceRow]:
cutoff = datetime.now(tz=timezone.utc) - timedelta(hours=72)
stmt = (
select(Bridge, BridgeConf, CloudAccount)
.join_from(Bridge, BridgeConf)
.join_from(Bridge, CloudAccount)
.where(
CloudAccount.provider == provider,
Bridge.destroyed.is_not(None),
Bridge.destroyed >= cutoff,
)
)
bridges: Sequence[BridgeResourceRow] = db.session.execute(stmt).all()
return bridges
class BridgeAutomation(TerraformAutomation):
template: str
"""
Terraform configuration template using Jinja 2.
"""
template_parameters: List[str]
"""
List of parameters to be read from the application configuration for use
in the templating of the Terraform configuration.
"""
max_bridges = sys.maxsize
# TODO: Only enable providers that have details configured
enabled = True
def tf_prehook(self) -> Optional[Any]: # pylint: disable=useless-return
return None
def tf_generate(self) -> None:
self.tf_write(
self.template,
active_resources=active_bridges_by_provider(self.provider),
destroyed_resources=recently_destroyed_bridges_by_provider(self.provider),
global_namespace=app.config["GLOBAL_NAMESPACE"],
terraform_modules_path=os.path.join(
*list(os.path.split(app.root_path))[:-1], "terraform-modules"
),
backend_config=f"""backend "http" {{
lock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
unlock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
}}""",
**{k: app.config[k.upper()] for k in self.template_parameters},
)
def tf_posthook(self, *, prehook_result: Any = None) -> None:
outputs = self.tf_output()
for output in outputs:
if output.startswith("bridge_hashed_fingerprint_"):
parts = outputs[output]["value"].split(" ")
if len(parts) < 2:
continue
bridge = Bridge.query.filter(
Bridge.id == output[len("bridge_hashed_fingerprint_") :]
).first()
bridge.nickname = parts[0]
bridge.hashed_fingerprint = parts[1]
bridge.terraform_updated = datetime.now(tz=timezone.utc)
if output.startswith("bridge_bridgeline_"):
parts = outputs[output]["value"].split(" ")
if len(parts) < 4:
continue
bridge = Bridge.query.filter(
Bridge.id == output[len("bridge_bridgeline_") :]
).first()
del parts[3]
bridge.bridgeline = " ".join(parts)
bridge.terraform_updated = datetime.now(tz=timezone.utc)
db.session.commit()
@classmethod
def active_bridges_count(self) -> int:
active_bridges = Bridge.query.filter(
Bridge.provider == self.provider,
Bridge.destroyed.is_(None),
).all()
return len(active_bridges)