From c3712622f1c23eae0edeae20c74b3f3e0dc4ec83 Mon Sep 17 00:00:00 2001 From: Patrick Valsecchi Date: Thu, 30 Mar 2017 14:25:13 +0200 Subject: [PATCH] Add version information to the applications --- Makefile | 7 ++- README.md | 1 + acceptance_tests/app/Dockerfile | 4 ++ acceptance_tests/tests/tests/test_versions.py | 6 +++ c2cwsgiutils/pyramid.py | 3 +- c2cwsgiutils/sql_profiler.py | 13 +++-- c2cwsgiutils/version.py | 15 ++++++ c2cwsgiutils_genversion.py | 54 +++++++++++++++++++ setup.py | 5 +- 9 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 acceptance_tests/tests/tests/test_versions.py create mode 100644 c2cwsgiutils/version.py create mode 100755 c2cwsgiutils_genversion.py diff --git a/Makefile b/Makefile index 6d8e2f767..b78385c38 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ else DOCKER_COMPOSE_VERSION := $(DOCKER_COMPOSE_VERSION_ACTUAL) endif +GIT_TAG := $(shell git describe --tags --first-parent 2>/dev/null || echo "none") +GIT_HASH := $(shell git rev-parse HEAD) + .PHONY: all all: acceptance @@ -40,8 +43,8 @@ build_acceptance: .PHONY: build_test_app build_test_app: - rsync -a c2cwsgiutils c2cwsgiutils_run rel_requirements.txt setup.cfg acceptance_tests/app/ - docker build -t $(DOCKER_BASE)_test_app:$(DOCKER_TAG) acceptance_tests/app + rsync -a c2cwsgiutils c2cwsgiutils_run c2cwsgiutils_genversion.py rel_requirements.txt setup.cfg acceptance_tests/app/ + docker build -t $(DOCKER_BASE)_test_app:$(DOCKER_TAG) --build-arg "GIT_TAG=$(GIT_TAG)" --build-arg "GIT_HASH=$(GIT_HASH)" acceptance_tests/app .venv/timestamp: rel_requirements.txt dev_requirements.txt /usr/bin/virtualenv --python=/usr/bin/python3.5 .venv diff --git a/README.md b/README.md index 3278a79dd..28ede712c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ applications: * SQL profiler to debug DB performance problems, disabled by default. You must enable it by setting the SQL_PROFILER_SECRET env var and using the view (/sql_profiler) to switch it ON and OFF. Warning, it will slow down everything. +* A view to get the version information about the application and the installed packages (/versions.json) * Error handlers to send JSON messages to the client in case of error * A cornice service drop in replacement for setting up CORS diff --git a/acceptance_tests/app/Dockerfile b/acceptance_tests/app/Dockerfile index 85b12df6b..aa948d3d3 100644 --- a/acceptance_tests/app/Dockerfile +++ b/acceptance_tests/app/Dockerfile @@ -23,9 +23,13 @@ ENV SQLALCHEMY_URL_SLAVE postgresql://www-data:www-data@db_slave:5432/test # Step #2 copy the rest of the files (watch for the .dockerignore) COPY . /app +ARG GIT_TAG +ARG GIT_HASH + RUN python ./setup.py install && \ ./models_graph.py > models.dot && \ ./models_graph.py Hello > models_hello.dot && \ + ./c2cwsgiutils_genversion.py $GIT_TAG $GIT_HASH && \ flake8 c2cwsgiutils c2cwsgiutils_app CMD ["./c2cwsgiutils_run"] diff --git a/acceptance_tests/tests/tests/test_versions.py b/acceptance_tests/tests/tests/test_versions.py new file mode 100644 index 000000000..34b9c389a --- /dev/null +++ b/acceptance_tests/tests/tests/test_versions.py @@ -0,0 +1,6 @@ +def test_ok(app_connection): + response = app_connection.get_json('versions.json') + assert 'main' in response + assert 'git_hash' in response['main'] + assert 'packages' in response + assert 'pyramid' in response['packages'] diff --git a/c2cwsgiutils/pyramid.py b/c2cwsgiutils/pyramid.py index 7f5c260fb..90907a81d 100644 --- a/c2cwsgiutils/pyramid.py +++ b/c2cwsgiutils/pyramid.py @@ -1,7 +1,7 @@ import cornice import pyramid_tm -from c2cwsgiutils import stats, pyramid_logging, sql_profiler +from c2cwsgiutils import stats, pyramid_logging, sql_profiler, version def includeme(config): @@ -15,5 +15,6 @@ def includeme(config): stats.init(config) pyramid_logging.install_subscriber(config) sql_profiler.init(config) + version.init(config) config.scan("c2cwsgiutils.services") config.scan("c2cwsgiutils.errors") diff --git a/c2cwsgiutils/sql_profiler.py b/c2cwsgiutils/sql_profiler.py index 027c0fd20..0eb3098c7 100644 --- a/c2cwsgiutils/sql_profiler.py +++ b/c2cwsgiutils/sql_profiler.py @@ -52,11 +52,14 @@ def _indent(statement, indent=' '): def _before_cursor_execute(conn, _cursor, statement, parameters, _context, _executemany): if statement.startswith("SELECT ") and LOG.isEnabledFor(logging.INFO): - output = "statement:\n%s\nparameters: %s\nplan:\n " % (_indent(_beautify_sql(statement)), - repr(parameters)) - output += '\n '.join([row[0] for row in conn.engine.execute("EXPLAIN ANALYZE " + statement, - parameters)]) - LOG.info(output) + try: + output = "statement:\n%s\nparameters: %s\nplan:\n " % (_indent(_beautify_sql(statement)), + repr(parameters)) + output += '\n '.join([row[0] for row in conn.engine.execute("EXPLAIN ANALYZE " + statement, + parameters)]) + LOG.info(output) + except: + pass def init(config): diff --git a/c2cwsgiutils/version.py b/c2cwsgiutils/version.py new file mode 100644 index 000000000..d76897519 --- /dev/null +++ b/c2cwsgiutils/version.py @@ -0,0 +1,15 @@ +import logging +import json +import os + +VERSIONS_PATH = '/app/versions.json' +LOG = logging.getLogger(__name__) + + +def init(config): + if os.path.isfile(VERSIONS_PATH): + with open(VERSIONS_PATH) as file: + versions = json.load(file) + config.add_route("c2c_versions", r"/versions.json", request_method="GET") + config.add_view(lambda request: versions, route_name="c2c_versions", renderer="json", http_cache=0) + LOG.info("Installed the /versions.json service") diff --git a/c2cwsgiutils_genversion.py b/c2cwsgiutils_genversion.py new file mode 100755 index 000000000..174212b0e --- /dev/null +++ b/c2cwsgiutils_genversion.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import json +import os +import re +import subprocess +import sys + +VERSION_RE = re.compile(r'^(.*)==(.*)$') + + +def _get_git_versions(root): + git_hash = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root).strip() + try: + with open(os.devnull, "w") as devnull: + git_tag = subprocess.check_output(["git", "describe", "--tags", "--first-parent"], + stderr=devnull, cwd=root).strip() + git_tag = re.sub(r"-g[a-f0-9]+$", "", git_tag) + except: + git_tag = None + return { + "git_hash": git_hash, + "git_tag": git_tag + } + + +def _get_packages_version(): + result = {} + with open(os.devnull, "w") as devnull: + for comp in subprocess.check_output(["pip3", "freeze"], stderr=devnull).decode().strip().split('\n'): + matcher = VERSION_RE.match(comp) + if matcher: + name, version = matcher.groups() + result[name] = version + else: + print("Cannot parse pacakge version: " + comp) + return result + + +def main(): + git_tag = sys.argv[1] + git_hash = sys.argv[2] + report = { + 'main': { + "git_hash": git_hash, + "git_tag": git_tag + }, + 'packages': _get_packages_version() + } + with open('versions.json', 'w') as file: + json.dump(report, file, indent=2) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index a12761884..37e966c26 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = '0.7.0' +VERSION = '0.8.0' HERE = os.path.abspath(os.path.dirname(__file__)) INSTALL_REQUIRES = open(os.path.join(HERE, 'rel_requirements.txt')).read().splitlines() @@ -35,6 +35,7 @@ ] }, scripts=[ - 'c2cwsgiutils_run' + 'c2cwsgiutils_run', + 'c2cwsgiutils_genversion.py' ] )