From 39bdac1ecf9e5f206821fc7af863f0c00e8044b6 Mon Sep 17 00:00:00 2001 From: irl Date: Fri, 6 Dec 2024 18:02:59 +0000 Subject: [PATCH] feat: use custom type to handle existing naive datetimes --- app/__init__.py | 15 ++-- app/api/onion.py | 12 +-- app/api/util.py | 4 +- app/api/web.py | 5 +- app/brm/brn.py | 1 + app/cli/db.py | 12 +-- app/cli/list.py | 4 +- app/extensions.py | 2 +- app/lists/__init__.py | 2 +- app/lists/bridgelines.py | 1 + app/lists/redirector.py | 3 +- app/models/__init__.py | 17 ++-- app/models/activity.py | 3 +- app/models/alarms.py | 5 +- app/models/automation.py | 5 +- app/models/bridges.py | 3 +- app/models/cloud.py | 3 +- app/models/mirrors.py | 3 +- app/models/onions.py | 3 +- app/models/types.py | 21 +++++ app/portal/bridge.py | 3 +- app/portal/cloud.py | 7 +- app/portal/eotk.py | 2 +- app/portal/forms.py | 2 +- app/portal/onion.py | 2 +- app/portal/pool.py | 9 +- app/portal/proxy.py | 3 +- app/portal/smart_proxy.py | 2 +- app/portal/static.py | 10 ++- app/portal/util.py | 2 +- app/terraform/__init__.py | 4 +- app/terraform/alarms/eotk_aws.py | 4 +- app/terraform/alarms/proxy_cloudfront.py | 2 +- app/terraform/alarms/smart_aws.py | 4 +- app/terraform/block/bridge_github.py | 3 +- app/terraform/block/bridge_gitlab.py | 3 +- app/terraform/block_roskomsvoboda.py | 2 +- app/terraform/list/__init__.py | 4 +- app/terraform/proxy/__init__.py | 6 +- app/terraform/proxy/azure_cdn.py | 2 +- app/terraform/proxy/meta.py | 1 + app/tfstate.py | 2 +- app/util/onion.py | 2 +- app/util/x509.py | 4 +- .../cb3d6f0cdb86_switch_to_timezone_aware.py | 85 +++++++++++++++++++ 45 files changed, 210 insertions(+), 84 deletions(-) create mode 100644 app/models/types.py create mode 100644 migrations/versions/cb3d6f0cdb86_switch_to_timezone_aware.py diff --git a/app/__init__.py b/app/__init__.py index 5c83406..dea99a2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,24 +3,23 @@ import sys from typing import Iterator import yaml -from flask import Flask, redirect, url_for, send_from_directory +from flask import Flask, redirect, send_from_directory, url_for from flask.typing import ResponseReturnValue -from prometheus_client import make_wsgi_app, Metric, CollectorRegistry -from prometheus_client.metrics_core import GaugeMetricFamily, CounterMetricFamily -from prometheus_client.registry import Collector, REGISTRY +from prometheus_client import CollectorRegistry, Metric, make_wsgi_app +from prometheus_client.metrics_core import (CounterMetricFamily, + GaugeMetricFamily) +from prometheus_client.registry import REGISTRY, Collector +from prometheus_flask_exporter import PrometheusMetrics from sqlalchemy import text from sqlalchemy.exc import SQLAlchemyError from werkzeug.middleware.dispatcher import DispatcherMiddleware from app.api import api -from app.extensions import bootstrap -from app.extensions import db -from app.extensions import migrate +from app.extensions import bootstrap, db, migrate from app.models.automation import Automation, AutomationState from app.portal import portal from app.portal.report import report from app.tfstate import tfstate -from prometheus_flask_exporter import PrometheusMetrics app = Flask(__name__) app.config.from_file("../config.yaml", load=yaml.safe_load) diff --git a/app/api/onion.py b/app/api/onion.py index 0092821..c284161 100644 --- a/app/api/onion.py +++ b/app/api/onion.py @@ -1,18 +1,20 @@ import sys from datetime import datetime, timezone -from typing import List, TypedDict, NotRequired, Optional +from typing import List, NotRequired, Optional, TypedDict from cryptography import x509 -from flask import request, abort, jsonify, Blueprint +from flask import Blueprint, abort, jsonify, request from flask.typing import ResponseReturnValue from sqlalchemy import exc +from app.api.util import (DOMAIN_NAME_REGEX, MAX_ALLOWED_ITEMS, + MAX_DOMAIN_NAME_LENGTH, ListFilter, + get_single_resource, list_resources, + validate_description) from app.extensions import db -from app.api.util import ListFilter, MAX_DOMAIN_NAME_LENGTH, DOMAIN_NAME_REGEX, list_resources, MAX_ALLOWED_ITEMS, \ - validate_description, get_single_resource from app.models.base import Group from app.models.onions import Onion -from app.util.onion import onion_hostname, decode_onion_keys +from app.util.onion import decode_onion_keys, onion_hostname from app.util.x509 import validate_tls_keys api_onion = Blueprint('api_onion', __name__) diff --git a/app/api/util.py b/app/api/util.py index 2d659d9..fd43a1b 100644 --- a/app/api/util.py +++ b/app/api/util.py @@ -2,9 +2,9 @@ import base64 import binascii import logging import re -from typing import Union, Any, Literal, Type, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union -from flask import abort, request, jsonify +from flask import abort, jsonify, request from flask.typing import ResponseReturnValue from sqlalchemy import BinaryExpression, ColumnElement, select diff --git a/app/api/web.py b/app/api/web.py index adc48a1..09ecfaa 100644 --- a/app/api/web.py +++ b/app/api/web.py @@ -1,10 +1,11 @@ from datetime import datetime, timedelta, timezone from typing import List -from flask import Blueprint, request, abort +from flask import Blueprint, abort, request from flask.typing import ResponseReturnValue -from app.api.util import ListFilter, MAX_DOMAIN_NAME_LENGTH, DOMAIN_NAME_REGEX, list_resources, MAX_ALLOWED_ITEMS +from app.api.util import (DOMAIN_NAME_REGEX, MAX_ALLOWED_ITEMS, + MAX_DOMAIN_NAME_LENGTH, ListFilter, list_resources) from app.models.base import Group from app.models.mirrors import Origin, Proxy diff --git a/app/brm/brn.py b/app/brm/brn.py index 9f0c88b..f9a167f 100644 --- a/app/brm/brn.py +++ b/app/brm/brn.py @@ -3,6 +3,7 @@ Bypass Censorship Resource Names. """ from __future__ import annotations + from dataclasses import dataclass, field from typing import Any diff --git a/app/cli/db.py b/app/cli/db.py index 3de3e8e..c4f518f 100644 --- a/app/cli/db.py +++ b/app/cli/db.py @@ -9,15 +9,15 @@ from typing import Any, Callable, Dict, List, Type from sqlalchemy import inspect from app import app -from app.cli import _SubparserType, BaseCliHandler +from app.cli import BaseCliHandler, _SubparserType from app.extensions import db -from app.models.activity import Webhook, Activity -from app.models.automation import AutomationLogs, Automation, AutomationState -from app.models.base import Group, MirrorList, PoolGroup, Pool +from app.models.activity import Activity, Webhook +from app.models.alarms import Alarm, AlarmState +from app.models.automation import Automation, AutomationLogs, AutomationState +from app.models.base import Group, MirrorList, Pool, PoolGroup from app.models.bridges import Bridge, BridgeConf from app.models.mirrors import Origin, Proxy, SmartProxy -from app.models.alarms import Alarm, AlarmState -from app.models.onions import Onion, Eotk +from app.models.onions import Eotk, Onion from app.models.tfstate import TerraformState Model = Type[db.Model] # type: ignore[name-defined] diff --git a/app/cli/list.py b/app/cli/list.py index 07ca1b6..fad43dc 100644 --- a/app/cli/list.py +++ b/app/cli/list.py @@ -1,10 +1,10 @@ import json import logging import sys -from typing import Callable, Any +from typing import Any, Callable from app import app -from app.cli import _SubparserType, BaseCliHandler +from app.cli import BaseCliHandler, _SubparserType from app.lists import lists from app.models.base import Pool diff --git a/app/extensions.py b/app/extensions.py index 1653fdd..257b535 100644 --- a/app/extensions.py +++ b/app/extensions.py @@ -1,6 +1,6 @@ +from flask_bootstrap import Bootstrap5 from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy -from flask_bootstrap import Bootstrap5 from sqlalchemy import MetaData convention = { diff --git a/app/lists/__init__.py b/app/lists/__init__.py index c1784cc..3146aee 100644 --- a/app/lists/__init__.py +++ b/app/lists/__init__.py @@ -1,4 +1,4 @@ -from typing import Dict, Callable, Any +from typing import Any, Callable, Dict from app.lists.bc2 import mirror_sites from app.lists.bridgelines import bridgelines diff --git a/app/lists/bridgelines.py b/app/lists/bridgelines.py index 556bc4a..98de134 100644 --- a/app/lists/bridgelines.py +++ b/app/lists/bridgelines.py @@ -1,4 +1,5 @@ from typing import List, Optional, TypedDict + from sqlalchemy.orm import selectinload from app.models.base import Pool diff --git a/app/lists/redirector.py b/app/lists/redirector.py index ae7cacd..263ec3e 100644 --- a/app/lists/redirector.py +++ b/app/lists/redirector.py @@ -1,4 +1,5 @@ -from typing import List, Dict, Optional, TypedDict +from typing import Dict, List, Optional, TypedDict + from sqlalchemy.orm import selectinload from app.models.base import Pool diff --git a/app/models/__init__.py b/app/models/__init__.py index c967bec..73207ad 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column from app.brm.brn import BRN from app.extensions import db +from app.models.types import AwareDateTime class AbstractConfiguration(db.Model): # type: ignore @@ -14,9 +15,9 @@ class AbstractConfiguration(db.Model): # type: ignore id: Mapped[int] = mapped_column(db.Integer, primary_key=True) description: Mapped[str] - added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) - updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) - destroyed: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True) + added: Mapped[datetime] = mapped_column(AwareDateTime()) + updated: Mapped[datetime] = mapped_column(AwareDateTime()) + destroyed: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True) @property @abstractmethod @@ -43,7 +44,7 @@ class Deprecation(db.Model): # type: ignore[name-defined,misc] id: Mapped[int] = mapped_column(db.Integer, primary_key=True) resource_type: Mapped[str] resource_id: Mapped[int] - deprecated_at: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) + deprecated_at: Mapped[datetime] = mapped_column(AwareDateTime()) meta: Mapped[Optional[Dict[str, Any]]] = mapped_column(db.JSON()) reason: Mapped[str] @@ -58,11 +59,11 @@ class AbstractResource(db.Model): # type: ignore __abstract__ = True id: Mapped[int] = mapped_column(db.Integer, primary_key=True) - added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) - updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) - deprecated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True) + added: Mapped[datetime] = mapped_column(AwareDateTime()) + updated: Mapped[datetime] = mapped_column(AwareDateTime()) + deprecated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True) deprecation_reason: Mapped[Optional[str]] - destroyed: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True) + destroyed: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True) def __init__(self, *, id: Optional[int] = None, diff --git a/app/models/activity.py b/app/models/activity.py index 3ba8eb7..0f3cb1f 100644 --- a/app/models/activity.py +++ b/app/models/activity.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column from app.brm.brn import BRN from app.extensions import db from app.models import AbstractConfiguration +from app.models.types import AwareDateTime class Activity(db.Model): # type: ignore @@ -14,7 +15,7 @@ class Activity(db.Model): # type: ignore group_id: Mapped[Optional[int]] activity_type: Mapped[str] text: Mapped[str] - added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) + added: Mapped[datetime] = mapped_column(AwareDateTime()) def __init__(self, *, id: Optional[int] = None, diff --git a/app/models/alarms.py b/app/models/alarms.py index 88714e7..2f89f40 100644 --- a/app/models/alarms.py +++ b/app/models/alarms.py @@ -6,6 +6,7 @@ from sqlalchemy.orm import Mapped, mapped_column from app.extensions import db from app.models.activity import Activity +from app.models.types import AwareDateTime class AlarmState(enum.Enum): @@ -30,8 +31,8 @@ class Alarm(db.Model): # type: ignore target: Mapped[str] aspect: Mapped[str] alarm_state: Mapped[AlarmState] = mapped_column(default=AlarmState.UNKNOWN) - state_changed: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) - last_updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) + state_changed: Mapped[datetime] = mapped_column(AwareDateTime()) + last_updated: Mapped[datetime] = mapped_column(AwareDateTime()) text: Mapped[str] @classmethod diff --git a/app/models/automation.py b/app/models/automation.py index 99e5387..c050118 100644 --- a/app/models/automation.py +++ b/app/models/automation.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column from app.brm.brn import BRN from app.extensions import db from app.models import AbstractConfiguration, AbstractResource +from app.models.types import AwareDateTime class AutomationState(enum.Enum): @@ -19,8 +20,8 @@ class Automation(AbstractConfiguration): short_name: Mapped[str] state: Mapped[AutomationState] = mapped_column(default=AutomationState.IDLE) enabled: Mapped[bool] - last_run: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True)) - next_run: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True)) + last_run: Mapped[Optional[datetime]] = mapped_column(AwareDateTime()) + next_run: Mapped[Optional[datetime]] = mapped_column(AwareDateTime()) next_is_full: Mapped[bool] logs = db.relationship("AutomationLogs", back_populates="automation") diff --git a/app/models/bridges.py b/app/models/bridges.py index 979eecf..b2e538d 100644 --- a/app/models/bridges.py +++ b/app/models/bridges.py @@ -8,6 +8,7 @@ from app.brm.brn import BRN from app.extensions import db from app.models import AbstractConfiguration, AbstractResource from app.models.base import Pool +from app.models.types import AwareDateTime class ProviderAllocation(enum.Enum): @@ -54,7 +55,7 @@ class BridgeConf(AbstractConfiguration): class Bridge(AbstractResource): conf_id: Mapped[int] = mapped_column(db.ForeignKey("bridge_conf.id")) cloud_account_id: Mapped[int] = mapped_column(db.ForeignKey("cloud_account.id")) - terraform_updated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True) + terraform_updated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True) nickname: Mapped[Optional[str]] fingerprint: Mapped[Optional[str]] hashed_fingerprint: Mapped[Optional[str]] diff --git a/app/models/cloud.py b/app/models/cloud.py index f3aec7d..d10101e 100644 --- a/app/models/cloud.py +++ b/app/models/cloud.py @@ -1,5 +1,5 @@ import enum -from typing import Any, Dict, List, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -8,7 +8,6 @@ from app.extensions import db from app.models import AbstractConfiguration from app.models.mirrors import StaticOrigin - if TYPE_CHECKING: from app.models.bridges import Bridge diff --git a/app/models/mirrors.py b/app/models/mirrors.py index b213d54..8e0d64c 100644 --- a/app/models/mirrors.py +++ b/app/models/mirrors.py @@ -16,6 +16,7 @@ from app.extensions import db from app.models import AbstractConfiguration, AbstractResource, Deprecation from app.models.base import Group, Pool from app.models.onions import Onion +from app.models.types import AwareDateTime country_origin = db.Table( 'country_origin', @@ -275,7 +276,7 @@ class Proxy(AbstractResource): provider: Mapped[str] = mapped_column(db.String(20), nullable=False) psg: Mapped[Optional[int]] = mapped_column(db.Integer, nullable=True) slug: Mapped[Optional[str]] = mapped_column(db.String(20), nullable=True) - terraform_updated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True) + terraform_updated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True) url: Mapped[Optional[str]] = mapped_column(db.String(255), nullable=True) origin: Mapped[Origin] = relationship("Origin", back_populates="proxies") diff --git a/app/models/onions.py b/app/models/onions.py index 897f6f2..7efd601 100644 --- a/app/models/onions.py +++ b/app/models/onions.py @@ -7,6 +7,7 @@ from app.brm.brn import BRN from app.extensions import db from app.models import AbstractConfiguration, AbstractResource from app.models.base import Group +from app.models.types import AwareDateTime from app.util.onion import onion_hostname @@ -36,7 +37,7 @@ class Onion(AbstractConfiguration): group_id: Mapped[int] = mapped_column(db.ForeignKey("group.id")) domain_name: Mapped[str] - cert_expiry: Mapped[datetime] = mapped_column(db.DateTime(timezone=True)) + cert_expiry: Mapped[datetime] = mapped_column(AwareDateTime()) cert_sans: Mapped[str] onion_public_key: Mapped[bytes] onion_private_key: Mapped[bytes] diff --git a/app/models/types.py b/app/models/types.py new file mode 100644 index 0000000..7bb978d --- /dev/null +++ b/app/models/types.py @@ -0,0 +1,21 @@ +from datetime import timezone + +from sqlalchemy import DateTime, TypeDecorator + + +class AwareDateTime(TypeDecorator): + impl = DateTime(timezone=True) + + cache_ok = True + + def process_bind_param(self, value, dialect): + # Ensure the value is aware. If it's naive, assume UTC. + if value is not None and value.tzinfo is None: + value = value.replace(tzinfo=timezone.utc) + return value + + def process_result_value(self, value, dialect): + # Ensure the value is aware. If it's naive, assume UTC. + if value is not None and value.tzinfo is None: + value = value.replace(tzinfo=timezone.utc) + return value diff --git a/app/portal/bridge.py b/app/portal/bridge.py index d4fd278..5ac7013 100644 --- a/app/portal/bridge.py +++ b/app/portal/bridge.py @@ -1,6 +1,7 @@ from typing import Optional -from flask import render_template, Response, flash, redirect, url_for, Blueprint +from flask import (Blueprint, Response, flash, redirect, render_template, + url_for) from flask.typing import ResponseReturnValue from app.extensions import db diff --git a/app/portal/cloud.py b/app/portal/cloud.py index 90858c7..06d9767 100644 --- a/app/portal/cloud.py +++ b/app/portal/cloud.py @@ -1,9 +1,10 @@ -from typing import List, Union, Optional, Dict, Type +from typing import Dict, List, Optional, Type, Union -from flask import render_template, url_for, redirect, Blueprint +from flask import Blueprint, redirect, render_template, url_for from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm -from wtforms import SelectField, StringField, SubmitField, IntegerField, BooleanField, Form, FormField +from wtforms import (BooleanField, Form, FormField, IntegerField, SelectField, + StringField, SubmitField) from wtforms.validators import InputRequired from app.extensions import db diff --git a/app/portal/eotk.py b/app/portal/eotk.py index 79f08ee..ad40f5e 100644 --- a/app/portal/eotk.py +++ b/app/portal/eotk.py @@ -1,4 +1,4 @@ -from flask import current_app, render_template, Blueprint, Response +from flask import Blueprint, Response, current_app, render_template from flask.typing import ResponseReturnValue from sqlalchemy import desc diff --git a/app/portal/forms.py b/app/portal/forms.py index 0f053d7..2d52d33 100644 --- a/app/portal/forms.py +++ b/app/portal/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField, SelectField +from wtforms import SelectField, StringField, SubmitField class EditMirrorForm(FlaskForm): # type: ignore diff --git a/app/portal/onion.py b/app/portal/onion.py index e2b4271..fa8f759 100644 --- a/app/portal/onion.py +++ b/app/portal/onion.py @@ -1,6 +1,6 @@ from typing import Optional -from flask import redirect, Blueprint +from flask import Blueprint, redirect from flask.typing import ResponseReturnValue from app.models.onions import Onion diff --git a/app/portal/pool.py b/app/portal/pool.py index 1184488..665ff11 100644 --- a/app/portal/pool.py +++ b/app/portal/pool.py @@ -2,15 +2,16 @@ import logging import secrets from datetime import datetime, timezone -from flask import render_template, url_for, flash, redirect, Response, Blueprint +import sqlalchemy +from flask import (Blueprint, Response, flash, redirect, render_template, + url_for) from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm -import sqlalchemy -from wtforms import StringField, SubmitField, SelectField +from wtforms import SelectField, StringField, SubmitField from wtforms.validators import DataRequired from app.extensions import db -from app.models.base import Pool, Group +from app.models.base import Group, Pool from app.portal.util import LifecycleForm bp = Blueprint("pool", __name__) diff --git a/app/portal/proxy.py b/app/portal/proxy.py index a234dd6..1efda70 100644 --- a/app/portal/proxy.py +++ b/app/portal/proxy.py @@ -1,4 +1,5 @@ -from flask import render_template, Response, flash, redirect, url_for, Blueprint +from flask import (Blueprint, Response, flash, redirect, render_template, + url_for) from flask.typing import ResponseReturnValue from sqlalchemy import desc diff --git a/app/portal/smart_proxy.py b/app/portal/smart_proxy.py index d744e66..bfa7b15 100644 --- a/app/portal/smart_proxy.py +++ b/app/portal/smart_proxy.py @@ -1,4 +1,4 @@ -from flask import render_template, Blueprint +from flask import Blueprint, render_template from flask.typing import ResponseReturnValue from sqlalchemy import desc diff --git a/app/portal/static.py b/app/portal/static.py index dd82c35..3bf5b45 100644 --- a/app/portal/static.py +++ b/app/portal/static.py @@ -1,18 +1,20 @@ import logging -from typing import Optional, List, Any +from typing import Any, List, Optional import sqlalchemy.exc -from flask import flash, redirect, url_for, render_template, Response, Blueprint, current_app +from flask import (Blueprint, Response, current_app, flash, redirect, + render_template, url_for) from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm from sqlalchemy import exc -from wtforms import StringField, SelectField, SubmitField, BooleanField, FileField +from wtforms import (BooleanField, FileField, SelectField, StringField, + SubmitField) from wtforms.validators import DataRequired from app.brm.static import create_static_origin from app.models.base import Group from app.models.cloud import CloudAccount, CloudProvider -from app.models.mirrors import StaticOrigin, Origin +from app.models.mirrors import Origin, StaticOrigin from app.portal.util import response_404, view_lifecycle bp = Blueprint("static", __name__) diff --git a/app/portal/util.py b/app/portal/util.py index 0d40c0d..58c27ac 100644 --- a/app/portal/util.py +++ b/app/portal/util.py @@ -1,4 +1,4 @@ -from flask import Response, render_template, flash, redirect, url_for +from flask import Response, flash, redirect, render_template, url_for from flask.typing import ResponseReturnValue from flask_wtf import FlaskForm from wtforms import SubmitField diff --git a/app/terraform/__init__.py b/app/terraform/__init__.py index ce6d75b..09be884 100644 --- a/app/terraform/__init__.py +++ b/app/terraform/__init__.py @@ -1,7 +1,7 @@ import os import stat -from typing import Tuple, Any, Optional -from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED +from typing import Any, Optional, Tuple +from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo import jinja2 diff --git a/app/terraform/alarms/eotk_aws.py b/app/terraform/alarms/eotk_aws.py index 99f69b7..c5bc3aa 100644 --- a/app/terraform/alarms/eotk_aws.py +++ b/app/terraform/alarms/eotk_aws.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional +from typing import Optional, Tuple import boto3 from sqlalchemy import func @@ -6,8 +6,8 @@ from sqlalchemy import func from app import app from app.alarms import get_or_create_alarm from app.extensions import db -from app.models.base import Group from app.models.alarms import AlarmState +from app.models.base import Group from app.models.onions import Eotk from app.terraform import BaseAutomation diff --git a/app/terraform/alarms/proxy_cloudfront.py b/app/terraform/alarms/proxy_cloudfront.py index 92ac05f..f91ce40 100644 --- a/app/terraform/alarms/proxy_cloudfront.py +++ b/app/terraform/alarms/proxy_cloudfront.py @@ -7,8 +7,8 @@ from app import app from app.alarms import get_or_create_alarm from app.brm.brn import BRN from app.extensions import db -from app.models.mirrors import Proxy from app.models.alarms import AlarmState +from app.models.mirrors import Proxy from app.terraform import BaseAutomation diff --git a/app/terraform/alarms/smart_aws.py b/app/terraform/alarms/smart_aws.py index 99f7c6d..94737cc 100644 --- a/app/terraform/alarms/smart_aws.py +++ b/app/terraform/alarms/smart_aws.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional +from typing import Optional, Tuple import boto3 from sqlalchemy import func @@ -6,8 +6,8 @@ from sqlalchemy import func from app import app from app.alarms import get_or_create_alarm from app.extensions import db -from app.models.base import Group from app.models.alarms import AlarmState +from app.models.base import Group from app.models.mirrors import SmartProxy from app.terraform import BaseAutomation diff --git a/app/terraform/block/bridge_github.py b/app/terraform/block/bridge_github.py index 8a8c928..c1528da 100644 --- a/app/terraform/block/bridge_github.py +++ b/app/terraform/block/bridge_github.py @@ -1,7 +1,8 @@ from flask import current_app from github import Github -from app.terraform.block.bridge_reachability import BlockBridgeReachabilityAutomation +from app.terraform.block.bridge_reachability import \ + BlockBridgeReachabilityAutomation class BlockBridgeGitHubAutomation(BlockBridgeReachabilityAutomation): diff --git a/app/terraform/block/bridge_gitlab.py b/app/terraform/block/bridge_gitlab.py index aca08db..f1c1590 100644 --- a/app/terraform/block/bridge_gitlab.py +++ b/app/terraform/block/bridge_gitlab.py @@ -1,7 +1,8 @@ from flask import current_app from gitlab import Gitlab -from app.terraform.block.bridge_reachability import BlockBridgeReachabilityAutomation +from app.terraform.block.bridge_reachability import \ + BlockBridgeReachabilityAutomation class BlockBridgeGitlabAutomation(BlockBridgeReachabilityAutomation): diff --git a/app/terraform/block_roskomsvoboda.py b/app/terraform/block_roskomsvoboda.py index 0333c18..0346b95 100644 --- a/app/terraform/block_roskomsvoboda.py +++ b/app/terraform/block_roskomsvoboda.py @@ -2,7 +2,7 @@ import json import logging from io import BytesIO from typing import Any, Optional -from zipfile import ZipFile, BadZipFile +from zipfile import BadZipFile, ZipFile import lxml # nosec: B410 import requests diff --git a/app/terraform/list/__init__.py b/app/terraform/list/__init__.py index e4699b7..50c500f 100644 --- a/app/terraform/list/__init__.py +++ b/app/terraform/list/__init__.py @@ -1,7 +1,7 @@ -from collections.abc import Mapping, Sequence import json import os -from typing import List, Any +from collections.abc import Mapping, Sequence +from typing import Any, List from app import app from app.lists import lists diff --git a/app/terraform/proxy/__init__.py b/app/terraform/proxy/__init__.py index ae8b0fc..34c66d8 100644 --- a/app/terraform/proxy/__init__.py +++ b/app/terraform/proxy/__init__.py @@ -1,9 +1,9 @@ +import datetime import os.path import sys from abc import abstractmethod -import datetime from collections import defaultdict -from typing import Optional, Any, List, Dict +from typing import Any, Dict, List, Optional from flask import current_app from sqlalchemy import text @@ -11,7 +11,7 @@ from sqlalchemy import text from app import app from app.extensions import db from app.models.base import Group -from app.models.mirrors import Proxy, Origin, SmartProxy +from app.models.mirrors import Origin, Proxy, SmartProxy from app.terraform.terraform import TerraformAutomation diff --git a/app/terraform/proxy/azure_cdn.py b/app/terraform/proxy/azure_cdn.py index 55097f5..b580aa5 100644 --- a/app/terraform/proxy/azure_cdn.py +++ b/app/terraform/proxy/azure_cdn.py @@ -1,4 +1,4 @@ -from typing import Optional, Any +from typing import Any, Optional from app.extensions import db from app.models.mirrors import Proxy diff --git a/app/terraform/proxy/meta.py b/app/terraform/proxy/meta.py index 602322f..55c8f36 100644 --- a/app/terraform/proxy/meta.py +++ b/app/terraform/proxy/meta.py @@ -235,6 +235,7 @@ class ProxyMetaAutomation(BaseAutomation): """ pools = Pool.query.all() for pool in pools: + logging.debug(pool.added < datetime.now(tz=timezone.utc)) if pool.id == -1: continue # Skip hotspare pool logging.debug("Missing proxy check for %s", pool.pool_name) diff --git a/app/tfstate.py b/app/tfstate.py index 5c657f5..5a6028f 100644 --- a/app/tfstate.py +++ b/app/tfstate.py @@ -1,6 +1,6 @@ import json -from flask import Blueprint, request, Response +from flask import Blueprint, Response, request from flask.typing import ResponseReturnValue from app.extensions import db diff --git a/app/util/onion.py b/app/util/onion.py index 527972d..3c180aa 100644 --- a/app/util/onion.py +++ b/app/util/onion.py @@ -1,6 +1,6 @@ import base64 import hashlib -from typing import Tuple, Optional, List, Dict +from typing import Dict, List, Optional, Tuple def onion_hostname(onion_public_key: bytes) -> str: diff --git a/app/util/x509.py b/app/util/x509.py index 0813a50..cd005d4 100644 --- a/app/util/x509.py +++ b/app/util/x509.py @@ -1,12 +1,12 @@ import ssl from datetime import datetime, timezone -from typing import Optional, Tuple, List, Dict, TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple from cryptography import x509 from cryptography.hazmat._oid import ExtensionOID from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey diff --git a/migrations/versions/cb3d6f0cdb86_switch_to_timezone_aware.py b/migrations/versions/cb3d6f0cdb86_switch_to_timezone_aware.py new file mode 100644 index 0000000..8c3693e --- /dev/null +++ b/migrations/versions/cb3d6f0cdb86_switch_to_timezone_aware.py @@ -0,0 +1,85 @@ +"""switch to timezone aware + +Revision ID: cb3d6f0cdb86 +Revises: 54b31e87fe33 +Create Date: 2024-12-06 17:34:51.630311 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cb3d6f0cdb86' +down_revision = '54b31e87fe33' +branch_labels = None +depends_on = None + + +def upgrade(): + def alter_column_to_timezone_aware(table_name, column_name): + with op.batch_alter_table(table_name, schema=None) as batch_op: + batch_op.alter_column( + column_name, + type_=sa.DateTime(timezone=True) + ) + + # AbstractConfiguration derived tables + configuration_tables = [ + "automation", + "cloud_account", + "bridge_conf", + "country", + "group", + "mirror_list", + "onion", + "origin", + "pool", + "static_origin", + "webhook" + ] + for t in configuration_tables: + alter_column_to_timezone_aware(t, 'added') + alter_column_to_timezone_aware(t, 'updated') + alter_column_to_timezone_aware(t, 'destroyed') + + # AbstractResource derived tables + resource_tables = [ + "bridge", + "proxy", + "smart_proxy", + "automation_logs", + "eotk" + ] + for t in resource_tables: + alter_column_to_timezone_aware(t, 'added') + alter_column_to_timezone_aware(t, 'updated') + alter_column_to_timezone_aware(t, 'deprecated') + alter_column_to_timezone_aware(t, 'destroyed') + + # Deprecation + alter_column_to_timezone_aware("deprecation", "deprecated_at") + + # Activity + alter_column_to_timezone_aware("activity", "added") + + # Alarm + alter_column_to_timezone_aware("alarm", "state_changed") + alter_column_to_timezone_aware("alarm", "last_updated") + + # Bridge terraform_updated + alter_column_to_timezone_aware("bridge", "terraform_updated") + + # Proxy terraform_updated + alter_column_to_timezone_aware("proxy", "terraform_updated") + + # Automation last_run, next_run + alter_column_to_timezone_aware("automation", "last_run") + alter_column_to_timezone_aware("automation", "next_run") + + # Onion cert_expiry + alter_column_to_timezone_aware("onion", "cert_expiry") + + +def downgrade(): + pass