예제 #1
0
파일: etc.py 프로젝트: isomeric/isomer
def write_instance(instance_configuration):
    """Write a new or updated instance"""

    instance_name = instance_configuration["name"]

    instance_file = os.path.join(
        get_etc_instance_path(), instance_name + ".conf"
    )
    instance_directory = os.path.join(get_etc_instance_path(), instance_name)
    try:
        log("Configuration:", instance_configuration, pretty=True, lvl=debug)
        with open(instance_file, "w") as f:
            f.write(dumps(instance_configuration))
        log("Instance configuration stored.", lvl=debug)

        if not os.path.exists(instance_directory):
            os.mkdir(instance_directory)
            log("Instance configuration directory created.", lvl=debug)
    except PermissionError:
        log(
            "PermissionError: Could not write instance management configuration "
            "file or create instance configuration directory.",
            lvl=error,
        )
        abort(EXIT_NO_PERMISSION)
예제 #2
0
def _get_configuration(ctx):
    try:
        log("Configuration:", ctx.obj["config"], lvl=verbose, pretty=True)
        log("Instance:", ctx.obj["instance"], lvl=debug)
    except KeyError:
        log("Invalid configuration, stopping.", lvl=error)
        abort(EXIT_INVALID_CONFIGURATION)

    try:
        instance_configuration = ctx.obj["instances"][ctx.obj["instance"]]
        log("Instance Configuration:",
            instance_configuration,
            lvl=verbose,
            pretty=True)
    except NonExistentKey:
        log("Instance %s does not exist" % ctx.obj["instance"], lvl=warn)
        abort(EXIT_INSTANCE_UNKNOWN)
        return

    environment_name = instance_configuration["environment"]
    environment_config = instance_configuration["environments"][
        environment_name]

    ctx.obj["environment"] = environment_config

    ctx.obj["instance_configuration"] = instance_configuration
예제 #3
0
def _launch_service(ctx):
    """Actually enable and launch newly set up environment"""
    instance_name = ctx.obj["instance"]

    service_name = "isomer-" + instance_name + ".service"

    success, result = run_process("/", ["systemctl", "enable", service_name],
                                  sudo="root")

    if not success:
        log("Error activating service:",
            format_result(result),
            pretty=True,
            lvl=error)
        abort(5000)

    log("Launching service")

    success, result = run_process("/", ["systemctl", "start", service_name],
                                  sudo="root")

    if not success:
        log("Error activating service:",
            format_result(result),
            pretty=True,
            lvl=error)
        abort(5000)

    return True
예제 #4
0
def _get_credentials(username=None, password=None, dbhost=None):
    """Obtain user credentials by arguments or asking the user"""

    # Database salt
    system_config = dbhost.objectmodels["systemconfig"].find_one(
        {"active": True})

    try:
        salt = system_config.salt.encode("ascii")
    except (KeyError, AttributeError):
        log("No systemconfig or it is without a salt! "
            "Reinstall the system provisioning with"
            "iso install provisions -p system")
        abort(3)
        return

    if username is None:
        username = ask("Please enter username: "******"utf-8")
    except UnicodeDecodeError:
        password = password

    password_hash = bcrypt.hashpw(password, salt).decode('ascii')

    return username, password_hash
