majuna/app/terraform/proxy/__init__.py

136 lines
4.8 KiB
Python
Raw Normal View History

2022-05-16 11:44:03 +01:00
from abc import abstractmethod
2022-04-25 15:05:28 +01:00
from collections import defaultdict
import datetime
import math
import string
import random
2022-05-16 11:44:03 +01:00
from typing import Dict, Optional, Any, List
2022-03-10 14:26:22 +00:00
2022-04-25 15:06:24 +01:00
from sqlalchemy import text
from tldextract import tldextract
2022-04-25 15:06:24 +01:00
2022-03-10 14:26:22 +00:00
from app import app
from app.extensions import db
2022-04-22 14:01:16 +01:00
from app.models.base import Group
from app.models.mirrors import Proxy
from app.terraform.terraform import TerraformAutomation
2022-03-10 14:26:22 +00:00
class ProxyAutomation(TerraformAutomation):
subgroup_max = math.inf
2022-05-16 11:44:03 +01:00
"""
Maximum number of proxies to deploy per sub-group. This is required for some providers
where the number origins per group may exceed the number of proxies that can be configured
in a single "configuration block", e.g. Azure CDN's profiles.
"""
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.
"""
2022-03-10 14:26:22 +00:00
def get_subgroups(self) -> Dict[int, Dict[int, int]]:
2022-04-25 15:05:28 +01:00
conn = db.engine.connect()
2022-04-25 15:06:24 +01:00
result = conn.execute(text("""
2022-04-25 15:05:28 +01:00
SELECT origin.group_id, proxy.psg, COUNT(proxy.id) FROM proxy, origin
WHERE proxy.origin_id = origin.id
AND proxy.destroyed IS NULL
AND proxy.provider = :provider
GROUP BY origin.group_id, proxy.psg;
2022-04-25 15:06:24 +01:00
"""), provider=self.provider)
2022-05-16 11:44:03 +01:00
subgroups: Dict[int, Dict[int, int]] = defaultdict(lambda: defaultdict(lambda: 0))
2022-04-25 15:05:28 +01:00
for row in result:
subgroups[row[0]][row[1]] = row[2]
return subgroups
2022-05-16 11:44:03 +01:00
def create_missing_proxies(self) -> None:
groups = Group.query.all()
subgroups = self.get_subgroups()
for group in groups:
subgroup = 0
for origin in group.origins:
if origin.destroyed is not None:
continue
while True:
if subgroups[group.id][subgroup] >= self.subgroup_max:
subgroup += 1
else:
break
proxies = [
x for x in origin.proxies
if x.provider == self.provider and x.deprecated is None and x.destroyed is None
]
if not proxies:
subgroups[group.id][subgroup] += 1
proxy = Proxy()
proxy.origin_id = origin.id
proxy.provider = self.provider
proxy.psg = subgroup
2022-05-16 12:47:40 +01:00
# The random usage below is good enough for its purpose: to create a slug that
# hasn't been used before.
proxy.slug = tldextract.extract(origin.domain_name).domain[:5] + ''.join(
2022-05-16 12:47:40 +01:00
random.choices(string.ascii_lowercase, k=12)) # nosec
proxy.added = datetime.datetime.utcnow()
proxy.updated = datetime.datetime.utcnow()
db.session.add(proxy)
db.session.commit()
2022-03-10 14:26:22 +00:00
2022-05-16 11:44:03 +01:00
def deprecate_orphaned_proxies(self) -> None:
proxies = Proxy.query.filter(
2022-05-16 13:29:48 +01:00
Proxy.deprecated.is_(None),
Proxy.destroyed.is_(None),
Proxy.provider == self.provider
).all()
for proxy in proxies:
if proxy.origin.destroyed is not None:
proxy.deprecate(reason="origin_destroyed")
db.session.commit()
2022-05-16 11:44:03 +01:00
def destroy_expired_proxies(self) -> None:
2022-03-10 14:26:22 +00:00
cutoff = datetime.datetime.utcnow() - datetime.timedelta(days=3)
proxies = Proxy.query.filter(
2022-05-16 13:29:48 +01:00
Proxy.destroyed.is_(None),
2022-03-10 14:26:22 +00:00
Proxy.provider == self.provider,
Proxy.deprecated < cutoff
).all()
for proxy in proxies:
proxy.destroyed = datetime.datetime.utcnow()
proxy.updated = datetime.datetime.utcnow()
db.session.commit()
2022-05-16 11:44:03 +01:00
@abstractmethod
def import_state(self, state: Any) -> None:
raise NotImplementedError()
def tf_prehook(self) -> Optional[Any]:
self.create_missing_proxies()
self.deprecate_orphaned_proxies()
self.destroy_expired_proxies()
2022-05-16 11:44:03 +01:00
return None
2022-05-16 11:44:03 +01:00
def tf_posthook(self, *, prehook_result: Any = None) -> None:
self.import_state(self.tf_show())
2022-05-16 11:44:03 +01:00
def tf_generate(self) -> None:
self.tf_write(
2022-03-10 14:26:22 +00:00
self.template,
groups=Group.query.all(),
proxies=Proxy.query.filter(
Proxy.provider == self.provider,
2022-05-16 13:29:48 +01:00
Proxy.destroyed.is_(None)
2022-03-10 14:26:22 +00:00
).all(),
2022-05-01 16:23:45 +01:00
subgroups=self.get_subgroups(),
2022-03-10 14:26:22 +00:00
global_namespace=app.config['GLOBAL_NAMESPACE'],
2022-04-19 14:32:04 +01:00
bypass_token=app.config['BYPASS_TOKEN'],
2022-03-10 14:26:22 +00:00
**{
k: app.config[k.upper()]
for k in self.template_parameters
}
)