import os from datetime import datetime, timezone from typing import Any from app import app from app.extensions import db from app.models.base import Group from app.models.onions import Eotk from app.terraform import DeterministicZip from app.terraform.eotk import eotk_configuration from app.terraform.terraform import TerraformAutomation def update_eotk_instance(group_id: int, region: str, instance_id: str) -> None: instance = Eotk.query.filter( Eotk.group_id == group_id, Eotk.region == region, Eotk.provider == "aws", Eotk.destroyed.is_(None) ).first() if instance is None: instance = Eotk() instance.added = datetime.now(tz=timezone.utc) instance.group_id = group_id instance.provider = "aws" instance.region = region db.session.add(instance) instance.updated = datetime.now(tz=timezone.utc) instance.instance_id = instance_id class EotkAWSAutomation(TerraformAutomation): short_name = "eotk_aws" description = "Deploy EOTK instances to AWS" template_parameters = [ "aws_access_key", "aws_secret_key" ] template = """ terraform { {{ backend_config }} required_providers { aws = { version = "~> 4.4.0" } } } provider "aws" { access_key = "{{ aws_access_key }}" secret_key = "{{ aws_secret_key }}" region = "us-east-2" } {% for group in groups %} module "eotk_{{ group.id }}" { source = "{{ terraform_modules_path }}/terraform-aws-bc-eotk" namespace = "{{ global_namespace }}" tenant = "{{ group.group_name }}" name = "eotk" label_order = ["namespace", "tenant", "name", "attributes"] tags = { "Application" = "EOTK" } configuration_bundle = "{{ group.id }}.zip" } output "eotk_instances_{{ group.id }}" { value = module.eotk_{{ group.id }}.instances } {% endfor %} """ def tf_generate(self) -> None: if not self.working_dir: raise RuntimeError("No working directory specified.") self.tf_write( self.template, groups=Group.query.filter( Group.eotk.is_(True), Group.destroyed.is_(None) ).all(), 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 } ) for group in Group.query.filter( Group.eotk.is_(True), Group.destroyed.is_(None) ).order_by(Group.id).all(): with DeterministicZip(os.path.join(self.working_dir, f"{group.id}.zip")) as dzip: dzip.add_file("sites.conf", eotk_configuration(group).encode('utf-8')) for onion in sorted(group.onions, key=lambda o: o.onion_name): dzip.add_file(f"{onion.onion_name}.v3pub.key", onion.onion_public_key) dzip.add_file(f"{onion.onion_name}.v3sec.key", onion.onion_private_key) dzip.add_file(f"{onion.onion_name[:20]}-v3.cert", onion.tls_public_key) dzip.add_file(f"{onion.onion_name[:20]}-v3.pem", onion.tls_private_key) def tf_posthook(self, *, prehook_result: Any = None) -> None: for e in Eotk.query.all(): db.session.delete(e) outputs = self.tf_output() for output in outputs: if output.startswith("eotk_instances_"): try: group_id = int(output[len("eotk_instance_") + 1:]) for az in outputs[output]['value']: update_eotk_instance(group_id, az, outputs[output]['value'][az]) except ValueError: pass db.session.commit()