From 3b895e7bd0462cf81afbf8eb34e4a66c45a4c7b2 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Mon, 12 Aug 2019 17:11:38 -0400 Subject: [PATCH 01/10] matthew: removing uneeded files Signed-off-by: mdgreenwald --- __init__.py | 0 app/__init__.py | 6 ------ push-deploy.py | 1 - 3 files changed, 7 deletions(-) delete mode 100644 __init__.py delete mode 100644 app/__init__.py delete mode 100644 push-deploy.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index 16adb8e..0000000 --- a/app/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from flask import Flask -from app.config import Config - -app = Flask(__name__) -instance.config.from_object(Config) - diff --git a/push-deploy.py b/push-deploy.py deleted file mode 100644 index f301245..0000000 --- a/push-deploy.py +++ /dev/null @@ -1 +0,0 @@ -print("Hello World!") From 80b7fde1a92c7b962d87bd33568787503f6fd81b Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Mon, 12 Aug 2019 17:15:05 -0400 Subject: [PATCH 02/10] matthew: k8s clientless poc Signed-off-by: mdgreenwald --- Dockerfile | 1 - config.py | 8 +++++++ docker-compose.yml | 8 +++---- kubernetes/app.yaml | 3 +++ pushdeploy/__init__.py | 51 ++++++++++++++++++++++++++++++++++++++++++ pushdeploy/apiv1.py | 50 +++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 +++ 7 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 config.py create mode 100755 pushdeploy/__init__.py create mode 100755 pushdeploy/apiv1.py diff --git a/Dockerfile b/Dockerfile index a9da1e7..55173d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,3 @@ COPY . . EXPOSE 5000 -CMD [ "python", "./push-deploy.py" ] diff --git a/config.py b/config.py new file mode 100644 index 0000000..d7c0ac3 --- /dev/null +++ b/config.py @@ -0,0 +1,8 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + +PD_NAMESPACE = os.environ.get('PD_NAMESPACE') +PD_DEPLOYMENT = os.environ.get('PD_DEPLOYMENT') +PD_USER = os.environ.get('PD_USER') +PD_PASSWORD = os.environ.get('PD_PASSWORD') +SECRET_KEY = os.environ.get('PD_SECRET_KEY') diff --git a/docker-compose.yml b/docker-compose.yml index f349b39..95e0c90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: stdin_open: true tty: true restart: always + ports: + - "5000:5000" volumes: - - "./:/opt/app" - environment: - PD_NAMESPACE: ${PD_NAMESPACE} - PD_DEPLOYMENT: ${PD_DEPLOYMENT} + - "./:/opt/push-deploy" + diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 5503135..702dc3d 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -15,6 +15,9 @@ rules: - apiGroups: ["", "apps"] resources: ["deployments"] verbs: ["list", "patch"] +- apiGroups: [""] + resources: ["pods"] + verbs: ["list"] --- kind: RoleBinding diff --git a/pushdeploy/__init__.py b/pushdeploy/__init__.py new file mode 100755 index 0000000..52ff318 --- /dev/null +++ b/pushdeploy/__init__.py @@ -0,0 +1,51 @@ +import os +from flask import Flask, jsonify +from flask_jwt_extended import ( + JWTManager, jwt_required, jwt_optional, create_access_token, + get_jwt_identity + ) +from pushdeploy import apiv1 + +def create_app(test_config=None): + """Create and configure an instance of the Flask application.""" + app = Flask(__name__, instance_relative_config=True) + app.config.from_object('config') + + app.config['JWT_SECRET_KEY'] = app.config['SECRET_KEY'] + jwt = JWTManager(app) + + @jwt.expired_token_loader + def my_expired_token_callback(expired_token): + token_type = expired_token['type'] + return jsonify({ + 'status': 401, + 'sub_status': 42, + 'msg': 'The {} token has expired'.format(token_type) + }), 401 + + if test_config is None: + app.config.from_pyfile("config.py", silent=False) + else: + app.config.update(test_config) + + try: + os.makedirs(app.instance_path) + except OSError: + pass + + @app.route('/health', methods=['GET']) + @jwt_optional + def health(): + return jsonify( + status='healthy', + releaseId='0.0.0', + ), 200 + + @app.route('/', methods=['GET']) + @jwt_required + def index(): + return jsonify(), 200 + + app.register_blueprint(apiv1.bp) + + return app diff --git a/pushdeploy/apiv1.py b/pushdeploy/apiv1.py new file mode 100755 index 0000000..a4a9016 --- /dev/null +++ b/pushdeploy/apiv1.py @@ -0,0 +1,50 @@ +import functools + +from datetime import timedelta +from flask import Blueprint +from flask import current_app +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import session +from flask import url_for +from flask import jsonify + +from flask_jwt_extended import ( + JWTManager, jwt_required, jwt_optional, create_access_token, + get_jwt_identity + ) + +bp = Blueprint("apiv1", __name__, url_prefix="/api/v1") + +@bp.route('/auth', methods=['POST']) +def login(): + if not request.is_json: + return jsonify({"msg": "Missing JSON in request"}), 400 + + username = request.json.get('username', None) + password = request.json.get('password', None) + if not username: + return jsonify({"msg": "Missing username parameter"}), 400 + if not password: + return jsonify({"msg": "Missing password parameter"}), 400 + + if username != current_app.config['PD_USER'] or password != current_app.config['PD_PASSWORD']: + return jsonify({"msg": "Bad username or password"}), 401 + + # Identity can be any data that is json serializable + access_token = create_access_token(identity=username, expires_delta=timedelta(seconds=300)) + return jsonify(access_token=access_token), 200 + +@bp.route('/', methods=['GET']) +@jwt_required +def index(): + return jsonify(), 200 + +@bp.route('/deploy', methods=['GET']) +@jwt_required +def deploy(): + current_user = get_jwt_identity() + return jsonify(logged_in_as=current_user), 200 diff --git a/requirements.txt b/requirements.txt index 0335cf3..99d2afc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ kubernetes==10.0.0 Flask==1.1.1 +Flask-API==1.1 +flask-jwt-extended==3.21.0 +gunicorn==19.9.0 From 3a5e8e09145ac8115f07872edb0be524f7669e74 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Tue, 13 Aug 2019 13:31:03 -0400 Subject: [PATCH 03/10] matthew: working with alpine and gunicorn in k8s Signed-off-by: mdgreenwald --- Dockerfile | 3 +-- Tiltfile | 2 +- docker-compose.yml | 2 +- kubernetes/app.yaml | 34 +++++++++++++++++++++++++++++++--- kubernetes/ingress.yaml | 16 ++++++++++++++++ wsgi.py | 4 ++++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 kubernetes/ingress.yaml create mode 100644 wsgi.py diff --git a/Dockerfile b/Dockerfile index 55173d3..4c6adab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3-slim-stretch +FROM python:3-alpine WORKDIR /opt/push-deploy @@ -9,4 +9,3 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 - diff --git a/Tiltfile b/Tiltfile index 9038299..1575566 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,6 +1,6 @@ # one static YAML file k8s_yaml('./kubernetes/app.yaml') -docker_build('docker.io/library/push-deploy', '.') +docker_build('push-deploy', '.') k8s_resource('push-deploy', port_forwards='5000:5000') diff --git a/docker-compose.yml b/docker-compose.yml index 95e0c90..4db2e98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: build: dockerfile: Dockerfile context: . - entrypoint: /bin/bash + entrypoint: /bin/sh stdin_open: true tty: true restart: always diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 702dc3d..550f49e 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -51,9 +51,22 @@ spec: serviceAccountName: push-deploy containers: - image: push-deploy:latest - imagePullPolicy: Always - command: ["python"] - args: ["push-deploy.py"] + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 + readinessProbe: + tcpSocket: + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + command: ["gunicorn"] + args: ["-w", "4", "-b", "0.0.0.0", "wsgi:app"] name: push-deploy resources: requests: @@ -73,3 +86,18 @@ spec: terminationGracePeriodSeconds: 30 --- +apiVersion: v1 +kind: Service +metadata: + name: push-deploy + labels: + app: push-deploy +spec: + ports: + - port: 80 + targetPort: 8000 + protocol: TCP + selector: + app: push-deploy + +--- \ No newline at end of file diff --git a/kubernetes/ingress.yaml b/kubernetes/ingress.yaml new file mode 100644 index 0000000..dd013f2 --- /dev/null +++ b/kubernetes/ingress.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: push-deploy +spec: + rules: + - host: pushdeploy.internal + http: + paths: + - path: / + backend: + serviceName: push-deploy + servicePort: 80 + +--- diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..1bb6b66 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +import os +from pushdeploy import create_app + +app = create_app() \ No newline at end of file From d1271ec9a31135ede654e8b7fd8379a31e56627d Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Tue, 13 Aug 2019 14:26:20 -0400 Subject: [PATCH 04/10] matthew: enable gunicorn stdout and stderr Signed-off-by: mdgreenwald --- kubernetes/app.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 550f49e..42aab84 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -66,7 +66,7 @@ spec: initialDelaySeconds: 10 periodSeconds: 10 command: ["gunicorn"] - args: ["-w", "4", "-b", "0.0.0.0", "wsgi:app"] + args: ["-w", "4", "--access-logfile", "-", "--error-logfile", "-", "-b", "0.0.0.0", "wsgi:app"] name: push-deploy resources: requests: From 0fd49ba021978b90511682f46d6737889da5c201 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Wed, 14 Aug 2019 12:59:35 -0400 Subject: [PATCH 05/10] matthew: working non-param deploy patch Signed-off-by: mdgreenwald --- kubernetes/app.yaml | 4 ++-- pushdeploy/__init__.py | 1 + pushdeploy/apiv1.py | 34 +++++++++++++++++++++++++++++++--- requirements.txt | 2 +- wsgi.py | 3 ++- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 42aab84..6c9f46b 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -57,13 +57,13 @@ spec: readinessProbe: tcpSocket: port: 8000 - initialDelaySeconds: 10 + initialDelaySeconds: 6 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 - initialDelaySeconds: 10 + initialDelaySeconds: 14 periodSeconds: 10 command: ["gunicorn"] args: ["-w", "4", "--access-logfile", "-", "--error-logfile", "-", "-b", "0.0.0.0", "wsgi:app"] diff --git a/pushdeploy/__init__.py b/pushdeploy/__init__.py index 52ff318..4adc9d1 100755 --- a/pushdeploy/__init__.py +++ b/pushdeploy/__init__.py @@ -1,4 +1,5 @@ import os +from kubernetes import client, config from flask import Flask, jsonify from flask_jwt_extended import ( JWTManager, jwt_required, jwt_optional, create_access_token, diff --git a/pushdeploy/apiv1.py b/pushdeploy/apiv1.py index a4a9016..6a3fcf5 100755 --- a/pushdeploy/apiv1.py +++ b/pushdeploy/apiv1.py @@ -1,5 +1,6 @@ import functools - +import json +from kubernetes import client, config from datetime import timedelta from flask import Blueprint from flask import current_app @@ -19,6 +20,33 @@ bp = Blueprint("apiv1", __name__, url_prefix="/api/v1") +@bp.before_app_request +def init_api(): + """Creates instances of the incluster config and client API + and stores them in global""" + g.configuration = config.load_incluster_config() + g.api_instance = client.AppsV1Api(client.ApiClient(g.configuration)) + # g.api_instance = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient(g.configuration)) + +def read_deployment(): + api_response = g.api_instance.list_namespaced_deployment( + namespace="default", + field_selector="metadata.name=http-svc" + ) + if len(api_response.items) == 1: + return api_response.items[0] + else: + return "Deployment selectors not unique enough." + +def update_deployment(deployment): + deployment.spec.template.spec.containers[0].image = "gcr.io/google_containers/echoserver:1.10" + api_response = g.api_instance.patch_namespaced_deployment( + name="http-svc", + namespace="default", + body=deployment, + field_manager="push-deploy") + print("Deployment updated. status='%s'" % str(api_response.status)) + @bp.route('/auth', methods=['POST']) def login(): if not request.is_json: @@ -46,5 +74,5 @@ def index(): @bp.route('/deploy', methods=['GET']) @jwt_required def deploy(): - current_user = get_jwt_identity() - return jsonify(logged_in_as=current_user), 200 + deploy = update_deployment(deployment=read_deployment()) + return jsonify(msg=deploy), 200 diff --git a/requirements.txt b/requirements.txt index 99d2afc..d175f5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -kubernetes==10.0.0 +kubernetes==10.0.1 Flask==1.1.1 Flask-API==1.1 flask-jwt-extended==3.21.0 diff --git a/wsgi.py b/wsgi.py index 1bb6b66..c9e17e2 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,4 +1,5 @@ import os +from werkzeug.middleware.proxy_fix import ProxyFix from pushdeploy import create_app -app = create_app() \ No newline at end of file +app = ProxyFix(create_app(), x_for=1, x_host=1) \ No newline at end of file From 0b568b830d3991c049ba2416df216f84325611c3 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Wed, 14 Aug 2019 13:07:07 -0400 Subject: [PATCH 06/10] matthew: newlines Signed-off-by: mdgreenwald --- kubernetes/app.yaml | 2 +- wsgi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 6c9f46b..5ddf709 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -100,4 +100,4 @@ spec: selector: app: push-deploy ---- \ No newline at end of file +--- diff --git a/wsgi.py b/wsgi.py index c9e17e2..4d23b7d 100644 --- a/wsgi.py +++ b/wsgi.py @@ -2,4 +2,4 @@ from werkzeug.middleware.proxy_fix import ProxyFix from pushdeploy import create_app -app = ProxyFix(create_app(), x_for=1, x_host=1) \ No newline at end of file +app = ProxyFix(create_app(), x_for=1, x_host=1) From fc5ab7a183da5d47ac9a9b87452e624b520cc952 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Wed, 14 Aug 2019 13:25:40 -0400 Subject: [PATCH 07/10] matthew: readme Signed-off-by: mdgreenwald --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2e27a5..ecaf9cf 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Push-Deploy # -Push-Deploy is a Python application which helps to securely and simply enable communication between external tools (GitHub Actions, Circle CI, etc…) and Kubernetes without exposing credentials. +Push-Deploy is a Python application which helps to securely and simply enable communication between external tools (GitHub Actions, Circle CI, etc…) and Kubernetes without exposing cluster credentials. -In particular for projects which may not have semver inplace where other tools like keel and weave/flux make more sense. +In particular for projects which may not have `semver` inplace where other tools like keel.sh and weave/flux would make more sense. -- From 323a65fdef1d76f53896a806694e8222d8c34f63 Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Wed, 14 Aug 2019 13:43:37 -0400 Subject: [PATCH 08/10] matthew: updating ignore files Signed-off-by: mdgreenwald --- .dockerignore | 4 ++++ .gitignore | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.dockerignore b/.dockerignore index 23d11a9..fda8c41 100644 --- a/.dockerignore +++ b/.dockerignore @@ -33,3 +33,7 @@ venv.bak/ # macos .DS_Store + +# secrets +*secrets* +*credentials* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 66bcc26..dc3577f 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,7 @@ venv.bak/ # macos .DS_Store + +# secrets +*secrets* +*credentials* \ No newline at end of file From 9cb44e5622a65db2fd33819f016a04abf6cb40eb Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Fri, 16 Aug 2019 04:14:32 -0400 Subject: [PATCH 09/10] matthew: parameterized funcs Signed-off-by: mdgreenwald --- config.py | 1 + kubernetes/app.yaml | 17 +++++++---------- pushdeploy/apiv1.py | 39 +++++++++++++++++++++++---------------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/config.py b/config.py index d7c0ac3..ab3e100 100644 --- a/config.py +++ b/config.py @@ -3,6 +3,7 @@ PD_NAMESPACE = os.environ.get('PD_NAMESPACE') PD_DEPLOYMENT = os.environ.get('PD_DEPLOYMENT') +PD_REGISTRY = os.environ.get('PD_REGISTRY') PD_USER = os.environ.get('PD_USER') PD_PASSWORD = os.environ.get('PD_PASSWORD') SECRET_KEY = os.environ.get('PD_SECRET_KEY') diff --git a/kubernetes/app.yaml b/kubernetes/app.yaml index 5ddf709..afb36ab 100644 --- a/kubernetes/app.yaml +++ b/kubernetes/app.yaml @@ -15,9 +15,6 @@ rules: - apiGroups: ["", "apps"] resources: ["deployments"] verbs: ["list", "patch"] -- apiGroups: [""] - resources: ["pods"] - verbs: ["list"] --- kind: RoleBinding @@ -68,18 +65,18 @@ spec: command: ["gunicorn"] args: ["-w", "4", "--access-logfile", "-", "--error-logfile", "-", "-b", "0.0.0.0", "wsgi:app"] name: push-deploy + envFrom: + - configMapRef: + name: push-deploy-config + - secretRef: + name: push-deploy-secrets resources: requests: - memory: "96Mi" + memory: "192Mi" cpu: "50m" limits: - memory: "256Mi" + memory: "320Mi" cpu: "250m" - env: - - name: PD_NAMESPACE - value: default - - name: PD_DEPLOYMENT - value: "nginx" imagePullSecrets: - name: k8s-reg-key securityContext: {} diff --git a/pushdeploy/apiv1.py b/pushdeploy/apiv1.py index 6a3fcf5..6af0ca7 100755 --- a/pushdeploy/apiv1.py +++ b/pushdeploy/apiv1.py @@ -26,27 +26,37 @@ def init_api(): and stores them in global""" g.configuration = config.load_incluster_config() g.api_instance = client.AppsV1Api(client.ApiClient(g.configuration)) - # g.api_instance = kubernetes.client.AppsV1Api(kubernetes.client.ApiClient(g.configuration)) + g.PD_NAMESPACE = current_app.config['PD_NAMESPACE'] + g.PD_DEPLOYMENT = current_app.config['PD_DEPLOYMENT'] + g.PD_REGISTRY = current_app.config['PD_REGISTRY'] def read_deployment(): + namespace = "%s" % str(g.PD_NAMESPACE) + field = "metadata.name=%s" % str(g.PD_DEPLOYMENT) api_response = g.api_instance.list_namespaced_deployment( - namespace="default", - field_selector="metadata.name=http-svc" + namespace=namespace, + field_selector=field ) if len(api_response.items) == 1: return api_response.items[0] else: - return "Deployment selectors not unique enough." + return "Deployment selector not unique enough." -def update_deployment(deployment): - deployment.spec.template.spec.containers[0].image = "gcr.io/google_containers/echoserver:1.10" +def update_deployment(deployment, image_name, image_tag): + image = "%s/%s:%s" % (g.PD_REGISTRY, image_name, image_tag) + deployment.spec.template.spec.containers[0].image = image api_response = g.api_instance.patch_namespaced_deployment( - name="http-svc", - namespace="default", + name=g.PD_DEPLOYMENT, + namespace=g.PD_NAMESPACE, body=deployment, field_manager="push-deploy") print("Deployment updated. status='%s'" % str(api_response.status)) +@bp.route('/', methods=['GET']) +@jwt_required +def index(): + return jsonify(), 200 + @bp.route('/auth', methods=['POST']) def login(): if not request.is_json: @@ -63,16 +73,13 @@ def login(): return jsonify({"msg": "Bad username or password"}), 401 # Identity can be any data that is json serializable - access_token = create_access_token(identity=username, expires_delta=timedelta(seconds=300)) + access_token = create_access_token(identity=username, expires_delta=timedelta(seconds=90)) return jsonify(access_token=access_token), 200 -@bp.route('/', methods=['GET']) -@jwt_required -def index(): - return jsonify(), 200 - @bp.route('/deploy', methods=['GET']) @jwt_required def deploy(): - deploy = update_deployment(deployment=read_deployment()) - return jsonify(msg=deploy), 200 + commit_sha = request.args['commit_sha'] + image_name = request.args['image_name'] + deploy = update_deployment(deployment=read_deployment(), image_name=image_name, image_tag=commit_sha) + return jsonify(msg=deploy), 201 From 4e33e90df83b2d68d487a8c93d252f4869ae428e Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Fri, 16 Aug 2019 04:27:57 -0400 Subject: [PATCH 10/10] matthew: adding version Signed-off-by: mdgreenwald --- pushdeploy/__init__.py | 3 ++- version.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 version.py diff --git a/pushdeploy/__init__.py b/pushdeploy/__init__.py index 4adc9d1..b7bc2e1 100755 --- a/pushdeploy/__init__.py +++ b/pushdeploy/__init__.py @@ -1,3 +1,4 @@ +from version import __version__ import os from kubernetes import client, config from flask import Flask, jsonify @@ -39,7 +40,7 @@ def my_expired_token_callback(expired_token): def health(): return jsonify( status='healthy', - releaseId='0.0.0', + releaseId=__version__, ), 200 @app.route('/', methods=['GET']) diff --git a/version.py b/version.py new file mode 100644 index 0000000..2192205 --- /dev/null +++ b/version.py @@ -0,0 +1,2 @@ + +__version__ = '0.0.1'