예제 #5
0
파일: remote.py 프로젝트: ri0t/isomer
def remote(ctx, name, install, platform, source, url, existing):
    """Remote instance control (Work in Progress!)"""

    ctx.obj["remote"] = name
    ctx.obj["platform"] = platform
    ctx.obj["source"] = source
    ctx.obj["url"] = url
    ctx.obj["existing"] = existing

    if ctx.invoked_subcommand == "add":
        return

    remotes = ctx.obj["remotes"] = load_remotes()

    if ctx.invoked_subcommand == "list":
        return

    # log('Remote configurations:', remotes, pretty=True)

    host_config = remotes.get(name, None)

    if host_config is None:
        log("Cannot proceed, remote unknown", lvl=error)
        abort(5000)

    ctx.obj["host_config"] = host_config

    if platform is None:
        platform = ctx.obj["host_config"].get("platform", "debian")
    ctx.obj["platform"] = platform

    spur_config = dict(host_config["login"])

    if spur_config["private_key_file"] == "":
        spur_config.pop("private_key_file")

    if spur_config["port"] != 22:
        log(
            "Warning! Using any port other than 22 is not supported right now.",
            lvl=warn,
        )

    spur_config.pop("port")

    shell = spur.SshShell(**spur_config)

    if install:
        success, result = run_process("/", ["iso", "info"], shell)
        if success:
            log("Isomer version on remote:", format_result(result))
        else:
            log('Use "remote install" for now')
            # if existing is None:
            #     get_isomer(source, url, '/root', shell=shell)
            #     destination = '/' + host_config['login']['username'] + '/repository'
            # else:
            #     destination = existing
            # install_isomer(platform, host_config.get('use_sudo', True), shell, cwd=destination)

    ctx.obj["shell"] = shell
예제 #6
0
파일: iso.py 프로젝트: ri0t/isomer
def main():
    """Try to load the tool and launch it. If it can't be loaded, try to install
    all required things first."""

    try:
        from isomer.tool.tool import isotool
    except ImportError as import_exception:
        print(
            "\033[1;33;41mIsomer startup error! Please check your Isomer/Python installation.\033[0m"
        )
        print(type(import_exception), ":", import_exception, "\n")

        if not ask(
                "Maybe the dependencies not installed, do you want to try to install them",
                default=False,
                data_type="bool",
                show_hint=True,
        ):
            abort(EXIT_CANNOT_IMPORT_TOOL)

        install_isomer()
        print("Please restart the tool now.")
        sys.exit()

    isotool(obj={}, auto_envvar_prefix="ISOMER")
예제 #7
0
파일: database.py 프로젝트: ri0t/isomer
def rename(ctx, source, destination, keep, clear_target):
    """Rename Mongodb databases"""

    from pymongo import MongoClient

    client = MongoClient(ctx.obj["dbhost"])

    if source not in client.list_database_names():
        log("Source database", source, "does not exist!", lvl=warn)
        abort(-1)

    database = client.admin
    log("Copying", source, "to", destination)

    if destination in client.list_database_names():
        log("Destination exists")
        if clear_target:
            log("Clearing")
            client.drop_database(destination)
        else:
            log("Not destroying existing data", lvl=warn)
            abort(-1)

    database.command("copydb", fromdb=source, todb=destination)

    if not keep:
        log("Deleting old database")
        client.drop_database(source)

    finish(ctx)
예제 #8
0
def install(ctx, **kwargs):
    """Install a new environment of an instance"""

    log("Installing instance")

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

    green = env["green"]["installed"]
    blue = env["blue"]["installed"]

    if green or blue:
        log(
            "At least one environment is installed or in a non-clear state.\n"
            "Please use 'iso instance upgrade' to upgrade an instance.",
            lvl=warn,
        )
        abort(50081)

    _clear_instance(ctx, force=kwargs["force"], clear=False, no_archive=True)

    _install_environment(ctx, **kwargs)

    ctx.obj["instance_configuration"]["source"] = kwargs["source"]
    ctx.obj["instance_configuration"]["url"] = kwargs["url"]

    write_instance(ctx.obj["instance_configuration"])

    _turnover(ctx, force=kwargs["force"])

    finish(ctx)
예제 #9
0
파일: system.py 프로젝트: isomeric/isomer
def configure(ctx):
    """Generate a skeleton configuration for Isomer (needs sudo)"""

    if os.path.exists(get_etc_path()):
        abort(EXIT_NOT_OVERWRITING_CONFIGURATION)
    ctx = create_configuration(ctx)

    finish(ctx)
예제 #10
0
def check_root():
    """Check if current user has root permissions"""

    if os.geteuid() != 0:
        log("If you installed into a virtual environment, don't forget to "
            "specify the interpreter binary for sudo, e.g:\n"
            "$ sudo /home/user/.virtualenv/isomer/bin/python3 iso")
        abort(EXIT_ROOT_REQUIRED)
