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