PK!encapsia_cli/__init__.pyPK!fw{{encapsia_cli/add_superuser.py"""Create a superuser e.g. for bootstrapping.""" import re import click from encapsia_cli import lib # See http://www.regular-expressions.info/email.html EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") def validate_email(ctx, param, value): if not EMAIL_REGEX.match(value): raise click.BadParameter("Not a valid email address") return value @click.command() @click.argument("email", callback=validate_email) @click.argument("first_name") @click.argument("last_name") @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) def main(email, first_name, last_name, host, host_env_var, token_env_var): """Create superuser with given name and email. In addition to adding the given user, this will also add a Superuser role. """ api = lib.get_api(host, host_env_var, token_env_var) api.post( "roles", json=[ {"name": "Superuser", "alias": "Superuser", "capabilities": ["superuser"]} ], ) api.post( "users", json=[ { "email": email, "first_name": first_name, "last_name": last_name, "role": "Superuser", "enabled": True, "is_site_user": False, } ], ) PK!\.encapsia_cli/config.py"""Get/set server configuration.""" import click from encapsia_cli import lib @click.group() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) @click.pass_context def app(ctx, host, host_env_var, token_env_var): """Get/set server configuration (not *trial* configuration).""" ctx.obj["api"] = lib.get_api(host, host_env_var, token_env_var) @app.command() @click.pass_context def show(ctx): """Show entire configuration.""" api = ctx.obj["api"] lib.pretty_print(api.get_all_config(), "json") @app.command() @click.argument("output", type=click.File("w")) @click.pass_context def save(ctx, output): """Save entire configuration to given file.""" api = ctx.obj["api"] lib.pretty_print(api.get_all_config(), "json", output=output) @app.command() @click.argument("input", type=click.File("r")) @click.pass_context def load(ctx, input): """Load (merge) configuration from given file.""" api = ctx.obj["api"] data = lib.parse(input.read(), "json") api.set_config_multi(data) @app.command() @click.argument("key") @click.pass_context def get(ctx, key): """Retrieve value against given key.""" api = ctx.obj["api"] lib.pretty_print(api.get_config(key), "json") @app.command() @click.argument("key") @click.argument("value") @click.pass_context def set(ctx, key, value): """Store value against given key.""" api = ctx.obj["api"] value = lib.parse(value, "json") api.set_config(key, value) @app.command() @click.argument("key") @click.pass_context def delete(ctx, key): """Delete value against given key.""" api = ctx.obj["api"] api.delete_config(key) def main(): app(obj={}) PK! i>encapsia_cli/dbctl.py"""Encapsia Database control actions e.g. backups, restores, and fixtures.""" import os import os.path import sys import time import click from encapsia_api import EncapsiaApi from encapsia_cli import lib def visual_poll(message, poll, NoTaskResultYet, wait=0.5): out = sys.stdout out.write(message) out.flush() result = poll() while result is NoTaskResultYet: time.sleep(wait) out.write(".") out.flush() result = poll() out.write("Done\n") out.flush() return result def dbctl_action(host, token, name, params, message): api = EncapsiaApi(host, token) poll, NoTaskResultYet = api.dbctl_action(name, params) result = visual_poll(message, poll, NoTaskResultYet) if result["status"] != "ok": raise click.Abort() return result["result"] @click.group() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) @click.pass_context def main(ctx, host, host_env_var, token_env_var): """Low-level Encapsia Database control.""" host, token = lib.discover_credentials(host, host_env_var, token_env_var) ctx.obj = dict(host=host, token=token) @main.command() @click.pass_context def list_fixtures(ctx): """List available fixtures.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "list_fixtures", dict(), "Fetching list of fixtures...", ) ) @main.command() @click.argument("name") @click.pass_context def create_fixture(ctx, name): """Create new fixture with given name.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "create_fixture", dict(name=name), "Create fixture {}...".format(name), ) ) @main.command() @click.argument("name") @click.pass_context def use_fixture(ctx, name): """Switch to fixture with given name.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "use_fixture", dict(name=name), "Switching to fixture {}...".format(name), ) ) @main.command() @click.argument("name") @click.pass_context def delete_fixture(ctx, name): """Delete fixture with given name.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "delete_fixture", dict(name=name), "Deleting fixture {}...".format(name), ) ) @main.command() @click.argument("name") @click.pass_context def create_extension_schema(ctx, name): """Create extension schema.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "create_extension_schema", dict(name=name), "Creating schema {}...".format(name), ) ) @main.command() @click.argument("name") @click.pass_context def delete_extension_schema(ctx, name): """Delete extension schema.""" print( dbctl_action( ctx.obj["host"], ctx.obj["token"], "delete_extension_schema", dict(name=name), "Deleting schema {}...".format(name), ) ) @main.command() @click.argument("name") @click.argument("filename") @click.pass_context def load_extension_sql(ctx, name, filename): """Load SQL from given file into extension schema.""" api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) handle = api.dbctl_upload_data(filename) dbctl_action( ctx.obj["host"], ctx.obj["token"], "load_extension_sql", dict(name=name, data_handle=handle), "Loading SQL into schema {}...".format(name), ) @main.command() @click.argument("handle") @click.option( "--filename", default=None, help="Optional filename into which the data will be downloaded.", ) @click.pass_context def download_data(ctx, handle, filename): """Download data of given handle.""" api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) temp_filename = api.dbctl_download_data(handle) if filename is None: filename = temp_filename else: os.rename(temp_filename, filename) size = os.path.getsize(filename) print("Downloaded {} bytes to {}".format(size, filename)) @main.command() @click.argument("filename") @click.pass_context def upload_data(ctx, filename): """Upload data in given file, printing a handle for re-use.""" api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) handle = api.dbctl_upload_data(filename) size = os.path.getsize(filename) print("Uploaded {} bytes from {}".format(size, filename)) print("Handle: {}".format(handle)) return handle @main.command() @click.option( "--filename", default=None, help="Optional filename into which the data will be downloaded.", ) @click.pass_context def backup(ctx, filename): """Backup database to given filename (or temp one if not given).""" handle = dbctl_action( ctx.obj["host"], ctx.obj["token"], "backup_database", dict(), "Backing up database...", ) api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) temp_filename = api.dbctl_download_data(handle) if filename is None: filename = temp_filename else: os.rename(temp_filename, filename) size = os.path.getsize(filename) print("Downloaded {} bytes to {}".format(size, filename)) @main.command() @click.argument("filename") @click.pass_context def restore(ctx, filename): """Restore database from given backup file.""" api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) 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. api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) poll, NoTaskResultYet = api.dbctl_action( "restore_database", dict(data_handle=handle) ) print("Database restore requested.") print("Please verify by other means (e.g. look at the logs).") PK!K>>encapsia_cli/expire_token.py"""Expire Encapsia token from a server.""" import click from encapsia_api import CredentialsStore, EncapsiaApiError from encapsia_cli import lib @click.command() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) def main(host, host_env_var, token_env_var): """Expire token from server, and update encapsia credentials if used.""" api = lib.get_api(host, host_env_var, token_env_var) try: api.delete("logout") lib.log("Expired token on server.") except EncapsiaApiError as e: lib.error("Failed to expire given token!") lib.error(str(e)) raise click.Abort() if host: CredentialsStore().remove(host) lib.log("Removed entry from encapsia credentials file.") PK! encapsia_cli/lib.pyimport contextlib import datetime import io import json import os import shutil import subprocess import tarfile import tempfile import time from pathlib import Path import click import toml from encapsia_api import CredentialsStore, EncapsiaApi def error(message): click.secho(message, fg="red") def log(message, nl=True): click.secho(message, fg="yellow", nl=nl) def log_output(message): click.secho(message, fg="green") def get_env_var(name): try: return os.environ[name] except KeyError: error("Environment variable {} does not exist!".format(name)) raise click.Abort() def discover_credentials(name, host_env_var, token_env_var): if name: store = CredentialsStore() try: host, token = store.get(name) except KeyError: error(f"Cannot find entry for '{name}' in encapsia credentials file.") raise click.Abort() else: host, token = get_env_var(host_env_var), get_env_var(token_env_var) return host, token def get_api(name, host_env_var, token_env_var): host, token = discover_credentials(name, host_env_var, token_env_var) return EncapsiaApi(host, token) def get_utc_now_as_iso8601(): return str(datetime.datetime.utcnow()) @contextlib.contextmanager def temp_directory(): """Context manager for creating a temporary directory. Cleans up afterwards. """ directory = tempfile.mkdtemp() try: yield Path(directory) finally: shutil.rmtree(directory) def most_recently_modified(directory): """Return datetime of most recently changed file in directory.""" files = list(directory.glob("**/*.*")) if files: return datetime.datetime.utcfromtimestamp(max(t.stat().st_mtime for t in files)) else: return None def run(*args, **kwargs): """Run external command.""" return subprocess.check_output(args, stderr=subprocess.STDOUT, **kwargs) def create_targz(directory, filename): with tarfile.open(filename, "w:gz") as tar: tar.add(directory, arcname=directory.name) def create_targz_as_bytes(directory): data = io.BytesIO() with tarfile.open(mode="w:gz", fileobj=data) as tar: tar.add(directory, arcname=directory.name) return data.getvalue() def pretty_print(obj, format, output=None): if format == "json": formatted = json.dumps(obj, sort_keys=True, indent=4).strip() elif format == "toml": formatted = toml.dumps(obj) if output is None: click.echo(formatted) else: output.write(formatted) def parse(obj, format): if format == "json": return json.loads(obj) elif format == "toml": return toml.loads(obj) def visual_poll(message, poll, NoTaskResultYet, wait=0.2): log(message, nl=False) result = poll() count = 0 while result is NoTaskResultYet: time.sleep(wait) log(".", nl=False) count += 1 result = poll() if count < 3: log("." * (3 - count), nl=False) log("Done") return result def run_plugins_task(host, token, name, params, message, data=None): api = EncapsiaApi(host, token) poll, NoTaskResultYet = api.run_task( "pluginsmanager", "icepluginsmanager.{}".format(name), params, data ) result = visual_poll(message, poll, NoTaskResultYet) log_output(result["output"].strip()) if result["status"] != "ok": raise click.Abort() PK!guuencapsia_cli/plugins.py"""Install / uninstall plugins.""" import datetime import shutil import sys from pathlib import Path import click import toml from encapsia_api import EncapsiaApi from encapsia_cli import lib @click.group() @click.option( "--host", help="Name to use to lookup credentials in ~/.encapsia/credentials.toml." ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST).", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN).", ) @click.pass_context def main(ctx, host, host_env_var, token_env_var): """Install / uninstall plugins.""" host, token = lib.discover_credentials(host, host_env_var, token_env_var) ctx.obj = dict(host=host, token=token) @main.command("dev-create-namespace") @click.argument("namespace") @click.argument("n_task_workers", default=1) @click.pass_context def dev_create_namespace(ctx, namespace, n_task_workers): """Create namespace of given name. Only useful during developmment.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "dev_create_namespace", dict(namespace=namespace, n_task_workers=n_task_workers), "Creating namespace", ) @main.command("dev-destroy-namespace") @click.argument("namespace") @click.pass_context def dev_destroy_namespace(ctx, namespace): """Destroy namespace of given name. Only useful during development""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "dev_destroy_namespace", dict(namespace=namespace), "Destroying namespace", ) @main.command() @click.pass_context def info(ctx): """Provide some information about installed plugins.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "list_namespaces", dict(), "Fetching list of namespaces", ) def read_toml(filename): with filename.open() as f: return toml.load(f) @main.command() @click.option("--versions", help="TOML file containing webapp names and versions.") @click.option( "--plugins-cache-dir", default="~/.encapsia/plugins-cache", help="Name of directory in which to cache plugins.", ) @click.option("--force", is_flag=True, help="Always install even if already installed.") @click.pass_context def install(ctx, versions, plugins_cache_dir, force): """Install plugins of particular versions.""" plugins_cache_dir = Path(plugins_cache_dir).expanduser() versions = Path(versions) api = EncapsiaApi(ctx.obj["host"], ctx.obj["token"]) for name, version in read_toml(versions).items(): plugin_filename = plugins_cache_dir / f"plugin-{name}-{version}.tar.gz" if not plugin_filename.exists(): print( f"Unable to find plugin {name} version {name} in cache ({plugins_cache_dir})" ) raise click.abort() # TODO only upload if not already installed? (unless --force) blob_id = api.upload_file_as_blob(plugin_filename.as_posix()) # TODO create plugin entity and pass that in (the pluginsmanager creates the pluginlogs entity) lib.log(f"Uploaded {plugin_filename} to blob: {blob_id}") lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "install_plugin", dict(blob_id=blob_id), "Installing", ) @main.command() @click.argument("namespace") @click.pass_context def uninstall(ctx, namespace): """Uninstall named plugin.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "uninstall_plugin", dict(namespace=namespace), f"Uninstalling {namespace}", ) class LastUploadedVsModifiedTracker: DIRECTORIES = ["tasks", "views", "wheels", "webfiles", "schedules"] def __init__(self, directory, reset=False): self.directory = directory encapsia_directory = directory / ".encapsia" encapsia_directory.mkdir(parents=True, exist_ok=True) self.filename = encapsia_directory / "last_uploaded_plugin_parts.toml" if reset: self.make_empty() else: self.load() def make_empty(self): self.data = {} self.save() def load(self): if not self.filename.exists(): self.make_empty() else: with self.filename.open() as f: self.data = toml.load(f) def save(self): with self.filename.open("w") as f: toml.dump(self.data, f) def get_modified_directories(self): for name in self.DIRECTORIES: last_modified = lib.most_recently_modified(self.directory / name) if last_modified is not None: if name in self.data: if last_modified > self.data[name]: yield Path(name) self.data[name] = datetime.datetime.utcnow() else: yield Path(name) self.data[name] = datetime.datetime.utcnow() self.save() def get_modified_plugin_directories(directory, reset=False): return list( LastUploadedVsModifiedTracker(directory, reset=reset).get_modified_directories() ) @main.command("dev-update") @click.argument("directory", default=".") @click.option("--reset", is_flag=True, help="Always update everything.") @click.pass_context def dev_update(ctx, directory, reset): """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.error("Not in a plugin directory.") sys.exit(1) modified_plugin_directories = get_modified_plugin_directories( directory, reset=reset ) 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 ) lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "dev_update_plugin", dict(), "Uploading to server", data=lib.create_targz_as_bytes(temp_directory), ) else: lib.log("Nothing to do.") PK!tNencapsia_cli/plugins_maker.py"""Create plugins.""" import re import shutil import urllib.request from pathlib import Path import click import toml from encapsia_cli import lib @click.group() @click.option( "--plugins-cache-dir", default="~/.encapsia/plugins-cache", help="Name of directory in which to cache plugins.", ) @click.option( "--force", is_flag=True, help="Always fetch/build even if already in cache." ) @click.pass_context def main(ctx, plugins_cache_dir, force): """Create plugins.""" plugins_cache_dir = Path(plugins_cache_dir).expanduser() plugins_cache_dir.mkdir(parents=True, exist_ok=True) ctx.obj = dict(plugins_cache_dir=plugins_cache_dir, force=force) def read_toml(filename): with filename.open() as f: return toml.load(f) def make_plugin_toml_file(filename, name, description, version, created_by): obj = dict( name=name, description=description, version=version, created_by=created_by, n_task_workers=1, reset_on_install=True, ) with filename.open("w") as f: toml.dump(obj, f) @main.command() @click.option("--versions", help="TOML file containing webapp names and versions.") @click.option("--email", prompt="Your email", help="Email creator of the plugins.") @click.option( "--s3-directory", default="ice-webapp-builds", help="Base directory on S3." ) @click.pass_context def build_from_legacy_s3(ctx, versions, email, s3_directory): """Build plugins from legacy webapps hosted on AWS S3.""" plugins_cache_dir = ctx.obj["plugins_cache_dir"] force = ctx.obj["force"] versions = Path(versions) for name, version in read_toml(versions).items(): output_filename = Path(plugins_cache_dir, f"plugin-{name}-{version}.tar.gz") if not force and output_filename.exists(): lib.log(f"Found: {output_filename} (Skipping)") else: _download_and_build_plugin_from_s3( s3_directory, name, version, email, output_filename ) lib.log(f"Created: {output_filename}") def _download_and_build_plugin_from_s3( s3_directory, name, version, email, output_filename ): with lib.temp_directory() as temp_directory: base_dir = temp_directory / f"plugin-{name}-{version}" base_dir.mkdir() # Download everything from S3 into the webfiles folder. # (we will move out the views and tasks if present). files_directory = base_dir / "webfiles" files_directory.mkdir() lib.run( "aws", "s3", "cp", f"s3://{s3_directory}/{name}/{version}", files_directory.as_posix(), "--recursive", ) # Move out the views if they exist. views_directory = files_directory / "views" if views_directory.exists(): views_directory.rename(base_dir / "views") # Move out the tasks if they exist. tasks_directory = files_directory / "tasks" if tasks_directory.exists(): tasks_directory.rename(base_dir / "tasks") # Create a plugin.toml manifest. make_plugin_toml_file( base_dir / "plugin.toml", name, f"Webapp {name}", version, email ) # Convert all into tar.gz lib.create_targz(base_dir, output_filename) @main.command() @click.argument("sources", nargs=-1) @click.pass_context def build_from_src(ctx, sources): """Build plugins from given source directories.""" plugins_cache_dir = ctx.obj["plugins_cache_dir"] force = ctx.obj["force"] for source_directory in sources: source_directory = Path(source_directory) manifest = read_toml(source_directory / "plugin.toml") name = manifest["name"] version = manifest["version"] output_filename = plugins_cache_dir / f"plugin-{name}-{version}.tar.gz" if not force and output_filename.exists(): lib.log(f"Found: {output_filename} (Skipping)") else: with lib.temp_directory() as temp_directory: base_dir = temp_directory / f"plugin-{name}-{version}" base_dir.mkdir() for t in ( "webfiles", "views", "tasks", "wheels", "schedules", "plugin.toml", ): source_t = source_directory / t if source_t.exists(): if source_t.is_file(): shutil.copy(source_t, base_dir / t) else: shutil.copytree(source_t, base_dir / t) lib.create_targz(base_dir, output_filename) lib.log(f"Created: {output_filename}") @main.command() @click.argument("url") @click.pass_context def fetch_from_url(ctx, url): """Copy a plugin from given URL into the plugin cache.""" plugins_cache_dir = ctx.obj["plugins_cache_dir"] force = ctx.obj["force"] full_name = url.rsplit("/", 1)[-1] m = re.match(r"plugin-([^-]*)-([^-]*).tar.gz", full_name) if m: output_filename = plugins_cache_dir / full_name if not force and output_filename.exists(): lib.log(f"Found: {output_filename} (Skipping)") else: filename, headers = urllib.request.urlretrieve(url) shutil.move(filename, output_filename) lib.log(f"Created: {output_filename}") else: print("That doesn't look like a plugin. Aborting!") raise click.Abort() PK!J݋m m encapsia_cli/schedule.py"""Manage task schedules.""" import click from encapsia_cli import lib @click.group() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) @click.pass_context def main(ctx, host, host_env_var, token_env_var): """Manage task schedules.""" host, token = lib.discover_credentials(host, host_env_var, token_env_var) ctx.obj = dict(host=host, token=token) @main.command("list") @click.pass_context def list_tasks(ctx): """List all scheduled tasks.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "list_scheduled_tasks", {}, "Fetching list of scheduled tasks", ) @main.command("add") @click.option("--description", prompt="Description", required=True) @click.option("--task-host", prompt="Task host", required=True) @click.option("--task-token", prompt="Task token", required=True) @click.option("--namespace", prompt="Namespace", required=True) @click.option("--task", prompt="Task (function)", required=True) @click.option("--params", prompt="Params (dict of args to function)", required=True) @click.option( "--cron", prompt="Cron string (e.g. '*/5 * * * *' means every 5 mins)", required=True, ) @click.option("--jitter", prompt="Jitter (int)", type=int, required=True) @click.pass_context def add_task( ctx, description, task_host, task_token, namespace, task, params, cron, jitter ): """Add new scheduled task.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "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", ) @main.command("remove_in_namespace") @click.argument("namespace") @click.pass_context def remove_tasks_in_namespace(ctx, namespace): """Remove all scheduled tasks in given namespace.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "remove_scheduled_tasks_in_namespace", dict(namespace=namespace), "Removing scheduled tasks", ) @main.command("remove") @click.argument("scheduled_task_id") @click.pass_context def remove_task(ctx, scheduled_task_id): """Remove scheduled task by id.""" lib.run_plugins_task( ctx.obj["host"], ctx.obj["token"], "remove_scheduled_task", dict(scheduled_task_id=scheduled_task_id), "Removing scheduled tasks", ) PK!s:""encapsia_cli/system_user.py"""Manage system users (create, list, delete etc).""" import click from encapsia_cli import lib @click.group() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) @click.pass_context def main(ctx, host, host_env_var, token_env_var): """Manage system users.""" ctx.obj = dict(api=lib.get_api(host, host_env_var, token_env_var)) @main.command() @click.argument("description") @click.argument("capabilities") @click.pass_context def add(ctx, description, capabilities): """Create system user with suitable user and role details. Use quoting in the shell appropriately. For example: encapsia-system-user add "This is a description" "capability1, capability2" """ api = ctx.obj["api"] api.add_system_user(description, [x.strip() for x in capabilities.split(",")]) @main.command() @click.pass_context def show(ctx): """Print system users.""" api = ctx.obj["api"] for su in api.get_system_users(): print(su) PK!,٘QQencapsia_cli/task.py"""Run an arbitrary Encapsia task.""" import click from encapsia_cli import lib @click.command() @click.argument("namespace") @click.argument("function") @click.argument("args", nargs=-1) @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) def main(namespace, function, args, host, host_env_var, token_env_var): """Run an arbitrary task in given plugin NAMESPACE and qualified FUNCTION e.g. encapsia-task example_namespace test_module.test_function x=3 y=tim "z=hello stranger" Note that all args must be named and the values are all considered strings (not least because task arguments are encoded over a URL string). """ api = lib.get_api(host, host_env_var, token_env_var) params = {} for arg in args: left, right = arg.split("=", 1) params[left.strip()] = right.strip() poll, NoTaskResultYet = api.run_task(namespace, function, params) result = lib.visual_poll(f"Running task {namespace}", poll, NoTaskResultYet) lib.log_output(str(result)) PK!Tbbencapsia_cli/whoami.py"""Print information about owner of token.""" import click from encapsia_cli import lib @click.command() @click.option( "--host", help="Name to use to lookup credentials in .encapsia/credentials.toml" ) @click.option( "--host-env-var", default="ENCAPSIA_HOST", help="Environment variable containing DNS hostname (default ENCAPSIA_HOST)", ) @click.option( "--token-env-var", default="ENCAPSIA_TOKEN", help="Environment variable containing server token (default ENCAPSIA_TOKEN)", ) @click.option( "--format", type=click.Choice(["json", "toml"]), default="json", help="Format as JSON or TOML (default JSON)", ) def main(host, host_env_var, token_env_var, format): """Print information about current owner of token.""" api = lib.get_api(host, host_env_var, token_env_var) lib.pretty_print(api.whoami(), format) PK!H݊-encapsia_cli-0.1.7.dist-info/entry_points.txtm] {<'1 vSB[Kٵ3g"%l\2@&eS2FPNaV`ox}g"tW[=$2xf,j 1ʾ1W`&UE\*."ǘL@u PK!kBړ66$encapsia_cli-0.1.7.dist-info/LICENSEMIT License Copyright (c) 2019 Timothy Corbett-Clark Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTU"encapsia_cli-0.1.7.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!HMEz%encapsia_cli-0.1.7.dist-info/METADATASMo0 Wae;ٚhIkע vB[my~T~m=l'{trE*d0jZoT+#LX̻V`Zl簲HUk@Tg{'lkZU0{I, Ce1iH+3 \vy_X888MRc`ጪtTZԖ;hdke hO;T\P8uU<ӓ<`: Kۅ,9L_}YɵSuw)N)F]1~x@m<ޜ2eO :@2xإj\[rws;CfJlIPo`3ڙد6}!L#!*# 29zתɁF\+eǗ7gLϥ Q^rLdwϕ5lхT~6 -fWBI=H`S]ޡӆ[SU'kVOpEd%FRqih5u9.[٢ٰdmct|'a$T9ioՐAQx6+SPK!HCIN+g#encapsia_cli-0.1.7.dist-info/RECORDɲX}= XâȠ( 9̠T_ˌU5=@oPVL8Qӝ.mקgI~сnQW {;ƅ~W-JSЏ v/$4Q(@aKxFp2k"G/I]e(LL-SYÊ{x]5ֵ`(F|86ۀgzw )EkZ&_{s »u uBÛy&,m@zYdRKw+Ck)iU >PfoifxLV pUPa%̼1ONv wcy/2})e2xS\v z7*&;gLh-̈́NЮkŝyWо޳ G}?Z?eašQtĒ"?` >Ȩɲv)T~p<؀CYиXY{7/^8n-L3ȥBoLypX16mq""_)TOб"7]զ3>jHR+z%R$7nM_ gR}q>!+Ã=9b[q¹r'?LUOGw$cWct=Xz@bco([]&C/] \k+Vi B)gJVL8heSk1TKg@ pؤhvuj_PK!encapsia_cli/__init__.pyPK!fw{{6encapsia_cli/add_superuser.pyPK!\.encapsia_cli/config.pyPK! i> encapsia_cli/dbctl.pyPK!K>>(encapsia_cli/expire_token.pyPK! \-encapsia_cli/lib.pyPK!guu&;encapsia_cli/plugins.pyPK!tNUencapsia_cli/plugins_maker.pyPK!J݋m m lencapsia_cli/schedule.pyPK!s:""wencapsia_cli/system_user.pyPK!,٘QQ|encapsia_cli/task.pyPK!Tbbencapsia_cli/whoami.pyPK!H݊-encapsia_cli-0.1.7.dist-info/entry_points.txtPK!kBړ66$encapsia_cli-0.1.7.dist-info/LICENSEPK!HڽTU"yencapsia_cli-0.1.7.dist-info/WHEELPK!HMEz% encapsia_cli-0.1.7.dist-info/METADATAPK!HCIN+g#encapsia_cli-0.1.7.dist-info/RECORDPKS