Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.8-slim-buster

WORKDIR /executor
COPY docker-executor /executor

ENV MAX_EXECUTABLE=8192
ENV MAX_DATA_SIZE=512

EXPOSE 5000

RUN pip3 install -r requirements.txt

CMD [ "gunicorn", "app:app", "-b", "0.0.0.0:5000"]
94 changes: 94 additions & 0 deletions docker/docker-executor/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from flask import jsonify, Flask, request
import os
import shlex
import subprocess
import base64

# Set environment flag of MAX_EXECUTABLE, MAX_DATA_SIZE


runtime_version = "google-cloud-function:2.0.3"
app = Flask(__name__)

def get_env(env, flag):
if flag not in env:
raise Exception(flag + " is missing")
return int(env[flag])


def success(returncode, stdout, stderr, err):
return (
jsonify(
{
"returncode": returncode,
"stdout": stdout,
"stderr": stderr,
"err": err,
"version": runtime_version,
}
),
200,
)


def bad_request(err):
return jsonify({"error": err}), 400

@app.route('/', methods=['POST'])
def execute():
"""Responds to any HTTP request.
Args:
request (flask.Request): HTTP request object.
Returns:
The response text or any set of values that can be turned into a
Response object using
`make_response <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>`.
"""
env = os.environ.copy()

MAX_EXECUTABLE = get_env(env, "MAX_EXECUTABLE")
MAX_DATA_SIZE = get_env(env, "MAX_DATA_SIZE")

request_json = request.get_json(force=True)
if "executable" not in request_json:
return bad_request("Missing executable value")
executable = base64.b64decode(request_json["executable"])
if len(executable) > MAX_EXECUTABLE:
return bad_request("Executable exceeds max size")
if "calldata" not in request_json:
return bad_request("Missing calldata value")
if len(request_json["calldata"]) > MAX_DATA_SIZE:
return bad_request("Calldata exceeds max size")
if "timeout" not in request_json:
return bad_request("Missing timeout value")
try:
timeout = int(request_json["timeout"])
except ValueError:
return bad_request("Timeout format invalid")

path = "/tmp/execute.sh"
with open(path, "w") as f:
f.write(executable.decode())

os.chmod(path, 0o775)
try:
env = os.environ.copy()
for key, value in request_json.get("env", {}).items():
env[key] = value

proc = subprocess.Popen(
[path] + shlex.split(request_json["calldata"]),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

proc.wait(timeout=(timeout / 1000))
returncode = proc.returncode
stdout = proc.stdout.read(MAX_DATA_SIZE).decode()
stderr = proc.stderr.read(MAX_DATA_SIZE).decode()
return success(returncode, stdout, stderr, "")
except OSError:
return success(126, "", "", "Execution fail")
except subprocess.TimeoutExpired:
return success(111, "", "", "Execution time limit exceeded")
31 changes: 31 additions & 0 deletions docker/docker-executor/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
aiodns==3.0.0
aiohttp==3.8.1
aiosignal==1.2.0
async-timeout==4.0.2
asynctest==0.13.0
attrs==21.4.0
bech32==1.2.0
ccxt==1.87.48
certifi==2022.5.18.1
cffi==1.15.0
charset-normalizer==2.0.12
click==8.1.3
cryptography==37.0.2
Flask==2.1.2
frozenlist==1.3.0
gunicorn==20.1.0
idna==3.3
importlib-metadata==4.11.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
multidict==6.0.2
pycares==4.1.2
pycparser==2.21
requests==2.28.0
typing-extensions==4.4.0
urllib3==1.26.9
websocket-client==1.3.2
Werkzeug==2.1.2
yarl==1.7.2
zipp==3.8.0