diff --git a/.gitignore b/.gitignore index 72364f9..9719cdf 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,5 @@ ENV/ # Rope project settings .ropeproject + +.idea \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..f4feb8c --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,28 @@ +#!/usr/bin/env groovy + +pipeline { + agent { + label { + label 'docker' + } + } + options { + timestamps() + timeout(time: 30, unit: 'MINUTES') + } + environment { + AWS_ACCESS_KEY_ID = credentials('AAMDEV_AWS_ACCESS_KEY_ID') + AWS_SECRET_ACCESS_KEY = credentials('AAMDEV_AWS_SECRET_ACCESS_KEY') + AWS_PROFILE = "aamdevelopment" + AWS_DEFAULT_REGION="eu-west-1" + DOCKER_REGISTRY = "${env.KDMTS_REGISTRY}" + IMAGE_TAG = "${env.BRANCH_NAME}" + } + stages { + stage('Build docker image') { + steps { + sh 'make docker-build-mocky' + } + } + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1513dc5 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +export WORKSPACE_DIR:=$(shell pwd) +export IMAGE_TAG?=dev + +PACKER?=packer.io + +ifeq ($(IMAGE_TAG), master) +export IMAGE_TAG=latest +endif + +ifdef AWS_PROFILE +export AWS_PROFILE_ARGS=--profile $(AWS_PROFILE) +endif + +docker-login-aws-ecr: + @$(shell aws ecr get-login --no-include-email --region eu-west-1 $(AWS_PROFILE_ARGS)) + +docker-build-mocky: docker-login-aws-ecr + $(PACKER) build ops/mocky_packer.json \ No newline at end of file diff --git a/endpoints.json b/endpoints.json deleted file mode 100644 index 8abea85..0000000 --- a/endpoints.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - "/hello" -] - - - diff --git a/examples/endpoints.json b/examples/endpoints.json new file mode 100644 index 0000000..2f9790b --- /dev/null +++ b/examples/endpoints.json @@ -0,0 +1,8 @@ +[ + "/hello", + "/hello/", + "/hello/world/<__world_number__>" +] + + + diff --git a/examples/responses/hello/alice/get.json b/examples/responses/hello/alice/get.json new file mode 100644 index 0000000..fa6eae9 --- /dev/null +++ b/examples/responses/hello/alice/get.json @@ -0,0 +1,5 @@ +{ + "body": "Hello Alice!", + "status": 201, + "headers": {"Content-Type": "application/json"} +} diff --git a/examples/responses/hello/bob/get.json b/examples/responses/hello/bob/get.json new file mode 100644 index 0000000..f7be33a --- /dev/null +++ b/examples/responses/hello/bob/get.json @@ -0,0 +1,5 @@ +{ + "body": "Hello Bob!", + "status": 201, + "headers": {"Content-Type": "application/json"} +} diff --git a/responses/hello/get.json b/examples/responses/hello/get.json similarity index 100% rename from responses/hello/get.json rename to examples/responses/hello/get.json diff --git a/examples/responses/hello/world/__world_number__/get.json b/examples/responses/hello/world/__world_number__/get.json new file mode 100644 index 0000000..430e7b8 --- /dev/null +++ b/examples/responses/hello/world/__world_number__/get.json @@ -0,0 +1,9 @@ +{ + "body": { + "World Number": 123 + }, + "status": 201, + "headers": { + "Content-Type": "application/json" + } +} diff --git a/mock.py b/mock.py index f3bd67d..edb9988 100644 --- a/mock.py +++ b/mock.py @@ -8,7 +8,15 @@ METHOD_NOT_ALLOWED_RESPONSE = { 'body': {'message': 'Method not implemented'}, 'headers': {'Content-Type': 'application/json'}, - 'status': 405 + 'status_code': 405 +} + +PREFLIGHT_RESPONSE = { + 'headers': {'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Max-Age': '86400'}, + 'status_code': 200 } @@ -19,7 +27,7 @@ def load_json(file_path): def save_json(file_path, data): - with open(file_path, 'w') as f: + with open(file_path, 'w+') as f: json.dump(data, f) @@ -28,10 +36,12 @@ class MethodFile(Enum): POST = 'post.json' PUT = 'put.json' DELETE = 'delete.json' + OPTIONS = 'options.json' class Config: def __init__(self): + print(os.getcwd()) mock_workdir = os.getenv('MOCK_WORKDIR') mock_endpoints = os.getenv('MOCK_ENDPOINTS', 'endpoints.json') responses_dir_name = os.getenv('MOCK_RESPONSES_DIR_NAME', 'responses') @@ -43,44 +53,64 @@ def __init__(self): class FileResource(Resource): _response_file_path = None + _request_data = None + _response = None + _method = None + _method_file = None def __init__(self, responses_path, endpoint_path): self._responses_path = responses_path self._endpoint_path = endpoint_path def get(self, **kwargs): - self._update_file_paths(MethodFile.GET, **kwargs) - self._save_request_data() - response = self._get_response() - return response + self._process(**kwargs) + return self._response def post(self, **kwargs): - self._update_file_paths(MethodFile.POST, **kwargs) - self._save_request_data() - response = self._get_response() - return response + self._process(**kwargs) + return self._response def put(self, **kwargs): - self._update_file_paths(MethodFile.PUT, **kwargs) - response = self._get_response() - return response + self._process(**kwargs) + return self._response def delete(self, **kwargs): - self._update_file_paths(MethodFile.DELETE, **kwargs) - response = self._get_response() - return response + self._process(**kwargs) + return self._response + + def options(self, **kwargs): + self._process(**kwargs) + return self._response + + def _process(self, **kwargs): + self._process_request(**kwargs) + self._response = self._get_response() + + def _process_request(self, **kwargs): + self._method = request.method + self._method_file = MethodFile[self._method].value + self._extract_request_data() + self._update_file_paths(**kwargs) + self._log_request_data() + self._save_request_data() - def _save_request_data(self): + def _extract_request_data(self): request_data = { 'headers': dict(request.headers), 'body': request.json if request.is_json else request.data.decode() or None, 'args': dict(request.args), 'endpoint': request.endpoint, - 'method': request.method + 'method': self._method } - save_json(self._request_file_path, request_data) + self._request_data = request_data - def _update_file_paths(self, method, **kwargs): + def _save_request_data(self): + save_json(self._request_file_path, self._request_data) + + def _log_request_data(self): + app.logger.info("REQUEST: %s" % (self._request_data,)) + + def _update_file_paths(self, **kwargs): endpoint_path = self._endpoint_path for key, value in kwargs.items(): @@ -90,17 +120,21 @@ def _update_file_paths(self, method, **kwargs): else: endpoint_path = endpoint_path.replace(path_key, value) - self._response_file_path = os.path.join(self._responses_path, endpoint_path, method.value) + self._response_file_path = os.path.join(self._responses_path, endpoint_path, self._method_file) self._request_file_path = os.path.join(self._responses_path, 'last_request.json') def _get_response(self): try: response_data = load_json(self._response_file_path) except IOError: - response_data = METHOD_NOT_ALLOWED_RESPONSE + if self._method_file == MethodFile.OPTIONS.value: + response_data = PREFLIGHT_RESPONSE + else: + response_data = METHOD_NOT_ALLOWED_RESPONSE body = response_data.get('body') status_code = response_data.get('status_code') headers = response_data.get('headers') + app.logger.info("RESPONSE: %s" % (response_data,)) response = Response(json.dumps(body), status_code, headers) return response diff --git a/ops/docker-entrypoint.sh b/ops/docker-entrypoint.sh new file mode 100755 index 0000000..8535b78 --- /dev/null +++ b/ops/docker-entrypoint.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -ex + +export EXEC_USER_NAME=exec_user + +function add_exec_user() { + if [ -z "${EXEC_USER_ID}" ] + then + EXEC_USER_ID=0 + fi + + if [ ${EXEC_USER_ID} == 0 ] + then + useradd -m -o -g 0 -u ${EXEC_USER_ID} ${EXEC_USER_NAME} + else + useradd -m -o -u ${EXEC_USER_ID} ${EXEC_USER_NAME} + fi +} + +id -u exec_user &> /dev/null || add_exec_user + +if [ "${1}" == "mock" ] +then + su ${EXEC_USER_NAME} -c "python3 /mock.py" +else + exec "$@" +fi diff --git a/ops/mocky_packer.json b/ops/mocky_packer.json new file mode 100644 index 0000000..7dca6e4 --- /dev/null +++ b/ops/mocky_packer.json @@ -0,0 +1,60 @@ +{ + "variables": { + "workspace_dir": "{{env `WORKSPACE_DIR`}}", + "aws_registry": "{{env `KDMTS_REGISTRY`}}", + "tag": "{{env `IMAGE_TAG`}}" + }, + "builders": [ + { + "type": "docker", + "image": "python:3.6", + "commit": true, + "changes": [ + "LABEL maintainer=artsalliancemedia.com", + "ENV MOCK_WORKDIR=/mock", + "EXPOSE 8080", + "VOLUME /mock", + "WORKDIR /mock", + "ENTRYPOINT [\"/docker-entrypoint.sh\"]", + "CMD [\"mock\"]" + ] + } + ], + "provisioners": [ + { + "type": "file", + "source": "{{user `workspace_dir`}}/mock.py", + "destination": "/" + }, + { + "type": "file", + "source": "{{user `workspace_dir`}}/ops/docker-entrypoint.sh", + "destination": "/" + }, + { + "type": "file", + "source": "{{user `workspace_dir`}}/requirements.txt", + "destination": "/tmp/" + }, + { + "type": "shell", + "inline": [ + "pip3 install --upgrade -r /tmp/requirements.txt" + ] + } + ], + "post-processors": [ + [ + { + "type": "docker-tag", + "repository": "{{user `aws_registry`}}/aam_mocky", + "tag": "{{user `tag`}}" + }, + { + "type": "docker-push", + "ecr_login": false, + "login_server": "https://{{user `aws_registry`}}/aam_mocky" + } + ] + ] +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a4581ba --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask +flask-restful \ No newline at end of file