lorax-composer initial commit
The lorax-composer program will launch a BDCS compatible API server using Flask and Gevent. Currently this is a skeleton application with only one active route (/api/v0/status). The API code lives in ./src/pylorax/api/v0.py with related code in other pylorax/api/* modules.
This commit is contained in:
parent
798023b199
commit
2008a413b6
21
src/pylorax/api/__init__.py
Normal file
21
src/pylorax/api/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#
|
||||||
|
# lorax-composer API server
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from pylorax.api.crossdomain import crossdomain
|
||||||
|
|
||||||
|
__all__ = ["crossdomain"]
|
||||||
|
|
63
src/pylorax/api/crossdomain.py
Normal file
63
src/pylorax/api/crossdomain.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# crossdomain decorator from - http://flask.pocoo.org/snippets/56/
|
||||||
|
from datetime import timedelta
|
||||||
|
from flask import make_response, request, current_app
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def crossdomain(origin, methods=None, headers=None,
|
||||||
|
max_age=21600, attach_to_all=True,
|
||||||
|
automatic_options=True):
|
||||||
|
if methods is not None:
|
||||||
|
methods = ', '.join(sorted(x.upper() for x in methods))
|
||||||
|
if headers is not None and not isinstance(headers, basestring):
|
||||||
|
headers = ', '.join(x.upper() for x in headers)
|
||||||
|
if not isinstance(origin, list):
|
||||||
|
origin = [origin]
|
||||||
|
if isinstance(max_age, timedelta):
|
||||||
|
max_age = max_age.total_seconds()
|
||||||
|
|
||||||
|
def get_methods():
|
||||||
|
if methods is not None:
|
||||||
|
return methods
|
||||||
|
|
||||||
|
options_resp = current_app.make_default_options_response()
|
||||||
|
return options_resp.headers['allow']
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
def wrapped_function(*args, **kwargs):
|
||||||
|
if automatic_options and request.method == 'OPTIONS':
|
||||||
|
resp = current_app.make_default_options_response()
|
||||||
|
else:
|
||||||
|
resp = make_response(f(*args, **kwargs))
|
||||||
|
if not attach_to_all and request.method != 'OPTIONS':
|
||||||
|
return resp
|
||||||
|
|
||||||
|
h = resp.headers
|
||||||
|
|
||||||
|
h.extend([("Access-Control-Allow-Origin", orig) for orig in origin])
|
||||||
|
h['Access-Control-Allow-Methods'] = get_methods()
|
||||||
|
h['Access-Control-Max-Age'] = str(max_age)
|
||||||
|
if headers is not None:
|
||||||
|
h['Access-Control-Allow-Headers'] = headers
|
||||||
|
return resp
|
||||||
|
|
||||||
|
f.provide_automatic_options = False
|
||||||
|
return update_wrapper(wrapped_function, f)
|
||||||
|
return decorator
|
32
src/pylorax/api/server.py
Normal file
32
src/pylorax/api/server.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
from flask import Flask, jsonify
|
||||||
|
|
||||||
|
from pylorax.api import crossdomain
|
||||||
|
from pylorax.api.v0 import v0_api
|
||||||
|
|
||||||
|
server = Flask(__name__)
|
||||||
|
|
||||||
|
__all__ = ["server"]
|
||||||
|
|
||||||
|
@server.route('/')
|
||||||
|
@crossdomain(origin="*")
|
||||||
|
def hello_world():
|
||||||
|
return 'Hello, World!'
|
||||||
|
|
||||||
|
v0_api(server)
|
44
src/pylorax/api/v0.py
Normal file
44
src/pylorax/api/v0.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
# Use pykickstart to calculate disk image size
|
||||||
|
from pykickstart.parser import KickstartParser
|
||||||
|
from pykickstart.version import makeVersion, RHEL7
|
||||||
|
|
||||||
|
from pylorax.api import crossdomain
|
||||||
|
from pylorax.creator import DRACUT_DEFAULT, mount_boot_part_over_root
|
||||||
|
from pylorax.creator import make_appliance, make_image, make_livecd, make_live_images
|
||||||
|
from pylorax.creator import make_runtime, make_squashfs
|
||||||
|
from pylorax.imgutils import copytree
|
||||||
|
from pylorax.imgutils import Mount, PartitionMount, umount
|
||||||
|
from pylorax.installer import InstallError
|
||||||
|
from pylorax.sysutils import joinpaths
|
||||||
|
|
||||||
|
|
||||||
|
# no-virt mode doesn't need libvirt, so make it optional
|
||||||
|
try:
|
||||||
|
import libvirt
|
||||||
|
except ImportError:
|
||||||
|
libvirt = None
|
||||||
|
|
||||||
|
def v0_api(api):
|
||||||
|
""" Setup v0 of the API server"""
|
||||||
|
@api.route("/api/v0/status")
|
||||||
|
@crossdomain(origin="*")
|
||||||
|
def v0_status():
|
||||||
|
return jsonify(build="devel", api="0", db_version="0", schema_version="0", db_supported=False)
|
@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
#
|
#
|
||||||
# lorax
|
# lorax
|
||||||
#
|
#
|
||||||
|
115
src/sbin/lorax-composer
Executable file
115
src/sbin/lorax-composer
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# lorax-composer
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("lorax-composer")
|
||||||
|
program_log = logging.getLogger("program")
|
||||||
|
pylorax_log = logging.getLogger("pylorax")
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from gevent.wsgi import WSGIServer
|
||||||
|
|
||||||
|
from pylorax import vernum
|
||||||
|
from pylorax.api.server import server
|
||||||
|
|
||||||
|
VERSION = "{0}-{1}".format(os.path.basename(sys.argv[0]), vernum)
|
||||||
|
|
||||||
|
def get_parser():
|
||||||
|
""" Return the ArgumentParser for lorax-composer"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser( description="Composer API Server",
|
||||||
|
fromfile_prefix_chars="@" )
|
||||||
|
|
||||||
|
parser.add_argument("--host", default="127.0.0.1", metavar="HOST",
|
||||||
|
help="Host or IP to bind to (127.0.0.1)")
|
||||||
|
parser.add_argument("--port", default=4000, metavar="PORT",
|
||||||
|
help="Port to bind to (4000)")
|
||||||
|
parser.add_argument("--log", dest="logfile", default="/var/log/lorax-composer/composer.log", metavar="LOG",
|
||||||
|
help="Path to logfile (/var/log/lorax-composer/composer.log)")
|
||||||
|
parser.add_argument("--mockfiles", default="/var/tmp/bdcs-mockfiles/", metavar="MOCKFILES",
|
||||||
|
help="Path to JSON files used for /api/mock/ paths (/var/tmp/bdcs-mockfiles/)")
|
||||||
|
parser.add_argument("--bdcs", default="/mddb/cs.repo", metavar="BDCS",
|
||||||
|
help="Path to the content store directory (/mddb/cs.repo)")
|
||||||
|
parser.add_argument("-V", action="store_true", dest="showver",
|
||||||
|
help="show program's version number and exit")
|
||||||
|
parser.add_argument("DB", metavar="DATABASE",
|
||||||
|
help="Path to the BDCS sqlite database")
|
||||||
|
parser.add_argument("RECIPES", metavar="RECIPES",
|
||||||
|
help="Path to the recipes")
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(logfile):
|
||||||
|
# Setup logging to console and to logfile
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
pylorax_log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
sh = logging.StreamHandler()
|
||||||
|
sh.setLevel(logging.INFO)
|
||||||
|
fmt = logging.Formatter("%(asctime)s: %(message)s")
|
||||||
|
sh.setFormatter(fmt)
|
||||||
|
log.addHandler(sh)
|
||||||
|
pylorax_log.addHandler(sh)
|
||||||
|
|
||||||
|
fh = logging.FileHandler(filename=logfile, mode="w")
|
||||||
|
fh.setLevel(logging.DEBUG)
|
||||||
|
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||||
|
fh.setFormatter(fmt)
|
||||||
|
log.addHandler(fh)
|
||||||
|
pylorax_log.addHandler(fh)
|
||||||
|
|
||||||
|
# External program output log
|
||||||
|
program_log.setLevel(logging.DEBUG)
|
||||||
|
logfile = os.path.abspath(os.path.dirname(logfile))+"/program.log"
|
||||||
|
fh = logging.FileHandler(filename=logfile, mode="w")
|
||||||
|
fh.setLevel(logging.DEBUG)
|
||||||
|
program_log.addHandler(fh)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# parse the arguments
|
||||||
|
opts = get_parser().parse_args()
|
||||||
|
|
||||||
|
if opts.showver:
|
||||||
|
print(VERSION)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
if not os.path.exists(opts.DB):
|
||||||
|
errors.append("Database, %s, doesn't exist." % opts.DB)
|
||||||
|
if not os.path.exists(opts.RECIPES):
|
||||||
|
errors.append("Recipe directory, %s, is missing." % opts.RECIPES)
|
||||||
|
|
||||||
|
for e in errors:
|
||||||
|
print("ERROR: " + e)
|
||||||
|
if errors:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
setup_logging(opts.logfile)
|
||||||
|
http_server = WSGIServer((opts.host, opts.port), server)
|
||||||
|
# The server writes directly to a file object, so point to our log directory
|
||||||
|
server_logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/server.log"
|
||||||
|
server_log = open(server_logfile, "w")
|
||||||
|
http_server.log = server_log
|
||||||
|
http_server.serve_forever()
|
Loading…
Reference in New Issue
Block a user