def rbac(ctx, schema, object_filter, action, role, all_schemata): """[GROUP] Role based access control""" database = ctx.obj["db"] if schema is None: if all_schemata is False: log("No schema given. Read the RBAC group help", lvl=warn) sys.exit() else: schemata = database.objectmodels.keys() else: schemata = [schema] things = [] if object_filter is None: parsed_filter = {} else: parsed_filter = json.loads(object_filter) for schema in schemata: for obj in database.objectmodels[schema].find(parsed_filter): things.append(obj) if len(things) == 0: log("No objects matched the criteria.", lvl=warn) sys.exit() ctx.obj["objects"] = things ctx.obj["action"] = action ctx.obj["role"] = role
def check_environment(ctx, dev): """General fitness tests of the built environment""" if _check_environment(ctx, dev=dev): log("Environment seems healthy") finish(ctx)
def modules(wip): """Install the plugin modules""" # TODO: Remove altogether, this should be done via instance/environment only install_modules(wip) log("Done: Install Modules")
def modify(ctx, schema, uuid, object_filter, field, value): """Modify field values of objects""" database = ctx.obj["db"] model = database.objectmodels[schema] obj = None if uuid: obj = model.find_one({"uuid": uuid}) elif object_filter: obj = model.find_one(literal_eval(object_filter)) else: log("No object uuid or filter specified.", lvl=error) if obj is None: log("No object found", lvl=error) return log("Object found, modifying", lvl=debug) try: new_value = literal_eval(value) except ValueError: log("Interpreting value as string") new_value = str(value) obj._fields[field] = new_value obj.validate() log("Changed object validated", lvl=debug) obj.save() finish(ctx)
def browser(ctx): """Tries to start or point a browser towards this instance's frontend""" instance_configuration = ctx.obj["instance_configuration"] server = instance_configuration.get("webserver", "internal") protocol = "http" if server != "internal": protocol += "s" host = instance_configuration.get( "web_hostname", instance_configuration.get("web", {}).get("address", "127.0.0.1")) port = instance_configuration.get("web", {}).get("port", None) if port is None: url = "%s://%s" % (protocol, host) else: url = "%s://%s:%i" % (protocol, host, port) log("Opening browser to:", url) log("If this is empty or unreachable, check your instance with:\n" " iso instance info\n" "Also try the health checks:\n" " iso instance check") webbrowser.open(url)
def view(ctx, schema, uuid, object_filter): """Show stored objects""" database = ctx.obj["db"] if schema is None: log("No schema given. Read the help", lvl=warn) return model = database.objectmodels[schema] if uuid: obj = model.find({"uuid": uuid}) elif object_filter: obj = model.find(literal_eval(object_filter)) else: obj = model.find() if obj is None or model.count() == 0: log("No objects found.", lvl=warn) for item in obj: pprint(item._fields) finish(ctx)
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)
def validate(ctx, schema, all_schemata): """Validates all objects or all objects of a given schema.""" database = ctx.obj["db"] if schema is None: if all_schemata is False: log("No schema given. Read the help", lvl=warn) return else: schemata = database.objectmodels.keys() else: schemata = [schema] for schema in schemata: try: things = database.objectmodels[schema] with click.progressbar( things.find(), length=things.count(), label="Validating %15s" % schema ) as object_bar: for obj in object_bar: obj.validate() except Exception as e: log( "Exception while validating:", schema, e, type(e), "\n\nFix this object and rerun validation!", emitter="MANAGE", lvl=error, ) finish(ctx)
def info(ctx): """Shows information about the selected remote""" if ctx.obj["host_config"]["login"]["password"] != "": ctx.obj["host_config"]["login"]["password"] = "******" log("Remote %s:" % ctx.obj["remote"], ctx.obj["host_config"], pretty=True)
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
def list_all(ctx): """List all available Mongo Databases on the configured database host.""" from pymongo import MongoClient client = MongoClient(ctx.obj["dbhost"]) log(client.list_database_names()) finish(ctx)
def _install_backend(ctx): """Installs the backend into an environment""" instance_name = ctx.obj["instance"] env = get_next_environment(ctx) set_instance(instance_name, env) log("Installing backend on", env, lvl=debug) env_path = get_path("lib", "") user = ctx.obj["instance_configuration"]["user"] success, result = run_process( os.path.join(env_path, "repository"), [ os.path.join(env_path, "venv", "bin", "python3"), "setup.py", "develop" ], sudo=user, ) if not success: output = str(result) if "was unable to detect version" in output: log( "Installing from dirty repository. This might result in dependency " "version problems!", lvl=hilight, ) else: log( "Something unexpected happened during backend installation:\n", result, lvl=hilight, ) # TODO: Another fault might be an unclean package path. # But i forgot the log message to check for. # log('This might be a problem due to unclean installations of Python' # ' libraries. Please check your path.') log("Installing requirements") success, result = run_process( os.path.join(env_path, "repository"), [ os.path.join(env_path, "venv", "bin", "pip3"), "install", "-r", "requirements.txt", ], sudo=user, ) if not success: log(format_result(result), lvl=error) return True
def remove_instance(instance_configuration): """Remove the configuration file for an instance""" filename = os.path.join(get_etc_instance_path(), instance_configuration + ".conf") if os.path.exists(filename): log("Removing instance", instance_configuration) os.remove(filename) else: log("Instance not found.")
def versions(ctx, source, url, fetch): """Check instance sources for installable versions""" releases = _get_versions(ctx, source, url, fetch) releases_keys = sorted_alphanumerical(releases.keys()) log("Available Isomer releases:", releases_keys, pretty=True) log("Latest:", releases_keys[-1])
def load_instance(instance): """Read a single instance configuration""" file = os.path.join(get_etc_instance_path(), instance + ".conf") with open(file) as f: config = loads(f.read()) log("Instance configuration'", instance, "'loaded.", lvl=debug) return config
def create_user(ctx): """Creates a new local user""" try: new_user = _create_user(ctx) new_user.save() finish(ctx) except KeyError: log("User already exists", lvl=warn)
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)
def test(ctx): """Run and return info command on a remote""" shell = ctx.obj["shell"] username = ctx.obj["host_config"]["login"]["username"] success, result = run_process(get_remote_home(username), ["iso", "-nc", "version"], shell=shell) log(success, "\n", format_result(result), pretty=True)
def create_admin(ctx): """Creates a new local user and assigns admin role""" try: admin = _create_user(ctx) admin.roles.append("admin") admin.save() finish(ctx) except KeyError: log("User already exists", lvl=warn)
def load_remotes(): """Read the remote system configurations""" config = {} for root, _, files in os.walk(get_etc_remote_path()): for file in files: with open(os.path.join(root, file)) as f: config[file.rstrip(".conf")] = loads(f.read()) log("Remote configuration loaded.", lvl=debug) return config
def enable(ctx): """Enable an existing user""" if ctx.obj["username"] is None: log('Specify the username with "iso db user --username ..."') return change_user = (ctx.obj["db"].objectmodels["user"].find_one( {"name": ctx.obj["username"]})) change_user.active = True change_user.save() finish(ctx)
def dependencies(ctx): """Install Isomer platform dependencies""" log("Installing platform dependencies") install_isomer( ctx.obj["platform"], ctx.obj["use_sudo"], show=ctx.obj["log_actions"], omit_platform=ctx.obj['platform'], omit_common=True, ) finish(ctx)
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
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)
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)
def load_configuration(): """Read the main system configuration""" filename = os.path.join(get_etc_path(), "isomer.conf") try: with open(filename, "r") as f: config = loads(f.read()) log("Isomer configuration loaded.", lvl=debug) except FileNotFoundError: log("Configuration not found.", lvl=warn) return None return config
def _add_system_user(use_sudo=False): """instance Isomer system user (isomer.isomer)""" command = [ "/usr/sbin/adduser", "--system", "--quiet", "--home", "/var/run/isomer", "--group", "--disabled-password", "--disabled-login", "isomer", ] success, output = run_process("/", command, sudo=use_sudo) if success is False: log("Error adding system user:"******"/usr/sbin/adduser", "isomer", "dialout"] success, output = run_process("/", command, sudo=use_sudo) if success is False: log("Error adding system user to dialout group:", lvl=error) log(output, lvl=error) time.sleep(2)
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)
def format_template(template, content): """Render a given pystache template with given content""" import pystache result = u"" try: result = pystache.render(template, content, string_encoding="utf-8") except (ValueError, KeyError) as e: log("Templating error:", e, type(e), exc=True, lvl=error) # pprint(result) return result
def events(): """List all known authorized and anonymous events""" from isomer.events.system import ( get_anonymous_events, get_user_events, populate_user_events, ) populate_user_events() event_list = {**get_user_events(), **get_anonymous_events()} log("Events:\n", event_list, pretty=True)