From 16f7e2199dcadc89da7a7ef34d0ae346a3abbf37 Mon Sep 17 00:00:00 2001 From: Iain Learmonth Date: Mon, 26 Sep 2022 13:40:59 +0100 Subject: [PATCH] resource pool system --- app/cli/automate.py | 4 +- app/models/base.py | 23 +++++ app/models/mirrors.py | 2 + app/portal/__init__.py | 2 + app/portal/automation.py | 1 + app/portal/forms.py | 5 -- app/portal/list.py | 13 ++- app/portal/pool.py | 78 +++++++++++++++++ app/portal/templates/base.html.j2 | 6 ++ app/portal/templates/icons.html.j2 | 5 ++ app/portal/templates/list.html.j2 | 6 +- app/portal/templates/pool.html.j2 | 21 +++++ app/portal/templates/tables.html.j2 | 23 +++++ app/terraform/proxy/__init__.py | 123 ++++++++++----------------- app/terraform/proxy/azure_cdn.py | 11 +-- app/terraform/proxy/fastly.py | 1 + app/terraform/proxy/lib.py | 12 +-- app/terraform/proxy/meta.py | 90 ++++++++++++++++++++ migrations/versions/45fedef32318_.py | 61 +++++++++++++ 19 files changed, 382 insertions(+), 105 deletions(-) create mode 100644 app/portal/pool.py create mode 100644 app/portal/templates/pool.html.j2 create mode 100644 app/terraform/proxy/meta.py create mode 100644 migrations/versions/45fedef32318_.py diff --git a/app/cli/automate.py b/app/cli/automate.py index d0038dc..3bf4ee0 100644 --- a/app/cli/automate.py +++ b/app/cli/automate.py @@ -28,6 +28,7 @@ from app.terraform.bridge.ovh import BridgeOvhAutomation from app.terraform.list.github import ListGithubAutomation from app.terraform.list.gitlab import ListGitlabAutomation from app.terraform.list.s3 import ListS3Automation +from app.terraform.proxy.meta import ProxyMetaAutomation from app.terraform.proxy.azure_cdn import ProxyAzureCdnAutomation from app.terraform.proxy.cloudfront import ProxyCloudfrontAutomation from app.terraform.proxy.fastly import ProxyFastlyAutomation @@ -56,7 +57,8 @@ jobs = { ListS3Automation, ProxyAzureCdnAutomation, ProxyCloudfrontAutomation, - ProxyFastlyAutomation + ProxyFastlyAutomation, + ProxyMetaAutomation ] } diff --git a/app/models/base.py b/app/models/base.py index d7fb5b6..72b0b6a 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -14,6 +14,7 @@ class Group(AbstractConfiguration): eotks = db.relationship("Eotk", back_populates="group") onions = db.relationship("Onion", back_populates="group") smart_proxies = db.relationship("SmartProxy", back_populates="group") + pools = db.relationship("Pool", secondary="pool_group", back_populates="groups") @classmethod def csv_header(cls) -> List[str]: @@ -22,7 +23,27 @@ class Group(AbstractConfiguration): ] +class Pool(AbstractConfiguration): + pool_name = db.Column(db.String(80), unique=True, nullable=False) + + @classmethod + def csv_header(cls) -> List[str]: + return super().csv_header() + [ + "pool_name" + ] + + proxies = db.relationship("Proxy", back_populates="pool") + lists = db.relationship("MirrorList", back_populates="pool") + groups = db.relationship("Group", secondary="pool_group", back_populates="pools") + + +class PoolGroup(db.Model): # type: ignore[misc] + pool_id = db.Column(db.Integer, db.ForeignKey("pool.id"), primary_key=True) + group_id = db.Column(db.Integer, db.ForeignKey("group.id"), primary_key=True) + + class MirrorList(AbstractConfiguration): + pool_id = db.Column(db.Integer, db.ForeignKey("pool.id")) provider = db.Column(db.String(255), nullable=False) format = db.Column(db.String(20), nullable=False) encoding = db.Column(db.String(20), nullable=False) @@ -31,6 +52,8 @@ class MirrorList(AbstractConfiguration): role = db.Column(db.String(255), nullable=True) filename = db.Column(db.String(255), nullable=False) + pool = db.relationship("Pool", back_populates="lists") + providers_supported = { "github": "GitHub", "gitlab": "GitLab", diff --git a/app/models/mirrors.py b/app/models/mirrors.py index 022d80f..669c224 100644 --- a/app/models/mirrors.py +++ b/app/models/mirrors.py @@ -50,6 +50,7 @@ class Origin(AbstractConfiguration): 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) @@ -57,6 +58,7 @@ class Proxy(AbstractResource): 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: diff --git a/app/portal/__init__.py b/app/portal/__init__.py index 719e36b..aff3ff1 100644 --- a/app/portal/__init__.py +++ b/app/portal/__init__.py @@ -21,6 +21,7 @@ from app.portal.group import bp as group from app.portal.list import bp as list_ from app.portal.origin import bp as origin from app.portal.onion import bp as onion +from app.portal.pool import bp as pool from app.portal.proxy import bp as proxy from app.portal.smart_proxy import bp as smart_proxy from app.portal.webhook import bp as webhook @@ -34,6 +35,7 @@ portal.register_blueprint(group, url_prefix="/group") portal.register_blueprint(list_, url_prefix="/list") portal.register_blueprint(origin, url_prefix="/origin") portal.register_blueprint(onion, url_prefix="/onion") +portal.register_blueprint(pool, url_prefix="/pool") portal.register_blueprint(proxy, url_prefix="/proxy") portal.register_blueprint(smart_proxy, url_prefix="/smart") portal.register_blueprint(webhook, url_prefix="/webhook") diff --git a/app/portal/automation.py b/app/portal/automation.py index abb7d01..4f60a2c 100644 --- a/app/portal/automation.py +++ b/app/portal/automation.py @@ -74,6 +74,7 @@ def automation_kick(automation_id: int) -> ResponseReturnValue: return view_lifecycle( header="Kick automation timer?", message=automation.description, + section="automation", success_view="portal.automation.automation_list", success_message="This automation job will next run within 1 minute.", resource=automation, diff --git a/app/portal/forms.py b/app/portal/forms.py index e2a6d6f..0f053d7 100644 --- a/app/portal/forms.py +++ b/app/portal/forms.py @@ -6,8 +6,3 @@ class EditMirrorForm(FlaskForm): # type: ignore origin = SelectField('Origin') url = StringField('URL') submit = SubmitField('Save Changes') - - -class EditProxyForm(FlaskForm): # type: ignore - origin = SelectField('Origin') - submit = SubmitField('Save Changes') diff --git a/app/portal/list.py b/app/portal/list.py index 77e7cb7..4475323 100644 --- a/app/portal/list.py +++ b/app/portal/list.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Optional +from typing import Optional, Any from flask import render_template, url_for, flash, redirect, Blueprint, Response from flask.typing import ResponseReturnValue @@ -13,7 +13,7 @@ from app.extensions import db from app.lists.bc2 import mirror_sites from app.lists.bridgelines import bridgelines from app.lists.mirror_mapping import mirror_mapping -from app.models.base import MirrorList +from app.models.base import MirrorList, Pool from app.portal.util import response_404, view_lifecycle bp = Blueprint("list", __name__) @@ -96,6 +96,7 @@ def list_new(group_id: Optional[int] = None) -> ResponseReturnValue: form.encoding.choices = list(MirrorList.encodings_supported.items()) if form.validate_on_submit(): list_ = MirrorList() + list_.pool_id = form.pool.data list_.provider = form.provider.data list_.format = form.format.data list_.encoding = form.encoding.data @@ -122,6 +123,7 @@ def list_new(group_id: Optional[int] = None) -> ResponseReturnValue: class NewMirrorListForm(FlaskForm): # type: ignore + pool = SelectField('Resource Pool', validators=[DataRequired()]) provider = SelectField('Provider', validators=[DataRequired()]) format = SelectField('Distribution Method', validators=[DataRequired()]) encoding = SelectField('Encoding', validators=[DataRequired()]) @@ -136,6 +138,12 @@ class NewMirrorListForm(FlaskForm): # type: ignore filename = StringField('Filename', validators=[DataRequired()]) submit = SubmitField('Save Changes') + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.pool.choices = [ + (pool.id, pool.pool_name) for pool in Pool.query.all() + ] + @bp.route('/edit/', methods=['GET', 'POST']) def list_edit(list_id: int) -> ResponseReturnValue: @@ -160,6 +168,7 @@ def list_edit(list_id: int) -> ResponseReturnValue: form.format.choices = list(MirrorList.formats_supported.items()) form.encoding.choices = list(MirrorList.encodings_supported.items()) if form.validate_on_submit(): + list_.pool_id = form.pool.data list_.provider = form.provider.data list_.format = form.format.data list_.encoding = form.encoding.data diff --git a/app/portal/pool.py b/app/portal/pool.py new file mode 100644 index 0000000..b2c9422 --- /dev/null +++ b/app/portal/pool.py @@ -0,0 +1,78 @@ +from datetime import datetime + +from flask import render_template, url_for, flash, redirect, Response, Blueprint +from flask.typing import ResponseReturnValue +from flask_wtf import FlaskForm +import sqlalchemy +from wtforms import StringField, SubmitField +from wtforms.validators import DataRequired + +from app.extensions import db +from app.models.base import Pool + +bp = Blueprint("pool", __name__) + + +class NewPoolForm(FlaskForm): # type: ignore + group_name = StringField("Short Name", validators=[DataRequired()]) + description = StringField("Description", validators=[DataRequired()]) + submit = SubmitField('Save Changes', render_kw={"class": "btn btn-success"}) + + +class EditPoolForm(FlaskForm): # type: ignore + description = StringField("Description", validators=[DataRequired()]) + submit = SubmitField('Save Changes', render_kw={"class": "btn btn-success"}) + + +@bp.route("/list") +def pool_list() -> ResponseReturnValue: + pools = Pool.query.order_by(Pool.pool_name).all() + return render_template("list.html.j2", + section="pool", + title="Resource Pools", + item="pool", + items=pools, + new_link=url_for("portal.pool.pool_new")) + + +@bp.route("/new", methods=['GET', 'POST']) +def pool_new() -> ResponseReturnValue: + form = NewPoolForm() + if form.validate_on_submit(): + pool = Pool() + pool.pool_name = form.group_name.data + pool.description = form.description.data + pool.created = datetime.utcnow() + pool.updated = datetime.utcnow() + try: + db.session.add(pool) + db.session.commit() + flash(f"Created new pool {pool.pool_name}.", "success") + return redirect(url_for("portal.pool.pool_edit", pool_id=pool.id)) + except sqlalchemy.exc.SQLAlchemyError: + flash("Failed to create new pool.", "danger") + return redirect(url_for("portal.pool.pool_list")) + return render_template("new.html.j2", section="pool", form=form) + + +@bp.route('/edit/', methods=['GET', 'POST']) +def pool_edit(pool_id: int) -> ResponseReturnValue: + pool = Pool.query.filter(Pool.id == pool_id).first() + if pool is None: + return Response(render_template("error.html.j2", + section="pool", + header="404 Pool Not Found", + message="The requested pool could not be found."), + status=404) + form = EditPoolForm(description=pool.description) + if form.validate_on_submit(): + pool.description = form.description.data + pool.updated = datetime.utcnow() + try: + db.session.commit() + flash("Saved changes to pool.", "success") + except sqlalchemy.exc.SQLAlchemyError: + flash("An error occurred saving the changes to the pool.", "danger") + return render_template("pool.html.j2", + section="pool", + pool=pool, form=form) diff --git a/app/portal/templates/base.html.j2 b/app/portal/templates/base.html.j2 index 91688c2..6215318 100644 --- a/app/portal/templates/base.html.j2 +++ b/app/portal/templates/base.html.j2 @@ -87,6 +87,12 @@ {{ icon("collection") }} Groups +