Esempio n. 1
0
def launch(ctx, run=True, **args):
    """Assemble and run an Isomer instance"""

    instance_name = ctx.obj["instance"]
    instance = load_instance(instance_name)
    environment_name = ctx.obj["environment"]

    isolog("Launching instance %s - (%s)" % (instance_name, environment_name),
           emitter="CORE",
           lvl=debug)

    database_host = ctx.obj["dbhost"]
    database_name = ctx.obj["dbname"]

    if ctx.params["live_log"] is True:
        from isomer import logger

        logger.live = True

    if args["web_certificate"] is not None:
        isolog(
            "Warning! Using SSL on the backend is currently not recommended!",
            lvl=critical,
            emitter="CORE",
        )

    isolog("Initializing database access", emitter="CORE", lvl=debug)
    initialize(database_host, database_name, instance_name)
    isolog("Setting instance paths", emitter="CORE", lvl=debug)
    set_instance(instance_name, environment_name)

    server = construct_graph(ctx, instance_name, instance, args)
    if run and not args["no_run"]:
        server.run()

    return server
Esempio n. 2
0
def cli(
    ctx,
    instance,
    env,
    quiet,
    no_colors,
    console_level,
    file_level,
    no_log,
    log_path,
    log_file,
    dbhost,
    dbname,
    prefix_path,
    config_path,
    fat_logo,
):
    """Isomer Management Tool

    This tool supports various operations to manage Isomer instances.

    Most of the commands are grouped. To obtain more information about the
    groups' available sub commands/groups, try

    iso [group]

    To display details of a command or its subgroups, try

    iso [group] [subgroup] [..] [command] --help

    To get a map of all available commands, try

    iso cmdmap
    """

    ctx.obj["quiet"] = quiet

    def _set_verbosity():
        if quiet:
            console_setting = 100
        else:
            console_setting = int(
                console_level if console_level is not None else 20)

        if no_log:
            file_setting = 100
        else:
            file_setting = int(file_level if file_level is not None else 20)

        global_setting = min(console_setting, file_setting)
        set_verbosity(global_setting, console_setting, file_setting)

    def _set_logger():
        if log_path is not None or log_file is not None:
            set_logfile(log_path, instance, log_file)

        if no_colors is False:
            set_color()

    _set_verbosity()
    _set_logger()

    ctx.obj["instance"] = instance

    log("Running with Python",
        sys.version.replace("\n", ""),
        sys.platform,
        lvl=verbose)
    log("Interpreter executable:", sys.executable, lvl=verbose)

    set_etc_path(config_path)
    configuration = load_configuration()

    if configuration is not None:
        ctx.obj["config"] = configuration
    else:
        if ctx.invoked_subcommand not in ("version", "cmdmap"):
            log(
                "No configuration found. Most commands won't work. "
                "Use 'iso system configure' to generate a configuration.",
                lvl=warn)
        return

    set_prefix_path(configuration['meta']['prefix'])

    instances = load_instances()

    ctx.obj["instances"] = instances

    if instance not in instances:
        log(
            "No instance configuration called %s found! Using fresh defaults."
            % instance,
            lvl=warn,
        )
        instance_configuration = instance_template
    else:
        instance_configuration = instances[instance]

    if file_level is None and console_level is None:
        instance_log_level = int(instance_configuration["loglevel"])

        set_verbosity(instance_log_level, file_level=instance_log_level)
        log("Instance log level set to", instance_log_level, lvl=verbose)

    ctx.obj["instance_configuration"] = instance_configuration

    instance_environment = instance_configuration["environment"]

    if env is not None:
        if env == "current":
            ctx.obj["acting_environment"] = instance_environment
        elif env == "other":
            ctx.obj["acting_environment"] = ("blue" if instance_environment
                                             == "green" else "blue")
        else:
            ctx.obj["acting_environment"] = env
        env = ctx.obj["acting_environment"]
    else:
        env = instance_configuration["environment"]
        ctx.obj["acting_environment"] = None

    def get_environment_toggle(platform, toggles):
        """Checks well known methods to determine if the other environment should be
        booted instead of the default environment."""
        def temp_file_toggle():
            """Check by looking for a state file in /tmp"""

            state_filename = "/tmp/isomer_toggle_%s" % instance_configuration[
                "name"]
            log("Checking for override state file ", state_filename, lvl=debug)

            if os.path.exists(state_filename):
                log("Environment override state file found!", lvl=warn)
                return True
            else:
                log("Environment override state file not found", lvl=debug)
                return False

        def gpio_switch_toggle():
            """Check by inspection of a GPIO pin for a closed switch"""

            log("Checking for override GPIO switch on channel ",
                RPI_GPIO_CHANNEL,
                lvl=debug)

            if platform != "rpi":
                log(
                    "Environment toggle: "
                    "GPIO switch can only be handled on Raspberry Pi!",
                    lvl=critical)
                return False
            else:
                try:
                    import RPi.GPIO as GPIO
                except ImportError:
                    log(
                        "RPi Python module not found. "
                        "This only works on a Raspberry Pi!",
                        lvl=critical)
                    return False

                GPIO.setmode(GPIO.BOARD)
                GPIO.setup(RPI_GPIO_CHANNEL, GPIO.IN)

                state = GPIO.input(RPI_GPIO_CHANNEL) is True

                if state:
                    log("Environment override switch active!", lvl=warn)
                else:
                    log("Environment override switch not active", lvl=debug)

                return state

        toggle = False
        if "temp_file" in toggles:
            toggle = toggle or temp_file_toggle()
        if "gpio_switch" in toggles:
            toggle = toggle or gpio_switch_toggle()

        if toggle:
            log("Booting other Environment per user request.")
        else:
            log("Booting active environment", lvl=debug)

        return toggle

    #log(configuration['meta'], pretty=True)
    #log(instance_configuration, pretty=True)

    if get_environment_toggle(configuration["meta"]["platform"],
                              instance_configuration['environment_toggles']):
        if env == 'blue':
            env = 'green'
        else:
            env = 'blue'

    ctx.obj["environment"] = env

    if not fat_logo:
        log("<> Isomer", version_info, " [%s|%s]" % (instance, env), lvl=99)
    else:
        from isomer.misc import logo

        pad = len(logo.split("\n", maxsplit=1)[0])
        log(("Isomer %s" % version_info).center(pad), lvl=99)
        for line in logo.split("\n"):
            log(line, lvl=99)

    if dbname is None:
        dbname = instance_configuration["environments"][env]["database"]
        if dbname in ("", None) and ctx.invoked_subcommand in (
                "config",
                "db",
                "environment",
                "plugin",
        ):
            log(
                "Database for this instance environment is unset, "
                "you probably have to install the environment first.",
                lvl=warn,
            )

    if dbhost is None:
        dbhost = "%s:%i" % (
            instance_configuration["database_host"],
            instance_configuration["database_port"],
        )

    ctx.obj["dbhost"] = dbhost
    ctx.obj["dbname"] = dbname

    set_instance(instance, env, prefix_path)

    if log_path is None and log_file is None:
        log_path = get_log_path()

        set_logfile(log_path, instance, log_file)
