summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2020-05-06 20:05:42 -0500
committerJohn Estabrook <jestabro@vyos.io>2020-05-06 20:05:42 -0500
commit260ab097bda099665ec376fd49b9e8edc0ad52b9 (patch)
treea4c0376134aaeb6afc213b5dfc963eae9a07730b
parented22334321d3b6f27b5d695a4f984257b909f78b (diff)
parent7b31dd720d96bc481323e3b00100da718f551cd5 (diff)
downloadvyos-1x-260ab097bda099665ec376fd49b9e8edc0ad52b9.tar.gz
vyos-1x-260ab097bda099665ec376fd49b9e8edc0ad52b9.zip
Merge branch 'http-api' of jestabro/vyos-1x into current
http api: T2395: add waitress as production WSGI server http api: T2395: replace bottle with flask as microframework http api: use decorator to get command data from request http api: catch appropriate errors http api: function names should be consistent
-rw-r--r--debian/control3
-rwxr-xr-xsrc/services/vyos-http-api-server92
2 files changed, 49 insertions, 46 deletions
diff --git a/debian/control b/debian/control
index 5c176f40a..32c0286a4 100644
--- a/debian/control
+++ b/debian/control
@@ -28,7 +28,8 @@ Depends: python3,
python3-isc-dhcp-leases,
python3-hurry.filesize,
python3-vici (>= 5.7.2),
- python3-bottle,
+ python3-flask,
+ python3-waitress,
python3-netaddr,
python3-zmq,
cron,
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index c36cbd640..4c41fa96d 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -26,7 +26,8 @@ import signal
import vyos.config
-import bottle
+from flask import Flask, request
+from waitress import serve
from functools import wraps
@@ -37,7 +38,7 @@ from vyos.config import VyOSError
DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf'
CFG_GROUP = 'vyattacfg'
-app = bottle.default_app()
+app = Flask(__name__)
# Giant lock!
lock = threading.Lock()
@@ -55,18 +56,31 @@ def check_auth(key_list, key):
return id
def error(code, msg):
- bottle.response.status = code
resp = {"success": False, "error": msg, "data": None}
- return json.dumps(resp)
+ return json.dumps(resp), code
def success(data):
resp = {"success": True, "data": data, "error": None}
return json.dumps(resp)
+def get_command(f):
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ cmd = request.form.get("data")
+ if not cmd:
+ return error(400, "Non-empty data field is required")
+ try:
+ cmd = json.loads(cmd)
+ except Exception as e:
+ return error(400, "Failed to parse JSON: {0}".format(e))
+ return f(cmd, *args, **kwargs)
+
+ return decorated_function
+
def auth_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
- key = bottle.request.forms.get("key")
+ key = request.form.get("key")
api_keys = app.config['vyos_keys']
id = check_auth(api_keys, key)
if not id:
@@ -75,28 +89,20 @@ def auth_required(f):
return decorated_function
-@app.route('/configure', method='POST')
+@app.route('/configure', methods=['POST'])
+@get_command
@auth_required
-def configure():
+def configure_op(commands):
session = app.config['vyos_session']
env = session.get_session_env()
config = vyos.config.Config(session_env=env)
- strict_field = bottle.request.forms.get("strict")
+ strict_field = request.form.get("strict")
if strict_field == "true":
strict = True
else:
strict = False
- commands = bottle.request.forms.get("data")
- if not commands:
- return error(400, "Non-empty data field is required")
- else:
- try:
- commands = json.loads(commands)
- except Exception as e:
- return error(400, "Failed to parse JSON: {0}".format(e))
-
# Allow users to pass just one command
if not isinstance(commands, list):
commands = [commands]
@@ -186,16 +192,14 @@ def configure():
else:
return success(None)
-@app.route('/retrieve', method='POST')
+@app.route('/retrieve', methods=['POST'])
+@get_command
@auth_required
-def get_value():
+def retrieve_op(command):
session = app.config['vyos_session']
env = session.get_session_env()
config = vyos.config.Config(session_env=env)
- command = bottle.request.forms.get("data")
- command = json.loads(command)
-
try:
op = command['op']
path = " ".join(command['path'])
@@ -229,20 +233,20 @@ def get_value():
return error(400, "\"{0}\" is not a valid operation".format(op))
except VyOSError as e:
return error(400, str(e))
+ except ConfigSessionError as e:
+ return error(400, str(e))
except Exception as e:
print(traceback.format_exc(), file=sys.stderr)
return error(500, "An internal error occured. Check the logs for details.")
return success(res)
-@app.route('/config-file', method='POST')
+@app.route('/config-file', methods=['POST'])
+@get_command
@auth_required
-def config_file_op():
+def config_file_op(command):
session = app.config['vyos_session']
- command = bottle.request.forms.get("data")
- command = json.loads(command)
-
try:
op = command['op']
except KeyError:
@@ -264,7 +268,7 @@ def config_file_op():
res = session.commit()
else:
return error(400, "\"{0}\" is not a valid operation".format(op))
- except VyOSError as e:
+ except ConfigSessionError as e:
return error(400, str(e))
except Exception as e:
print(traceback.format_exc(), file=sys.stderr)
@@ -272,14 +276,12 @@ def config_file_op():
return success(res)
-@app.route('/image', method='POST')
+@app.route('/image', methods=['POST'])
+@get_command
@auth_required
-def config_file_op():
+def image_op(command):
session = app.config['vyos_session']
- command = bottle.request.forms.get("data")
- command = json.loads(command)
-
try:
op = command['op']
except KeyError:
@@ -300,7 +302,7 @@ def config_file_op():
res = session.remove_image(name)
else:
return error(400, "\"{0}\" is not a valid operation".format(op))
- except VyOSError as e:
+ except ConfigSessionError as e:
return error(400, str(e))
except Exception as e:
print(traceback.format_exc(), file=sys.stderr)
@@ -309,14 +311,12 @@ def config_file_op():
return success(res)
-@app.route('/generate', method='POST')
+@app.route('/generate', methods=['POST'])
+@get_command
@auth_required
-def generate_op():
+def generate_op(command):
session = app.config['vyos_session']
- command = bottle.request.forms.get("data")
- command = json.loads(command)
-
try:
op = command['op']
path = command['path']
@@ -339,14 +339,12 @@ def generate_op():
return success(res)
-@app.route('/show', method='POST')
+@app.route('/show', methods=['POST'])
+@get_command
@auth_required
-def show_op():
+def show_op(command):
session = app.config['vyos_session']
- command = bottle.request.forms.get("data")
- command = json.loads(command)
-
try:
op = command['op']
path = command['path']
@@ -398,4 +396,8 @@ if __name__ == '__main__':
signal.signal(signal.SIGTERM, sig_handler)
- bottle.run(app, host=server_config["listen_address"], port=server_config["port"], debug=True)
+ try:
+ serve(app, host=server_config["listen_address"],
+ port=server_config["port"])
+ except OSError as e:
+ print(f"OSError {e}")