def run_view(obj, namespace, function, args, post, upload, save_as): """Run a view in given plugin NAMESPACE and FUNCTION with ARGS. e.g. \b encapsia run view example_namespace test_view 3 tim limit=45 If an ARGS contains an "=" sign then send it as an optional query string argument. Otherwise send it as a URL path segment. """ api = lib.get_api(**obj) query_args = {} path_segments = [] for arg in args: if "=" in arg: left, right = arg.split("=", 1) query_args[left] = right else: path_segments.append(arg) result = api.run_view( namespace, function, view_arguments=path_segments, view_options=query_args, use_post=post, upload=upload, download=save_as, ) _log_result(result)
def make_from_encapsia(host: str) -> PluginInfos: # TODO: use pluginsmanager.plugins() if it exists api = lib.get_api(host=host) raw_info = lib.resilient_call( api.run_view, "pluginsmanager", "installed_plugins_with_tags", description= "api.run_view('pluginsmanager', 'installed_plugins_with_tags')", idempotent=True, ) pis = [] for i in raw_info: tags = i.get("plugin_tags") if not isinstance(tags, list): tags = [] try: variant = get_variant_from_tags(tags) except TooManyVariantTagsError as e: lib.log_error(f"Error in {i['name']} tag list: {e}") pi = PluginInfo.make_from_name_variant_version( i["name"], variant, i["version"]) pi.extras.update({ "description": i["description"], "installed": _format_datetime(i["when"]), "plugin-tags": ", ".join(sorted(tags)), }) pis.append(pi) return PluginInfos(pis)
def run_job(obj, namespace, function, args, upload, save_as): """Run a job in given plugin NAMESPACE and FUNCTION with ARGS. E.g. \b encapsia run job example_namespace test_module.test_function x=3 y=tim "z=hello" Note that all args must be named and the values are all considered strings (not least because arguments are encoded over a URL string). """ api = lib.get_api(**obj) params = {} for arg in args: left, right = arg.split("=", 1) params[left.strip()] = right.strip() result = lib.run_job( api, namespace, function, params, f"Running job {namespace}.{function}", upload=upload, download=save_as, ) _log_result(result)
def dev_update(obj, directory): """Update plugin parts which have changed since previous update. Optionally pass in the DIRECTORY of the plugin (defaults to cwd). """ directory = Path(directory) plugin_toml_path = directory / "plugin.toml" if not plugin_toml_path.exists(): lib.log_error("Not in a plugin directory.", abort=True) with _get_modified_plugin_directories( directory, reset=obj["plugins_force"]) as modified_plugin_directories: if modified_plugin_directories: with lib.temp_directory() as temp_directory: shutil.copy(plugin_toml_path, temp_directory) for modified_directory in modified_plugin_directories: lib.log(f"Including: {modified_directory}") shutil.copytree( directory / modified_directory, temp_directory / modified_directory, ) api = lib.get_api(**obj) result = lib.run_plugins_task( api, "dev_update_plugin", dict(), "Uploading to server", data=lib.create_targz_as_bytes(temp_directory), ) if not result: raise _PluginsTaskError else: lib.log("Nothing to do.")
def logs(obj, plugins): """Print the latest install logs for given plugins.""" api = lib.get_api(**obj) # Despite the name, this only fetches the latest log for each plugin, not all! raw_info = lib.resilient_call( api.run_view, "pluginsmanager", "all_plugin_logs", description="api.run_view('pluginsmanager', 'all_plugin_logs')", idempotent=True, ) if plugins: # Filter to specified plugins. raw_info = [i for i in raw_info if i["name"] in plugins] for i in raw_info: for f in ( "name", "version", "description", "action", "server", "success", "when", ): lib.log(f"{f.capitalize()}: {i[f]}") lib.log("Logs:") if i["success"]: lib.log_output(i["output"].strip()) else: lib.log_error(i["output"].strip()) lib.log("") if len(raw_info) == 0 and len(plugins) > 0: lib.log( "No logs found. Note that any plugins must be exact name matches without version info." )
def set(obj, key, value): """Store value against given key.""" api = lib.get_api(**obj) value = lib.parse(value, "json") lib.resilient_call( api.set_config, key, value, description=f"api.set_config({key}, <value>)" )
def get(obj, key): """Retrieve value against given key.""" api = lib.get_api(**obj) value = lib.resilient_call( api.get_config, key, description=f"api.get_config({key})", idempotent=True ) lib.pretty_print(value, "json")
def save(obj, output): """Save entire configuration to given file.""" api = lib.get_api(**obj) config = lib.resilient_call( api.get_all_config, description="api.get_all_config()", idempotent=True ) lib.pretty_print(config, "json", output=output)
def show(obj): """Show entire configuration.""" api = lib.get_api(**obj) config = lib.resilient_call( api.get_all_config, description="api.get_all_config()", idempotent=True ) lib.pretty_print(config, "json")
def create_fixture(obj, name): """Create new fixture with given name.""" api = lib.get_api(**obj) lib.log_output( lib.dbctl_action( api, "create_fixture", dict(name=name), f"Creating fixture {name}" ) )
def make_from_encapsia(host): api = lib.get_api(host=host) raw_info = api.run_view("pluginsmanager", "installed_plugins_with_tags") return PluginInfos([ PluginInfo.make_from_name_version(i["name"], i["version"]) for i in raw_info ])
def remove_task(obj, scheduled_task_id): """Remove scheduled task by id.""" api = lib.get_api(**obj) lib.run_plugins_task( api, "remove_scheduled_task", dict(scheduled_task_id=scheduled_task_id), "Removing scheduled tasks", )
def remove_tasks_in_namespace(obj, namespace): """Remove all scheduled tasks in given namespace.""" api = lib.get_api(**obj) lib.run_plugins_task( api, "remove_scheduled_tasks_in_namespace", dict(namespace=namespace), "Removing scheduled tasks", )
def dev_create(obj, namespace, n_task_workers): """Create namespace of given name. Only useful during developmment.""" api = lib.get_api(**obj) lib.run_plugins_task( api, "dev_create_namespace", dict(namespace=namespace, n_task_workers=n_task_workers), "Creating namespace", )
def delete_fixture(obj, name, yes): """Delete fixture with given name.""" if not yes: click.confirm(f'Are you sure you want to delete fixture "{name}"?', abort=True) api = lib.get_api(**obj) lib.log_output( lib.dbctl_action( api, "delete_fixture", dict(name=name), f"Deleting fixture {name}" ) )
def use_fixture(obj, name, yes): """Switch to fixture with given name.""" if not yes: click.confirm( f'Are you sure you want to change the database to fixture "{name}"?', abort=True, ) api = lib.get_api(**obj) poll, NoTaskResultYet = api.dbctl_action("use_fixture", dict(name=name)) lib.log(f"Requested change to fixture {name}.") lib.log("Please verify by other means (e.g. look at the logs).")
def uninstall(obj, namespace): """Uninstall named plugin.""" if not obj["force"]: click.confirm( f"Are you sure you want to uninstall the plugin " + f"(delete all!) from namespace {namespace}", abort=True, ) api = lib.get_api(**obj) lib.run_plugins_task(api, "uninstall_plugin", dict(namespace=namespace), f"Uninstalling {namespace}")
def status(obj, plugins): """Print information about (successfully) installed plugins.""" local_versions = PluginInfos.make_from_local_store( obj["plugins_local_dir"]).filter_to_latest() api = lib.get_api(**obj) raw_info = api.run_view("pluginsmanager", "installed_plugins_with_tags") plugin_infos = [] for i in raw_info: pi = PluginInfo.make_from_name_version(i["name"], i["version"]) available_pi = local_versions.latest_version_matching_spec(i["name"]) if available_pi: temp = available_pi.formatted_version() available = "<same>" if temp == pi.formatted_version() else temp else: available = "" pi.extras = { "description": i["description"], "plugin-tags": (", ".join(sorted(i["plugin_tags"])) if isinstance( i["plugin_tags"], list) else ""), "installed": _format_datetime(i["when"]), "available": available, } plugin_infos.append(pi) if plugins: plugin_infos = PluginInfos(plugin_infos).filter_to_specs(plugins).pis for i in raw_info: i["version"] = PluginInfo.make_from_name_version( i["name"], i["version"]).formatted_version() headers = [ "name", "version", "available", "description", "installed", "plugin-tags", ] info = ([ pi.name, pi.formatted_version(), pi.extras["available"], pi.extras["description"], pi.extras["installed"], pi.extras["plugin-tags"], ] for pi in plugin_infos) lib.log(tabulate(info, headers=headers)) _log_message_explaining_semver()
def extend(obj, lifespan): """Extend the lifespan of token and update encapsia credentials (if used).""" api = lib.get_api(**obj) new_token = api.login_again(lifespan=lifespan) host = get_host(obj) if host: store = CredentialsStore() url, old_token = store.get(host) store.set(host, url, new_token) lib.log("Encapsia credentials file updated.") else: lib.log(new_token)
def info(obj, logs): """Provide information about installed plugins.""" api = lib.get_api(**obj) raw_info = api.run_view("pluginsmanager", "installed_plugins_with_tags") headers = ["name", "version", "description", "when", "plugin_tags"] info = ([p[h] for h in headers] for p in raw_info) lib.log(tabulate(info, headers=headers)) if logs: for i in raw_info: name, output = i["name"], i["output"] lib.log(f"\n[Install log for {name}]") lib.log_output(output.strip())
def export_users_and_roles(obj, filename, with_roles): """Export users (and roles) to given TOML file.""" filename = Path(filename) api = lib.get_api(**obj) export_data = {} export_data["users"] = api.get_all_users() if with_roles: export_data["roles"] = api.get_all_roles() with filename.open(mode="w") as f: lib.pretty_print(export_data, "toml", f) lib.log_output( f"Exported {len(export_data['users'])} users and {len(export_data.get('roles', []))} roles to {filename}" )
def import_users_and_roles(obj, filename): """Import users (and roles) from given TOML file.""" filename = Path(filename) api = lib.get_api(**obj) import_data = lib.read_toml(filename) users = import_data.get("users", []) roles = import_data.get("roles", []) if users: api.post("users", json=users) if roles: api.post("roles", json=roles) lib.log_output( f"Imported {len(users)} users and {len(roles)} roles from {filename}")
def expire(obj): """Expire token from server, and update encapsia credentials if used.""" api = lib.get_api(**obj) try: api.delete("logout") lib.log("Expired token on server.") except EncapsiaApiError as e: lib.log_error("Failed to expire given token!") lib.log_error(str(e), abort=True) host = get_host(obj) if host: CredentialsStore().remove(host) lib.log("Removed entry from encapsia credentials file.")
def backup(obj, filename): """Backup database to given filename. (or create a temp one if not given).""" if filename: filename = Path(filename) api = lib.get_api(**obj) handle = lib.dbctl_action(api, "backup_database", dict(), "Backing up database") temp_filename = Path( api.dbctl_download_data(handle) ) # TODO remove Path() once encapsia_api switched over to pathlib if filename is None: filename = temp_filename else: temp_filename.rename(filename) lib.log(f"Downloaded {filename.stat().st_size} bytes to {filename}")
def shell(obj): """Launch an httpie interactive shell with passed-in credentials.""" try: importlib.import_module("http_prompt") except ModuleNotFoundError: lib.log_error("Please install http-prompt first.", abort=True) api = lib.get_api(**obj) argv = [ "http-prompt", api.url, f"Authorization: Bearer {api.token}", "Accept: application/json", ] subprocess.run(argv)
def uninstall(obj, show_logs, namespaces): """Uninstall named plugin(s).""" if namespaces and not obj["plugins_force"]: lib.log("Preparing to uninstall: " + ", ".join(namespaces)) click.confirm( "Are you sure?", abort=True, ) api = lib.get_api(**obj) for namespace in namespaces: lib.run_plugins_task( api, "uninstall_plugin", dict(namespace=namespace), f"Uninstalling {namespace}", print_output=show_logs, )
def restore(obj, filename, yes): """Restore database from given backup file.""" filename = Path(filename) if not yes: click.confirm( f'Are you sure you want to restore the database from "{filename}"?', abort=True, ) api = lib.get_api(**obj) handle = api.dbctl_upload_data(filename) # On a restore, the server is temporarily stopped. # This means that attempts to use it will generate a 500 error when # Nginx tries to check the permission. # Further, the current token may no longer work. poll, NoResultYet = api.dbctl_action("restore_database", dict(data_handle=handle)) lib.log("Database restore requested.") lib.log("Please verify by other means (e.g. look at the logs).")
def list_users(obj, super_users, system_users, all_users): """List out information about users.""" api = lib.get_api(**obj) if not (super_users or system_users or all_users): # If no specific type of user specified then assume all-users was intended. all_users = True if super_users: lib.log_output("[Super users]") users = api.get_super_users() headers = ["email", "first_name", "last_name"] lib.log_output( tabulate.tabulate( [[getattr(row, header) for header in headers] for row in users], headers=headers, )) lib.log_output() if system_users: lib.log_output("[System users]") users = api.get_system_users() headers = ["email", "description", "capabilities"] lib.log_output( tabulate.tabulate( [[getattr(row, header) for header in headers] for row in users], headers=headers, )) lib.log_output() if all_users: lib.log_output("[All users]") users = api.get_all_users() headers = [ "email", "first_name", "last_name", "role", "enabled", "is_site_user", ] lib.log_output( tabulate.tabulate([[row[header] for header in headers] for row in users], headers=headers)) lib.log_output()
def add_task(obj, description, task_host, task_token, namespace, task, params, cron, jitter): """Add new scheduled task.""" api = lib.get_api(**obj) lib.run_plugins_task( api, "add_scheduled_task", dict( description=description, host=task_host, token=task_token, namespace=namespace, task=task, params=params, cron=cron, jitter=jitter, ), "Adding scheduled task", )
def install(obj, versions, plugin_filenames): """Install plugins from files and/or an optional versions.toml file. Plugins named directly will be put in the cache before being installed. Plugins specified in the versions.toml file will be taken from the cache. """ plugins_cache_dir = obj["plugins_cache_dir"] api = lib.get_api(**obj) for plugin_filename in plugin_filenames: plugin_filename = Path(plugin_filename).resolve() _fetch_plugin_and_store_in_cache(plugin_filename.as_uri(), plugins_cache_dir, force=True) _install_plugin(api, plugins_cache_dir / plugin_filename.name) if versions: versions = Path(versions) for name, version in lib.read_toml(versions).items(): _install_plugin( api, plugins_cache_dir / f"plugin-{name}-{version}.tar.gz")