Skip to content
This repository was archived by the owner on Apr 12, 2025. It is now read-only.

Commit 9bce9d3

Browse files
committed
Merge branch 'refactor' into 'master'
Code refactor See merge request ix.ai/csp!6
2 parents 0e6db96 + e636b49 commit 9bce9d3

17 files changed

+315
-108
lines changed

.gitlab-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
---
12
variables:
23
DOCKERHUB_REPO_NAME: csp
34
ENABLE_ARM64: 'true'
45
ENABLE_ARMv7: 'true'
56
ENABLE_ARMv6: 'true'
7+
ENABLE_386: 'true'
68

79
include:
810
- project: 'ix.ai/ci-templates'

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ confidence=
6060
# --enable=similarities". If you want to run only the classes checker, but have
6161
# no Warning level messages displayed, use "--disable=all --enable=classes
6262
# --disable=W".
63-
disable=logging-format-interpolation,
63+
disable=logging-fstring-interpolation,
6464
# too-few-public-methods,
6565
invalid-name,
6666
no-self-use,

Dockerfile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ FROM alpine:latest
22
LABEL maintainer="docker@ix.ai" \
33
ai.ix.repository="ix.ai/csp"
44

5-
WORKDIR /app
6-
7-
COPY src/ /app
5+
COPY csp/requirements.txt /csp/requirements.txt
86

97
RUN apk add --no-cache python3 py3-pip py3-waitress py3-flask py3-prometheus-client && \
10-
pip3 install --no-cache-dir -r requirements.txt
8+
pip3 install --no-cache-dir -r /csp/requirements.txt
9+
10+
COPY csp/ /csp
11+
COPY csp.sh /usr/local/bin/csp.sh
1112

1213
EXPOSE 9180
1314

14-
ENTRYPOINT ["python3", "/app/csp.py"]
15+
ENTRYPOINT ["/usr/local/bin/csp.sh"]

README.md

Lines changed: 70 additions & 15 deletions
Large diffs are not rendered by default.

build.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/usr/bin/env sh
22

3-
echo "Setting VERSION='${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}' in src/constants.py"
4-
echo "VERSION = '${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}'" >> src/constants.py
3+
echo "Setting VERSION"
4+
find . -name .git -type d -prune -o -type f -name constants.py -exec sed -i "s|^VERSION.*|VERSION\ =\ \'${CI_COMMIT_REF_NAME:-None}\'|g" {} + -exec grep VERSION {} +
5+
echo "Setting BUILD"
6+
find . -name .git -type d -prune -o -type f -name constants.py -exec sed -i "s|^BUILD.*|BUILD\ =\ \'${CI_PIPELINE_ID:-None}\'|g" {} + -exec grep BUILD {} +

csp.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env sh
2+
3+
exec python3 -m csp "$@"

csp/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
""" sets the logging defaults for csp """
4+
5+
import os
6+
from .lib import log as logging
7+
from .lib import constants
8+
9+
version = f'{constants.VERSION}-{constants.BUILD}'
10+
log = logging.setup_logger(
11+
name='csp',
12+
level=os.environ.get('LOGLEVEL', 'INFO'),
13+
gelf_host=os.environ.get('GELF_HOST'),
14+
gelf_port=int(os.environ.get('GELF_PORT', 12201)),
15+
_ix_id=__package__,
16+
_version=version,
17+
)

csp/__main__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
""" processes the environment variables and starts csp """
4+
5+
import logging
6+
from . import csp
7+
from .lib import helpers
8+
from .lib import constants
9+
10+
log = logging.getLogger('csp')
11+
12+
# The environ keys to use, each of them correlating to `int`, `list`, `string`, `boolean` or `filter`
13+
options = helpers.gather_environ({
14+
'max_content_length': 'int',
15+
'address': 'string',
16+
'port': 'int',
17+
'enable_healthz_version': 'boolean',
18+
'csp_path': 'string',
19+
'healthz_path': 'string',
20+
'metrics_path': 'string',
21+
})
22+
c = csp.CSP(**options)
23+
24+
version = f'{constants.VERSION}-{constants.BUILD}'
25+
log.warning(f"Starting **{__package__} {version}**. Listening on {c.get_address()}:{c.get_port()}")
26+
27+
c.start()

csp/csp.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
""" Web server that logs HTTP POST requests """
3+
4+
import logging
5+
import os
6+
import json
7+
from flask import Flask
8+
from flask import request
9+
from waitress import serve
10+
from .lib.constants import VERSION, BUILD, W001, W002, W003
11+
log = logging.getLogger('csp')
12+
13+
14+
class CSP():
15+
""" The main CSP class """
16+
17+
settings = {
18+
'max_content_length': 32768,
19+
'address': '*',
20+
'port': 9180,
21+
'enable_healthz_version': False,
22+
'csp_path': '/csp',
23+
'healthz_path': '/healthz',
24+
'metrics_path': '/metrics', # Placeholder
25+
}
26+
27+
def __init__(self, **kwargs):
28+
for k, v in kwargs.items():
29+
if k in self.settings:
30+
self.settings[k] = v
31+
else:
32+
log.warning(f'{k} not found in settings. Ignoring.')
33+
self.server = Flask(__name__)
34+
self.server.secret_key = os.urandom(64).hex()
35+
self.server.add_url_rule(self.settings['csp_path'], 'csp', self.log_csp, methods=['POST'])
36+
self.server.add_url_rule(self.settings['healthz_path'], 'healthz', self.healthz, methods=['GET'])
37+
38+
def log_csp(self):
39+
""" Logs the content posted """
40+
41+
result = ('OK', 200)
42+
43+
try:
44+
if request.content_length == 0:
45+
raise TypeError(W002)
46+
if request.content_length > self.settings['max_content_length']:
47+
log.warning(f'{W001} ({request.content_length}). Dropping.')
48+
result = (W001, 413)
49+
except TypeError:
50+
log.warning(W002)
51+
result = (W002, 422)
52+
53+
if result == ('OK', 200):
54+
log.debug(f"{request.environ}")
55+
content = request.get_data(as_text=True)
56+
try:
57+
json_content = json.loads(content)
58+
log.info(f'{json.dumps(json_content)}')
59+
# Placeholder: json_content can now be analysed. Ideally, with a function outside of the CSP class
60+
except json.decoder.JSONDecodeError:
61+
log.debug(f'{W003}: `{content}`')
62+
result = (W003, 422)
63+
64+
return result
65+
66+
def healthz(self):
67+
""" Healthcheck """
68+
version_string = f'{__package__} {VERSION}-{BUILD}'
69+
70+
log.debug(f'Healthcheck {version_string}')
71+
72+
message = 'OK'
73+
if self.settings['enable_healthz_version']:
74+
message = version_string
75+
return (message, 200)
76+
77+
def start(self):
78+
""" Start the web server """
79+
serve(
80+
self.server,
81+
host=self.settings['address'],
82+
port=self.settings['port'],
83+
ident=None,
84+
)
85+
86+
def get_port(self) -> int:
87+
""" returns the configured port from self.settings['port'] """
88+
return self.settings['port']
89+
90+
def get_address(self) -> int:
91+
""" returns the configured address from self.settings['port'] """
92+
return self.settings['address']

csp/lib/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
""" Constants declarations """
4+
5+
VERSION = None
6+
BUILD = None
7+
8+
W001 = 'Content too large'
9+
W002 = 'Empty content received'
10+
W003 = 'Content is not JSON'

0 commit comments

Comments
 (0)