From affa0f0149ec5423656eb9c966974b3741e530db Mon Sep 17 00:00:00 2001 From: Iain Learmonth Date: Mon, 29 Aug 2022 19:16:35 +0100 Subject: [PATCH] tfstate: very basic terraform state backend in flask --- app/__init__.py | 2 + app/models/tfstate.py | 7 +++ app/tfstate.py | 51 +++++++++++++++++++ .../665e340dbe09_add_terraform_state.py | 28 ++++++++++ 4 files changed, 88 insertions(+) create mode 100644 app/models/tfstate.py create mode 100644 app/tfstate.py create mode 100644 migrations/versions/665e340dbe09_add_terraform_state.py diff --git a/app/__init__.py b/app/__init__.py index ada98d7..0bb8b95 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -6,6 +6,7 @@ from app.extensions import db from app.extensions import migrate from app.extensions import bootstrap from app.portal import portal +from app.tfstate import tfstate app = Flask(__name__) app.config.from_file("../config.yaml", load=yaml.safe_load) @@ -14,6 +15,7 @@ migrate.init_app(app, db, render_as_batch=True) bootstrap.init_app(app) app.register_blueprint(portal, url_prefix="/portal") +app.register_blueprint(tfstate, url_prefix="/tfstate") @app.route('/') diff --git a/app/models/tfstate.py b/app/models/tfstate.py new file mode 100644 index 0000000..62dbf9a --- /dev/null +++ b/app/models/tfstate.py @@ -0,0 +1,7 @@ +from app.extensions import db + + +class TerraformState(db.Model): + key = db.Column(db.String, primary_key=True) + state = db.Column(db.String) + lock = db.Column(db.String) diff --git a/app/tfstate.py b/app/tfstate.py new file mode 100644 index 0000000..bfa58dd --- /dev/null +++ b/app/tfstate.py @@ -0,0 +1,51 @@ +import json + +from flask import Blueprint, request, Response + +from app.extensions import db +from app.models.tfstate import TerraformState + +tfstate = Blueprint("tfstate", __name__) + + +@tfstate.route("/", methods=['GET']) +def handle_get(key): + state = TerraformState.query.filter(TerraformState.key == key).first() + if state is None or state.state is None: + return "Not Found", 404 + return Response(state.state, content_type="application/json") + + +@tfstate.route("/", methods=['POST', 'DELETE', 'UNLOCK']) +def handle_update(key): + state = TerraformState.query.filter(TerraformState.key == key).first() + if not state: + if request.method in ["DELETE", "UNLOCK"]: + return "OK", 200 + state = TerraformState(key=key) + if state.lock and not (request.method == "UNLOCK" and request.args.get('ID') is None): + if json.loads(state.lock)['ID'] != request.args.get('ID'): + return Response(state.lock, status=409, content_type="application/json") + if request.method == "POST": + state.state = json.dumps(request.json) + elif request.method == "DELETE": + db.session.delete(state) + elif request.method == "UNLOCK": + state.lock = None + db.session.commit() + return "OK", 200 + + +@tfstate.route("/", methods=['LOCK']) +def handle_lock(key): + state = TerraformState.query.filter(TerraformState.key == key).with_for_update().first() + if state is None: + state = TerraformState(key=key) + db.session.add(state) + if state.lock is not None: + lock = state.lock + db.session.rollback() + return Response(lock, status=423, content_type="application/json") + state.lock = json.dumps(request.json) + db.session.commit() + return "OK", 200 diff --git a/migrations/versions/665e340dbe09_add_terraform_state.py b/migrations/versions/665e340dbe09_add_terraform_state.py new file mode 100644 index 0000000..efd1247 --- /dev/null +++ b/migrations/versions/665e340dbe09_add_terraform_state.py @@ -0,0 +1,28 @@ +"""add terraform state + +Revision ID: 665e340dbe09 +Revises: c644bb20d0e3 +Create Date: 2022-08-29 17:10:05.447985 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '665e340dbe09' +down_revision = 'c644bb20d0e3' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('terraform_state', + sa.Column('key', sa.String(), nullable=False), + sa.Column('state', sa.String(), nullable=True), + sa.Column('lock', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('key', name=op.f('pk_terraform_state')) + ) + + +def downgrade(): + op.drop_table('terraform_state')