# -*- coding: utf-8 -*- # Copyright (c) 2016 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Written by Matt Prahl except for the test functions from __future__ import print_function from flask_script import Manager, prompt_bool from functools import wraps import flask_migrate import logging import os import getpass import textwrap from werkzeug.datastructures import FileStorage from module_build_service import app, conf, db, create_app from module_build_service import models from module_build_service.utils import ( submit_module_build_from_yaml, load_local_builds, load_mmd_file, import_mmd, import_builds_from_local_dnf_repos, ) from module_build_service.errors import StreamAmbigous import module_build_service.messaging import module_build_service.scheduler.consumer manager = Manager(create_app) help_args = ("-?", "--help") manager.help_args = help_args migrations_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'migrations') migrate = flask_migrate.Migrate(app, db, directory=migrations_dir) manager.add_command("db", flask_migrate.MigrateCommand) manager.add_option("-d", "--debug", dest="debug", action="store_true") manager.add_option("-v", "--verbose", dest="verbose", action="store_true") manager.add_option("-q", "--quiet", dest="quiet", action="store_true") def console_script_help(f): @wraps(f) def wrapped(*args, **kwargs): import sys if any([arg in help_args for arg in sys.argv[1:]]): command = os.path.basename(sys.argv[0]) print(textwrap.dedent( """\ {0} Usage: {0} [{1}] See also: mbs-manager(1) """).strip().format(command, "|".join(help_args)) ) sys.exit(2) r = f(*args, **kwargs) return r return wrapped @console_script_help @manager.command def upgradedb(): """ Upgrades the database schema to the latest revision """ app.config["SERVER_NAME"] = "localhost" # TODO: configurable? migrations_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "migrations") with app.app_context(): flask_migrate.upgrade(directory=migrations_dir) @manager.command def cleardb(): """ Clears the database """ models.ModuleBuild.query.delete() models.ComponentBuild.query.delete() @console_script_help @manager.command def import_module(mmd_file): """ Imports the module from mmd_file """ mmd = load_mmd_file(mmd_file) import_mmd(db.session, mmd) @manager.option("--stream", action="store", dest="stream") @manager.option("--file", action="store", dest="yaml_file") @manager.option("--srpm", action="append", default=[], dest="srpms", metavar="SRPM") @manager.option("--skiptests", action="store_true", dest="skiptests") @manager.option("--offline", action="store_true", dest="offline") @manager.option("-l", "--add-local-build", action="append", default=None, dest="local_build_nsvs") @manager.option("-s", "--set-stream", action="append", default=[], dest="default_streams") @manager.option( "-r", "--platform-repo-file", action="append", default=[], dest="platform_repofiles" ) @manager.option("-p", "--platform-id", action="store", default=None, dest="platform_id") def build_module_locally( local_build_nsvs=None, yaml_file=None, srpms=None, stream=None, skiptests=False, default_streams=None, offline=False, platform_repofiles=None, platform_id=None, ): """ Performs local module build using Mock """ if "SERVER_NAME" not in app.config or not app.config["SERVER_NAME"]: app.config["SERVER_NAME"] = "localhost" if app.config["RESOLVER"] == "db": raise ValueError( "Please set RESOLVER to 'mbs' in your configuration for local builds.") conf.set_item("system", "mock") conf.set_item("base_module_repofiles", platform_repofiles) # Use our own local SQLite3 database. confdir = os.path.abspath(os.getcwd()) dbdir = \ os.path.abspath(os.path.join(confdir, "..")) if confdir.endswith("conf") else confdir dbpath = "/{0}".format(os.path.join(dbdir, ".mbs_local_build.db")) dburi = "sqlite://" + dbpath app.config["SQLALCHEMY_DATABASE_URI"] = dburi conf.set_item("sqlalchemy_database_uri", dburi) if os.path.exists(dbpath): os.remove(dbpath) db.create_all() params = { "local_build": True, "default_streams": dict(ns.split(":") for ns in default_streams) } if srpms: params["srpms"] = srpms username = getpass.getuser() if not yaml_file or not yaml_file.endswith(".yaml"): raise IOError("Provided modulemd file is not a yaml file.") yaml_file_path = os.path.abspath(yaml_file) with models.make_db_session(conf) as db_session: if offline: import_builds_from_local_dnf_repos(db_session, platform_id) load_local_builds(db_session, local_build_nsvs) with open(yaml_file_path) as fd: filename = os.path.basename(yaml_file) handle = FileStorage(fd) handle.filename = filename try: modules_list = submit_module_build_from_yaml( db_session, username, handle, params, stream=str(stream), skiptests=skiptests ) except StreamAmbigous as e: logging.error(str(e)) logging.error("Use '-s module_name:module_stream' to choose the stream") return stop = module_build_service.scheduler.make_simple_stop_condition(db_session) # Run the consumer until stop_condition returns True module_build_service.scheduler.main([], stop) if any(module.state == models.BUILD_STATES["failed"] for module in modules_list): raise RuntimeError("Module build failed") @manager.option( "identifier", metavar="NAME:STREAM[:VERSION[:CONTEXT]]", help="Identifier for selecting module builds to retire", ) @manager.option( "--confirm", action="store_true", default=False, help="Perform retire operation without prompting", ) def retire(identifier, confirm=False): """ Retire module build(s) by placing them into 'garbage' state. """ # Parse identifier and build query parts = identifier.split(":") if len(parts) < 2: raise ValueError("Identifier must contain at least NAME:STREAM") if len(parts) >= 5: raise ValueError("Too many parts in identifier") filter_by_kwargs = {"state": models.BUILD_STATES["ready"], "name": parts[0], "stream": parts[1]} if len(parts) >= 3: filter_by_kwargs["version"] = parts[2] if len(parts) >= 4: filter_by_kwargs["context"] = parts[3] with models.make_db_session(conf) as db_session: # Find module builds to retire module_builds = db_session.query(models.ModuleBuild).filter_by(**filter_by_kwargs).all() if not module_builds: logging.info("No module builds found.") return logging.info("Found %d module builds:", len(module_builds)) for build in module_builds: logging.info("\t%s", ":".join((build.name, build.stream, build.version, build.context))) # Prompt for confirmation is_confirmed = confirm or prompt_bool("Retire {} module builds?".format(len(module_builds))) if not is_confirmed: logging.info("Module builds were NOT retired.") return # Retire module builds for build in module_builds: build.transition( db_session, conf, models.BUILD_STATES["garbage"], "Module build retired") logging.info("Module builds retired.") @console_script_help @manager.command def run(host=None, port=None, debug=None): """ Runs the Flask app, locally. """ host = host or conf.host port = port or conf.port debug = debug or conf.debug logging.info("Starting Module Build Service frontend") app.run(host=host, port=port, debug=debug) def manager_wrapper(): manager.run() if __name__ == "__main__": manager_wrapper()