예제 #11
0
def configure(ctx):
    """Generate a skeleton configuration for Isomer (needs sudo)"""

    if os.path.exists(os.path.join(get_etc_path(), "isomer.conf")):
        abort(EXIT_NOT_OVERWRITING_CONFIGURATION)
    ctx = create_configuration(ctx)
    write_configuration(ctx.obj['config'])

    finish(ctx)
예제 #12
0
def archive(ctx, force, dynamic):
    """Archive the specified or non-active environment"""

    result = _archive(ctx, force, dynamic)
    if result:
        log("Archived to '%s'" % result)
        finish(ctx)
    else:
        log("Could not archive.", lvl=error)
        abort(50060)
예제 #13
0
파일: etc.py 프로젝트: isomeric/isomer
def write_remote(remote):
    """Write a new or updated remote"""

    filename = os.path.join(get_etc_remote_path(), remote["name"] + ".conf")
    try:
        with open(filename, "w") as f:
            f.write(dumps(remote))
        log("Instance configuration stored.", lvl=debug)
    except PermissionError:
        log(
            "PermissionError: Could not write instance management configuration file",
            lvl=error,
        )
        abort(EXIT_NO_PERMISSION)
예제 #14
0
def remove(ctx, clear, no_archive):
    """Irrevocably remove a whole instance"""

    if clear:
        log("Destructively removing instance:", ctx.obj["instance"], lvl=warn)

    if not ask("Are you sure", default=False, data_type="bool"):
        abort(EXIT_USER_BAILED_OUT)

    if clear:
        _clear_instance(ctx, force=True, clear=clear, no_archive=no_archive)

    remove_instance(ctx.obj["instance"])
    finish(ctx)
예제 #15
0
def frontend(ctx, dev, rebuild, no_install, build_type):
    """Build and install frontend"""

    # TODO: Move this to the environment handling and deprecate it here

    installed = install_frontend(
        force_rebuild=rebuild,
        development=dev,
        install=not no_install,
        build_type=build_type,
    )
    if installed is True:
        finish(ctx)
    else:
        abort(EXIT_FRONTEND_BUILD_FAILED, ctx)
예제 #16
0
def create(ctx, instance_name):
    """Create a new instance"""

    if instance_name == "":
        instance_name = ctx.obj["instance"]

    if instance_name in ctx.obj["instances"]:
        abort(EXIT_INSTANCE_EXISTS)

    log("Creating instance:", instance_name)
    instance_configuration = instance_template
    instance_configuration["name"] = instance_name
    ctx.obj["instances"][instance_name] = instance_configuration

    write_instance(instance_configuration)
    finish(ctx)
예제 #17
0
def update_service(ctx, next_environment):
    """Updates the specified service configuration"""

    validated, message = validate_services(ctx)

    if not validated:
        log("Service configuration validation failed:", message, lvl=error)
        abort(EXIT_SERVICE_INVALID)

    init = ctx.obj["config"]["meta"]["init"]
    environment_config = ctx.obj["instance_configuration"]["environments"][
        next_environment]

    log("Updating %s configuration of instance %s to %s" %
        (init, ctx.obj["instance"], next_environment))
    log("New environment:", environment_config, pretty=True)

    # TODO: Add update for systemd
    # * Stop possibly running service (it should not be running, though!)
    # * Actually update service files

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

    env_path = "/var/lib/isomer/" + instance_name + "/" + next_environment

    log("Updating systemd service for %s (%s)" %
        (instance_name, next_environment))

    launcher = os.path.join(env_path, "repository/iso")
    executable = os.path.join(env_path, "venv/bin/python3") + " " + launcher
    executable += " --quiet --instance " + instance_name + " launch"

    definitions = {
        "instance": instance_name,
        "executable": executable,
        "environment": next_environment,
        "user_name": config["user"],
        "user_group": config["group"],
    }
    service_name = "isomer-" + instance_name + ".service"

    write_template(
        service_template,
        os.path.join("/etc/systemd/system/", service_name),
        definitions,
    )
