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:
Brian C. Lane 2017-10-04 17:03:08 -07:00
parent 798023b199
commit 2008a413b6
6 changed files with 275 additions and 1 deletions

View 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"]

View 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
View 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
View 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)

View File

@ -1,5 +1,4 @@
#!/usr/bin/python
#
# lorax
#

115
src/sbin/lorax-composer Executable file
View 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()