Skip to content

Commit

Permalink
Implement build_number_generator
Browse files Browse the repository at this point in the history
  • Loading branch information
Hartmnt committed Mar 28, 2023
1 parent 1611571 commit e284dd2
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 0 deletions.
15 changes: 15 additions & 0 deletions build_number_generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from flask import Flask
import os

app_path = os.path.dirname(__file__)
app = Flask(__name__, root_path=app_path, instance_path=os.path.join(app_path, "instance"), instance_relative_config=True)

app.config.from_pyfile("config.py")

app.app_context()

with app.app_context():
from . import database
database.init()

from .controller import build_number
95 changes: 95 additions & 0 deletions build_number_generator/controller/build_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from flask import Flask, jsonify, current_app, request
from build_number_generator.database import get_database

import hashlib

def hash_token(token: str) -> str:
digest = hashlib.blake2b(digest_size=64)
digest.update(str.encode(token))
return digest.hexdigest()


def fail(status_message: str, status_code: int = 400) -> tuple:
response = {}
response["status"] = status_code
response["message"] = status_message
return response, status_code


@current_app.errorhandler(405)
def method_not_allowed(e) -> tuple:
return fail("Method not allowed", 405)


@current_app.route("/build-number", methods=["POST"])
@current_app.route("/build-number/<string:series>", methods=["POST"])
@current_app.route("/build-number/<string:series>/<string:commit>", methods=["POST"])
def build_number(series=None, commit=None):

token = request.form.get("token")

if not token:
return fail("Unautherized", 401)

if not hash_token(token) in current_app.config["AUTHORIZED_HASHES"]:
return fail("Unautherized", 401)

if not series:
return fail("Required parameter not provided: series")

if series.count(".") != 1:
return fail("Invalid parameter: series. Format: <major>.<minor>")

major, minor = series.split(".")

if not major.isnumeric() or not minor.isnumeric():
return fail("Invalid parameter: series. Format: <major>.<minor>")

major = int(major)
minor = int(minor)

if not commit:
return fail("Required parameter not provided: commit hash")

if len(commit) < 8:
return fail("Invalid parameter: commit")

response = {}

db = get_database()

# Find or insert series
result = db.execute("SELECT * FROM series WHERE name='%i.%i'" % (major, minor))
row = result.fetchone()

if row is not None:
series_id = row["id"]
else:
cursor = db.execute("INSERT INTO series VALUES (NULL, '%i.%i', NULL)" % (major, minor))
series_id = cursor.lastrowid

# Find or insert build number
result = db.execute("SELECT * FROM build WHERE series_id=%i AND commit_hash='%s'" % (series_id, commit))
row = result.fetchone()

if row is not None:
build_number = row["build_number"]

response["message"] = "Known commit"
else:
result = db.execute("SELECT build_number FROM build WHERE series_id=%i ORDER BY build_number DESC LIMIT 1" % (series_id))
row = result.fetchone()
build_number = (row["build_number"] + 1) if row is not None else 1
cursor = db.execute("INSERT INTO build VALUES (NULL, %i, '%s', %i, NULL)" % (series_id, commit, build_number))

response["message"] = "New build number created"

db.commit()

response["stauts"] = 200
response["commit_hash"] = commit
response["series"] = "%i.%i" % (major, minor)
response["series_id"] = series_id
response["build_number"] = build_number

return response, 200
45 changes: 45 additions & 0 deletions build_number_generator/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sqlite3

import click
from flask import current_app, g


def get_database() -> sqlite3.Connection:
"""Gets the database connection for the current request. If this function is called for the first time for the given request,
this function also connects to the database, whereas later calls will simply reuse that connection"""
if "database" not in g:
g.database = sqlite3.connect(
current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES
)
g.database.row_factory = sqlite3.Row

return g.database


def close_database(_ = None) -> None:
"""If a connection to the database has been established, this function closes it again"""
database: sqlite3.Connection = g.pop("database", None)

if database is not None:
database.close()


def init_database() -> None:
"""This function (re)creates the database from scratch. If the database existed
before, its contents will be dropped"""
database: sqlite3.Connection = get_database()

with current_app.open_resource("schema.sql") as file:
database.executescript(file.read().decode("utf-8"))


@click.command("init-db")
def init_db_command() -> None:
"""Clear existing data and create new tables."""
init_database()
click.echo("Database initialized")


def init() -> None:
current_app.teardown_appcontext(close_database)
current_app.cli.add_command(init_db_command)
18 changes: 18 additions & 0 deletions build_number_generator/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
DROP TABLE IF EXISTS series;
DROP TABLE IF EXISTS build;

CREATE TABLE series (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_on DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE build (
id INTEGER PRIMARY KEY AUTOINCREMENT,
series_id INTEGER NOT NULL,
commit_hash TEXT NOT NULL,
build_number INTEGER NOT NULL,
created_on DEFAULT CURRENT_TIMESTAMP,
UNIQUE(series_id, commit_hash),
FOREIGN KEY (series_id) REFERENCES series (id)
);

0 comments on commit e284dd2

Please sign in to comment.