예제 #18
0
def cert(ctx, selfsigned):
    """instance a local SSL certificate"""

    instance_configuration = ctx.obj["instance_configuration"]
    instance_name = ctx.obj["instance"]
    next_environment = get_next_environment(ctx)

    set_instance(instance_name, next_environment)

    if selfsigned:
        _instance_selfsigned(instance_configuration)
    else:
        log("This is work in progress")
        abort(55555)

        _instance_letsencrypt(instance_configuration)

    finish(ctx)
예제 #19
0
def install_environment_modules(ctx, source, force, urls, store_url):
    """Add and install a module only to a single environment

    Note: This does not modify the instance configuration, so this will not
    be permanent during upgrades etc.
    """

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

    next_environment = get_next_environment(ctx)
    user = instance_configuration["user"]
    installed = instance_configuration["environments"][next_environment][
        "installed"]

    if not installed:
        log("Please install the '%s' environment first." % next_environment,
            lvl=error)
        abort(50000)

    set_instance(instance_name, next_environment)

    for url in urls:
        result = _install_module(source,
                                 url,
                                 force=force,
                                 user=user,
                                 store_url=store_url)

        if result is False:
            log("Installation failed!", lvl=error)
            abort(50000)

        package_name, package_version = result

        descriptor = {"version": package_version, "source": source, "url": url}
        if store_url != DEFAULT_STORE_URL:
            descriptor["store_url"] = store_url
        instance_configuration["environments"][next_environment]["modules"][
            package_name] = descriptor

    write_instance(instance_configuration)

    finish(ctx)
예제 #20
0
def set_parameter(ctx, parameter, value, force):
    """Set a configuration parameter of an environment"""

    # TODO: Generalize and improve this.
    #  - To get less code redundancy (this is also in instance.py)
    #  - To be able to set lists, dicts and other types correctly

    log("Setting %s to %s" % (parameter, value))

    next_environment = get_next_environment(ctx)
    environment_configuration = ctx.obj["instance_configuration"][
        'environments'][next_environment]
    defaults = environment_template
    converted_value = None

    try:
        parameter_type = type(defaults[parameter])
        log(parameter_type, pretty=True, lvl=debug)

        if parameter_type == tomlkit.items.Integer:
            converted_value = int(value)
        elif parameter_type == bool:
            converted_value = value.upper() == "TRUE"
        else:
            converted_value = value
    except KeyError:
        log("Available parameters:", sorted(list(defaults.keys())))
        abort(EXIT_INVALID_PARAMETER)

    if converted_value is None:
        log("Converted value was None! Recheck the new config!", lvl=warn)

    environment_configuration[parameter] = converted_value
    log("New config:", environment_configuration, pretty=True, lvl=debug)

    ctx.obj["instances"][ctx.obj["instance"]]['environments'][
        next_environment] = environment_configuration

    if valid_configuration(ctx) or force:
        write_instance(ctx.obj["instances"][ctx.obj["instance"]])
        finish(ctx)
    else:
        log("New configuration would not be valid", lvl=critical)
        abort(EXIT_INVALID_CONFIGURATION)
예제 #21
0
파일: remote.py 프로젝트: ri0t/isomer
def set_parameter(ctx, login, parameter, value):
    """Set a configuration parameter of an instance"""

    log("Setting %s to %s" % (parameter, value))
    remote_config = ctx.obj["host_config"]
    defaults = remote_template

    converted_value = None

    try:
        if login:
            parameter_type = type(defaults["login"][parameter])
        else:
            parameter_type = type(defaults[parameter])

        log(parameter_type, pretty=True, lvl=verbose)

        if parameter_type == tomlkit.api.Integer:
            converted_value = int(value)
        else:
            converted_value = value
    except KeyError:
        log(
            "Invalid parameter specified. Available parameters:",
            sorted(list(defaults.keys())),
            lvl=warn,
        )
        abort(EXIT_INVALID_PARAMETER)

    if converted_value is None:
        abort(EXIT_INVALID_VALUE)

    if login:
        remote_config["login"][parameter] = converted_value
    else:
        remote_config[parameter] = converted_value

    log("New config:", remote_config, pretty=True, lvl=debug)

    ctx.obj["remotes"][ctx.obj["remote"]] = remote_config

    write_remote(remote_config)