Esempio n. 3
0
def _archive(ctx, force=False, dynamic=False):
    instance_configuration = ctx.obj["instance_configuration"]

    next_environment = get_next_environment(ctx)

    env = instance_configuration["environments"][next_environment]

    log("Instance info:",
        instance_configuration,
        next_environment,
        pretty=True,
        lvl=debug)
    log("Installed:", env["installed"], "Tested:", env["tested"], lvl=debug)

    if (not env["installed"] or not env["tested"]) and not force:
        log("Environment has not been installed - not archiving.", lvl=warn)
        return False

    log("Archiving environment:", next_environment)
    set_instance(ctx.obj["instance"], next_environment)

    timestamp = std_now().replace(":", "-").replace(".", "-")

    temp_path = mkdtemp(prefix="isomer_backup")

    log("Archiving database")
    if not dump(
            instance_configuration["database_host"],
            instance_configuration["database_port"],
            env["database"],
            os.path.join(temp_path, "db_" + timestamp + ".json"),
    ):
        if not force:
            log("Could not archive database.")
            return False

    archive_filename = os.path.join(
        "/var/backups/isomer/",
        "%s_%s_%s.tgz" % (ctx.obj["instance"], next_environment, timestamp),
    )

    try:
        shutil.copy(
            os.path.join(get_etc_instance_path(),
                         ctx.obj["instance"] + ".conf"),
            temp_path,
        )

        with tarfile.open(archive_filename, "w:gz") as f:
            if not dynamic:
                for item in locations:
                    path = get_path(item, "")
                    log("Archiving [%s]: %s" % (item, path))
                    f.add(path)
            f.add(temp_path, "db_etc")
    except (PermissionError, FileNotFoundError) as e:
        log("Could not archive environment:", e, lvl=error)
        if not force:
            return False
    finally:
        log("Clearing temporary backup target")
        shutil.rmtree(temp_path)

    ctx.obj["instance_configuration"]["environments"]["archive"][
        timestamp] = env

    log(ctx.obj["instance_configuration"])

    return archive_filename