예제 #22
0
def info_instance(ctx):
    """Print information about the selected instance"""

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

    environment_name = instance_configuration["environment"]
    environment_config = instance_configuration["environments"][
        environment_name]

    if instance_name not in instances:
        log("Instance %s unknown!" % instance_name, lvl=warn)
        abort(EXIT_INSTANCE_UNKNOWN)

    log("Instance configuration:", instance_configuration, pretty=True)
    log("Active environment (%s):" % environment_name,
        environment_config,
        pretty=True)

    finish(ctx)
예제 #23
0
def check_instance(ctx):
    """Check health of the selected instance"""

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

    environment_name = instance_configuration["environment"]
    environment_config = instance_configuration["environments"][
        environment_name]

    if instance_name not in instances:
        log("Instance %s unknown!" % instance_name, lvl=warn)
        abort(EXIT_INSTANCE_UNKNOWN)

    # TODO: Validate instance config

    _check_environment(ctx, "blue")
    _check_environment(ctx, "green")

    finish(ctx)
예제 #24
0
def set_parameter(ctx, parameter, value):
    """Set a configuration parameter of an instance"""

    log("Setting %s to %s" % (parameter, value))
    instance_configuration = ctx.obj["instance_configuration"]
    defaults = instance_template
    converted_value = None

    path = parameter.split(".")

    try:
        default = nested_map_find(defaults, path)
        parameter_type = type(default)
        log(parameter_type, pretty=True, lvl=debug)

        if parameter_type == tomlkit.items.Integer:
            converted_value = int(value)
        elif parameter_type == bool:
            converted_value = value.upper() == "TRUE"
        else:
            converted_value = value
    except KeyError:
        log("Available parameters:", sorted(list(defaults.keys())))
        abort(EXIT_INVALID_PARAMETER)

    if converted_value is None:
        log("Converted value was None! Recheck the new config!", lvl=warn)

    nested_map_update(instance_configuration, converted_value, path)
    #instance_configuration[parameter] = converted_value
    log("New config:", instance_configuration, pretty=True, lvl=debug)

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

    if valid_configuration(ctx):
        write_instance(instance_configuration)
        finish(ctx)
    else:
        log("New configuration would not be valid", lvl=critical)
        abort(EXIT_INVALID_CONFIGURATION)
예제 #25
0
def install_instance_modules(ctx, source, urls, install_env, force, store_url):
    """Add (and optionally immediately install) modules for an instance.

    This will add them to the instance's configuration, so they will be upgraded as well
    as reinstalled on other environment changes.

    If you're installing from a store, you can specify a custom store URL with the
    --store-url argument.
    """

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

    for url in urls:
        descriptor = [source, url]
        if store_url != DEFAULT_STORE_URL:
            descriptor.append(store_url)
        if descriptor not in instance_configuration["modules"]:
            instance_configuration["modules"].append(descriptor)
        elif not force:
            log("Module %s is already installed. Use --force to install anyway."
                % url)
            abort(50000)

    write_instance(instance_configuration)

    if install_env is True:
        next_environment = get_next_environment(ctx)
        environments = instance_configuration['environments']

        if environments[next_environment]["installed"] is False:
            log("Environment %s is not installed, cannot install modules." %
                next_environment,
                lvl=warn)
            abort(50600)
            return
        del ctx.params["install_env"]
        ctx.forward(install_environment_modules)

    finish(ctx)
예제 #26
0
def _system_all(ctx):

    use_sudo = ctx.obj["use_sudo"]

    config_path = get_etc_path()
    log("Generating configuration at", config_path)
    if os.path.exists(config_path):
        abort(EXIT_NOT_OVERWRITING_CONFIGURATION)
    ctx = create_configuration(ctx)

    log("Installing Isomer system wide")
    install_isomer(ctx.obj["platform"],
                   use_sudo,
                   show=ctx.obj["log_actions"],
                   omit_platform=ctx.obj['omit_platform'],
                   omit_common=True)

    log("Adding Isomer system user")
    _add_system_user(use_sudo)

    log("Creating Isomer filesystem locations")
    _create_system_folders(use_sudo)
예제 #27
0
def _turnover(ctx, force):
    """Internal turnover operation"""

    # if ctx.obj['acting_environment'] is not None:
    #    next_environment = ctx.obj['acting_environment']
    # else:
    next_environment = get_next_environment(ctx)

    log("Activating environment:", next_environment)
    env = ctx.obj["instance_configuration"]["environments"][next_environment]

    log("Inspecting new environment")

    if not force:
        if env.get("database", "") == "":
            log("Database has not been set up correctly.", lvl=critical)
            abort(EXIT_INSTALLATION_FAILED)

        if (not env.get("installed", False) or not env.get("tested", False)
                or not env.get("migrated", False)):
            log("Installation failed, cannot activate!", lvl=critical)
            abort(EXIT_INSTALLATION_FAILED)

    update_service(ctx, next_environment)

    ctx.obj["instance_configuration"]["environment"] = next_environment

    write_instance(ctx.obj["instance_configuration"])

    # TODO: Effect reload of service
    #  * Systemctl reload
    #  * (Re)start service
    #  * confirm correct operation
    #   - if not, switch back to the other instance, maybe indicate a broken
    #     state for next_environment
    #   - if yes, Store instance configuration and terminate, we're done

    log("Turned instance over to", next_environment)
예제 #28
0
def create_module(clear_target, target):
    """Creates a new template Isomer plugin module"""

    if os.path.exists(target):
        if clear_target:
            shutil.rmtree(target)
        else:
            log("Target exists! Use --clear to delete it first.",
                emitter="MANAGE")
            abort(2)

    done = False
    info = None

    while not done:
        info = _ask_questionnaire()
        pprint(info)
        done = ask("Is the above correct", default="y", data_type="bool")

    augmented_info = _augment_info(info)

    log("Constructing module %(plugin_name)s" % info)
    _construct_module(augmented_info, target)
예제 #29
0
def _get_versions(ctx, source, url, fetch):

    instance_configuration = ctx.obj["instance_configuration"]

    source = source if source is not None else instance_configuration["source"]

    releases = {}

    if source == "github":
        releases = get_github_releases()
    elif source == "pypi":
        releases = get_pypi_releases()
    elif source == "git":
        if url != "":
            repo = url
        else:
            repo = instance_configuration["url"]
        releases = get_git_releases(repo, fetch)
    else:
        log("Other methods to acquire versions than github are currently WiP")
        abort(60001)

    return releases
예제 #30
0
def _instance_letsencrypt(instance_configuration):
    hostnames = instance_configuration.get("web", {}).get("hostnames", False)
    hostnames = hostnames.replace(" ", "")

    if not hostnames or hostnames == "localhost":
        log(
            "Please configure the public fully qualified domain names of this instance.\n"
            "Use 'iso instance set web_hostnames your.hostname.tld' to do that.\n"
            "You can add multiple names by separating them with commas.",
            lvl=error,
        )
        abort(50031)

    contact = instance_configuration.get("contact", False)
    if not contact:
        log(
            "You need to specify a contact mail address for this instance to generate certificates.\n"
            "Use 'iso instance set contact [email protected]' to do that.",
            lvl=error,
        )
        abort(50032)

    success, result = run_process(
        "/",
        [
            "certbot",
            "--nginx",
            "certonly",
            "-m",
            contact,
            "-d",
            hostnames,
            "--agree-tos",
            "-n",
        ],
    )
    if not success:
        log(
            "Error getting certificate:",
            format_result(result),
            pretty=True,
            lvl=error,
        )
        abort(50033)