Esempio n. 4
0
def _install_environment(
    ctx,
    source=None,
    url=None,
    import_file=None,
    no_sudo=False,
    force=False,
    release=None,
    upgrade=False,
    skip_modules=False,
    skip_data=False,
    skip_frontend=False,
    skip_test=False,
    skip_provisions=False,
):
    """Internal function to perform environment installation"""

    if url is None:
        url = source_url
    elif url[0] == '.':
        url = url.replace(".", os.getcwd(), 1)

    if url[0] == '/':
        url = os.path.abspath(url)

    instance_name = ctx.obj["instance"]
    instance_configuration = ctx.obj["instance_configuration"]

    next_environment = get_next_environment(ctx)

    set_instance(instance_name, next_environment)

    env = copy(instance_configuration["environments"][next_environment])

    env["database"] = instance_name + "_" + next_environment

    env_path = get_path("lib", "")

    user = instance_configuration["user"]

    if no_sudo:
        user = None

    log("Installing new other environment for %s on %s from %s in %s" %
        (instance_name, next_environment, source, env_path))

    try:
        result = get_isomer(source,
                            url,
                            env_path,
                            upgrade=upgrade,
                            sudo=user,
                            release=release)
        if result is False:
            log("Getting Isomer failed", lvl=critical)
            abort(50011, ctx)
    except FileExistsError:
        if not force:
            log(
                "Isomer already present, please safely clear or "
                "inspect the environment before continuing! Use --force to ignore.",
                lvl=warn,
            )
            abort(50012, ctx)
        else:
            log("Isomer already present, forcing through anyway.")

    try:
        repository = Repo(os.path.join(env_path, "repository"))

        log("Repo:", repository, lvl=debug)
        env["version"] = str(repository.git.describe())
    except (exc.InvalidGitRepositoryError, exc.NoSuchPathError,
            exc.GitCommandError):
        env["version"] = version
        log(
            "Not running from a git repository; Using isomer.version:",
            version,
            lvl=warn,
        )

    ctx.obj["instance_configuration"]["environments"][next_environment] = env

    # TODO: Does it make sense to early-write the configuration and then again later?
    write_instance(ctx.obj["instance_configuration"])

    log("Creating virtual environment")
    success, result = run_process(
        env_path,
        [
            "virtualenv", "-p", "/usr/bin/python3", "--system-site-packages",
            "venv"
        ],
        sudo=user,
    )
    if not success:
        log(format_result(result), lvl=error)

    try:
        if _install_backend(ctx):
            log("Backend installed")
            env["installed"] = True
        if not skip_modules and _install_modules(ctx):
            log("Modules installed")
            # env['installed_modules'] = True
        if not skip_provisions and _install_provisions(
                ctx, import_file=import_file):
            log("Provisions installed")
            env["provisioned"] = True
        if not skip_data and _migrate(ctx):
            log("Data migrated")
            env["migrated"] = True
        if not skip_frontend and _install_frontend(ctx):
            log("Frontend installed")
            env["frontend"] = True
        if not skip_test and _check_environment(ctx):
            log("Environment tested")
            env["tested"] = True
    except Exception:
        log("Error during installation:", exc=True, lvl=critical)

    log("Environment status now:", env)

    ctx.obj["instance_configuration"]["environments"][next_environment] = env

    write_instance(ctx.obj["instance_configuration"])
Esempio n. 5
0
def _clear_environment(ctx,
                       force=False,
                       clear_env=None,
                       clear=False,
                       no_archive=False):
    """Tests an environment for usage, then clears it

    :param ctx: Click Context
    :param force: Irrefutably destroy environment content
    :param clear_env: Environment to clear (Green/Blue)
    :param clear: Also destroy generated folders
    :param no_archive: Don't attempt to archive instance
    """

    instance_name = ctx.obj["instance"]

    if clear_env is None:
        next_environment = get_next_environment(ctx)
    else:
        next_environment = clear_env

    log("Clearing environment:", next_environment)
    set_instance(instance_name, next_environment)

    # log('Testing', environment, 'for usage')

    env = ctx.obj["instance_configuration"]["environments"][next_environment]

    if not no_archive:
        if not (_archive(ctx, force) or force):
            log("Archival failed, stopping.")
            abort(5000)

    log("Clearing env:", env, lvl=debug)

    for item in locations:
        path = get_path(item, "")
        log("Clearing [%s]: %s" % (item, path), lvl=debug)
        try:
            shutil.rmtree(path)
        except FileNotFoundError:
            log("Path not found:", path, lvl=debug)
        except PermissionError:
            log("No permission to clear environment", lvl=error)
            return False

    if not clear:
        _create_folders(ctx)

    try:
        delete_database(ctx.obj["dbhost"],
                        "%s_%s" % (instance_name, next_environment),
                        force=True)
    except pymongo.errors.ServerSelectionTimeoutError:
        log("No database available")
    except Exception as e:
        log("Could not delete database:", e, lvl=warn, exc=True)

    ctx.obj["instance_configuration"]["environments"][
        next_environment] = environment_template
    write_instance(ctx.obj["instance_configuration"])
    return True