gigantum
Advanced tools
| import click | ||
| from gigantumcli.config import ConfigOverrideFile | ||
| @click.group(help="Manage the Client configuration file.") | ||
| def config(): | ||
| pass | ||
| @config.command() | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| def init(working_dir: str = "~/gigantum"): | ||
| """Initialize the Client configuration override file. | ||
| The default Client configuration file that is embedded in the Client can be modified by | ||
| "overriding" values based on the file located at `<working_dir>/.labmanager/config.yaml`. Typically | ||
| this is `~/gigantum/.labmanager/config.yaml`. | ||
| This command writes a configuration override file containing the most frequently needed fields | ||
| with their default values. | ||
| \f | ||
| Args: | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| config_helper = ConfigOverrideFile(working_dir) | ||
| if config_helper.config_exists(): | ||
| click.echo("A config file already exists at `{}`. " | ||
| "Remove this file before trying to init".format(config_helper.config_file)) | ||
| raise click.Abort() | ||
| config_helper.write_default_config_override() | ||
| click.echo("Config file written to `{}` with default values.".format(config_helper.config_file)) | ||
| @config.command('set-auth-mode') | ||
| @click.argument('mode', type=click.Choice(['local', 'multi-user'])) | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| def set_auth_mode(mode: str, working_dir: str = "~/gigantum"): | ||
| """Set the authentication mode (important for running the Client publicly). | ||
| By default the Client authorization middleware is in "local" mode. This means that | ||
| when logging in some identity data is cached so that the Client can work when disconnected | ||
| from the internet. If you wish to run the Client as a service for multiple users to share | ||
| (sometimes referred to as "multi-tenant mode" in some documentation) you must select the "multi-user" | ||
| middleware mode. | ||
| \f | ||
| Args: | ||
| mode(str): Mode for the auth middleware (local, multi-user) | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| # Translate from user friendly name | ||
| if mode == 'multi-user': | ||
| mode = 'browser' | ||
| config_helper = ConfigOverrideFile(working_dir) | ||
| config_helper.set_auth_middleware_mode(mode) | ||
| click.echo("Config updated. Restart the Client to apply changes.") |
| import webbrowser | ||
| import click | ||
| @click.command() | ||
| def feedback(): | ||
| """Open the default web browser to provide feedback. | ||
| /f | ||
| Returns: | ||
| None | ||
| """ | ||
| feedback_url = "https://feedback.gigantum.com" | ||
| click.echo("You can provide feedback here: {}".format(feedback_url)) | ||
| webbrowser.open_new(feedback_url) |
| from docker.errors import APIError, ImageNotFound | ||
| import click | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.changelog import ChangeLog | ||
| from gigantumcli.utilities import is_running_as_admin | ||
| @click.command() | ||
| @click.option('--edge', '-e', type=bool, is_flag=True, default=False, | ||
| help="Optional flag indicating if the edge version should be used. Note, you must have access " | ||
| "to this image and may need to log into DockerHub.") | ||
| def install(edge: bool): | ||
| """Install the Gigantum Client Docker Image. | ||
| This command will pull and configure the Gigantum Client Docker Image for the first time. The | ||
| process can take a few minutes depending on your internet speed. | ||
| \f | ||
| Args: | ||
| edge(bool): Flag indicating if the install is stable or edge | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| raise click.UsageError("Do not run `gigantum install` as root.") | ||
| docker_obj = DockerInterface() | ||
| image_name = docker_obj.image_name(edge) | ||
| try: | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker_obj.client.images.get(image_name) | ||
| raise click.UsageError("Gigantum Client image already installed. Run `gigantum update` instead.") | ||
| except ImageNotFound: | ||
| # Pull for the first time | ||
| print("\nDownloading and installing the Gigantum Client Docker Image. Please wait...\n") | ||
| cl = ChangeLog() | ||
| tag = cl.latest_tag() | ||
| image = docker_obj.client.images.pull(image_name, tag) | ||
| docker_obj.client.api.tag('{}:{}'.format(image_name, tag), image_name, 'latest') | ||
| except APIError: | ||
| click.echo("Failed to pull image! Verify your internet connection and try again.", err=True) | ||
| raise click.Abort() | ||
| short_id = image.short_id.split(':')[1] | ||
| print("\nSuccessfully pulled {}:{}\n".format(image_name, short_id)) |
| import click | ||
| from gigantumcli.utilities import ask_question | ||
| from gigantumcli.server import ServerConfig | ||
| @click.group(help="Manage server connections for this Client instance.") | ||
| def server(): | ||
| pass | ||
| @server.command() | ||
| @click.argument('url', type=str, nargs=1) | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| def add(url: str, working_dir: str = "~/gigantum"): | ||
| """Add a server to this Client. | ||
| Enter the URL for the server you wish to add to this Client (e.g. https://gigantum.mycompany.com). If the | ||
| server uses self-signed certificates you will be prompted before the server is added. Once added, you can | ||
| log into the server to sync Projects and Datasets. | ||
| \f | ||
| Args: | ||
| url(str): URL to the server | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| server_config = ServerConfig(working_dir=working_dir) | ||
| server_config.add_server(url) | ||
| click.echo("\nServer successfully added. The server will now be available on your Client login page.") | ||
| @server.command('list') | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| def list_server(working_dir: str = "~/gigantum"): | ||
| """List configured servers. | ||
| This command will list all servers currently available to this Client. | ||
| \f | ||
| Args: | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| server_config = ServerConfig(working_dir=working_dir) | ||
| server_config.list_servers(should_print=True) | ||
| @server.command() | ||
| @click.argument('server_id', metavar='ID', type=str, nargs=1) | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| @click.option('--yes', '-y', 'accept_confirmation', type=str, is_flag=True, default=False, | ||
| help="Flag to automatically accept all confirmation prompts") | ||
| def remove(server_id: str, working_dir: str = "~/gigantum", accept_confirmation: bool = False) -> None: | ||
| """Remove a server with ID from this Client. | ||
| This command will remove the server with the provided server ID from this | ||
| Client. All data related to this server will be removed locally. | ||
| \f | ||
| Args: | ||
| server_id(str): server ID to remove. Run `gigantum server list` to see all IDs | ||
| working_dir(str): Working dir for the client | ||
| accept_confirmation(bool): Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| Returns: | ||
| None | ||
| """ | ||
| server_config = ServerConfig(working_dir=working_dir) | ||
| servers = server_config.list_servers() | ||
| # Make sure the ID exists | ||
| match = [s for s in servers if s[0] == server_id] | ||
| if not match: | ||
| msg = "ID `{}` does not exist. Verify the correct server ID has been provided ".format(server_id) + \ | ||
| "by running `gigantum server list`." | ||
| raise click.UsageError(msg) | ||
| # Verify that they really want to do this | ||
| click.echo() | ||
| click.secho('********* DANGER *********', bg='red', fg='white', bold=True) | ||
| click.echo() | ||
| click.echo("Removing the server `{}` will delete ALL data locally.".format(match[0][1])) | ||
| click.echo("Any changes that have not been synced to the server will be lost!") | ||
| click.echo() | ||
| click.secho('********* DANGER *********', bg='red', fg='white', bold=True) | ||
| click.echo() | ||
| if ask_question("Do you want to proceed?", accept_confirmation): | ||
| # Remove server and make sure CURRENT is set to something valid | ||
| server_config.remove_server(server_id) | ||
| else: | ||
| click.echo("Server remove cancelled.") | ||
| return |
| import platform | ||
| from typing import Optional | ||
| from docker.errors import ImageNotFound, NotFound | ||
| import os | ||
| import webbrowser | ||
| import time | ||
| import requests | ||
| import uuid | ||
| import click | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.commands.install import install | ||
| from gigantumcli.config import ConfigOverrideFile | ||
| from gigantumcli.utilities import ask_question, is_running_as_admin, get_nvidia_driver_version | ||
| def _check_for_api(port: int = 10000, launch_browser: bool = False, timeout: int = 5): | ||
| """Check for the API to be live for up to `timeout` seconds, then optionally launch a browser window | ||
| Args: | ||
| port(int): port the server is running on | ||
| launch_browser(bool): flag indicating if the browser should be launched on success == True | ||
| timeout(int): Number of seconds to wait for the API before returning | ||
| Returns: | ||
| bool: flag indicating if the API is ready | ||
| """ | ||
| protocol = "http" | ||
| if port == 443: | ||
| protocol = "https" | ||
| success = False | ||
| for _ in range(timeout): | ||
| time.sleep(1) | ||
| try: | ||
| resp = requests.get("{}://localhost:{}/api/ping?v={}".format(protocol, port, uuid.uuid4().hex), | ||
| verify=False) | ||
| if resp.status_code == 200: | ||
| success = True | ||
| break | ||
| except requests.exceptions.ConnectionError: | ||
| # allow connection errors, which mean the API isn't up yet. | ||
| pass | ||
| if success is True and launch_browser is True: | ||
| time.sleep(1) | ||
| # If here, things look OK. Start browser | ||
| webbrowser.open_new("{}://localhost:{}".format(protocol, port)) | ||
| return success | ||
| @click.command() | ||
| @click.option('--edge', '-e', type=bool, is_flag=True, default=False, | ||
| help="Flag indicating if the edge version should be used. Note, you must have access " | ||
| "to this image and may need to log into DockerHub.") | ||
| @click.option('--wait', '-w', 'timeout', type=int, default=90, | ||
| help="Number of seconds to wait for the Client to be ready.") | ||
| @click.option('--yes', '-y', 'accept_confirmation', is_flag=True, default=False, | ||
| help="Flag to automatically accept all confirmation prompts") | ||
| @click.option('--tag', '-t', type=str, default=None, | ||
| help="Provide a tag to explicitly run instead of the latest available.") | ||
| @click.option('--port', type=int, default=10000, | ||
| help="Set the port on which the Client web server is exposed (10000).") | ||
| @click.option('--external', is_flag=True, default=False, | ||
| help="Set if you wish to run the Client for external access. Default runs on localhost.") | ||
| @click.option('--working-dir', '-d', 'working_dir', type=str, default="~/gigantum", | ||
| help="Set the Client working directory (`~/gigantum`).") | ||
| @click.pass_context | ||
| def start(ctx, edge: bool, timeout: int, tag: Optional[str] = None, port: int = 10000, working_dir: str = "~/gigantum", | ||
| external: bool = False, accept_confirmation: bool = False) -> None: | ||
| """Start Gigantum Client. | ||
| This command will clean-up any lingering Gigantum managed containers and then start the Client on the specified | ||
| port (default 10000). | ||
| Note, only running on localhost:10000 is supported with Gigantum.com. When using a self-hosted server, you can | ||
| use any additional hostnames and ports that have been added to the auth redirect allowlist by the system | ||
| administrator. | ||
| \f | ||
| Args: | ||
| edge(bool): Flag indicating if the install is stable or edge | ||
| timeout: Number of seconds to wait for API to come up | ||
| tag: Tag to run, defaults to latest | ||
| port: port the server runs on | ||
| working_dir: Location to mount as the Gigantum working directory | ||
| external: Optional flag if you want to run on the external network interface (0.0.0.0) | ||
| accept_confirmation: Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| raise click.UsageError("Do not run `gigantum start` as root.") | ||
| # Make sure you are in browser mode if running on 80/443 | ||
| if external: | ||
| config_file = ConfigOverrideFile(working_dir=working_dir) | ||
| if not config_file.is_browser_middleware_selected(): | ||
| click.secho("WARNING: When running the Client with external network access you MUST enable the " | ||
| "`multi-user` auth mode.", fg='yellow') | ||
| click.secho("Run `gigantum config set-auth-mode multi-user` to configure this.", fg='yellow') | ||
| click.secho("Failure to do so could allow anyone to access a valid user session.", fg='yellow', bold=True) | ||
| raise click.Abort() | ||
| click.echo("Verifying Docker is available...") | ||
| # Check if Docker is running | ||
| docker_obj = DockerInterface() | ||
| image_name = docker_obj.image_name(edge) | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = 'latest' | ||
| image_name_with_tag = "{}:{}".format(image_name, tag) | ||
| # Check if working dir exists | ||
| working_dir = os.path.expanduser(working_dir) | ||
| if not os.path.exists(working_dir): | ||
| os.makedirs(working_dir) | ||
| # Check if application has been installed | ||
| try: | ||
| docker_obj.client.images.get(image_name_with_tag) | ||
| except ImageNotFound: | ||
| if ask_question("The Gigantum Client Docker image not found. Would you like to install it now?", | ||
| accept_confirmation): | ||
| ctx.invoke(install, edge=edge) | ||
| else: | ||
| click.echo("Downloading the Gigantum Client Docker image is required to start the Client. " | ||
| "Please run `gigantum install`.") | ||
| return | ||
| # Check to see if already running | ||
| try: | ||
| if _check_for_api(port=port, launch_browser=False, timeout=1): | ||
| click.echo("Client already running on http://localhost:{}".format(port)) | ||
| _check_for_api(port=port, launch_browser=True) | ||
| click.echo("If page does not load, restart by running `gigantum stop` and then `gigantum start` again") | ||
| return | ||
| # remove any lingering gigantum managed containers | ||
| docker_obj.cleanup_containers() | ||
| except NotFound: | ||
| # If here, the API isn't running and an older container isn't lingering, so just move along. | ||
| pass | ||
| # Start | ||
| if external: | ||
| interface = "0.0.0.0" | ||
| else: | ||
| interface = "127.0.0.1" | ||
| port_mapping = {'10000/tcp': (interface, port)} | ||
| # Make sure the container-container share volume exists | ||
| if not docker_obj.share_volume_exists(): | ||
| docker_obj.create_share_volume() | ||
| volume_mapping = {docker_obj.share_vol_name: {'bind': '/mnt/share', 'mode': 'rw'}} | ||
| print('Host directory for Gigantum files: {}'.format(working_dir)) | ||
| if platform.system() == 'Windows': | ||
| # windows docker has some eccentricities | ||
| # no user ids, we specify a WINDOWS_HOST env var, and need to rewrite the paths for | ||
| # bind-mounting inside the Client (see `dockerize_mount_path()` for details) | ||
| rewritten_path = docker_obj.dockerize_mount_path(working_dir, image_name_with_tag) | ||
| environment_mapping = {'HOST_WORK_DIR': rewritten_path, | ||
| 'WINDOWS_HOST': 1} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'rw'} | ||
| elif platform.system() == 'Darwin': | ||
| # For macOS, use the cached mode for improved performance | ||
| environment_mapping = {'HOST_WORK_DIR': working_dir, | ||
| 'LOCAL_USER_ID': os.getuid()} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'cached'} | ||
| else: | ||
| # For anything else, just use default mode. | ||
| environment_mapping = {'HOST_WORK_DIR': working_dir, | ||
| 'LOCAL_USER_ID': os.getuid(), | ||
| 'NVIDIA_DRIVER_VERSION': get_nvidia_driver_version()} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'rw'} | ||
| volume_mapping['/var/run/docker.sock'] = {'bind': '/var/run/docker.sock', 'mode': 'rw'} | ||
| container = docker_obj.client.containers.run(image=image_name_with_tag, | ||
| detach=True, | ||
| name=image_name.replace("/", "."), | ||
| init=True, | ||
| ports=port_mapping, | ||
| volumes=volume_mapping, | ||
| environment=environment_mapping) | ||
| print("Starting, please wait...") | ||
| time.sleep(1) | ||
| # Make sure volumes have mounted properly, by checking for the log file for up to timeout seconds | ||
| success = False | ||
| for _ in range(timeout): | ||
| if os.path.exists(os.path.join(working_dir, '.labmanager', 'logs', 'labmanager.log')): | ||
| success = True | ||
| break | ||
| # Sleep for 1 sec and increment counter | ||
| time.sleep(1) | ||
| if not success: | ||
| msg = "\n\nWorking directory failed to mount! Have you granted Docker access to your user directory?" | ||
| msg = msg + " \nIn both Docker for Mac and Docker for Windows this should be shared by default, but may require" | ||
| msg = msg + " a confirmation from the user." | ||
| msg = msg + "\n\nRun `gigantum stop`, verify your OS and Docker versions are supported, the allowed Docker" | ||
| msg = msg + " volume share locations include `{}`, and try again.".format(working_dir) | ||
| msg = msg + "\n\nIf this problem persists, contact support." | ||
| # Stop and remove the container | ||
| container.stop() | ||
| container.remove() | ||
| click.echo(msg, err=True) | ||
| raise click.Abort() | ||
| # Wait for API to be live before opening the user's browser | ||
| if not _check_for_api(port=port, launch_browser=True, timeout=timeout): | ||
| msg = "\n\nTimed out waiting for Gigantum Client web API! Try restarting Docker and then start again." + \ | ||
| "\nOr, increase timeout with --wait option (default is 90 seconds)." | ||
| # Stop and remove the container | ||
| container.stop() | ||
| container.remove() | ||
| click.echo(msg, err=True) | ||
| raise click.Abort() | ||
| if external: | ||
| click.echo("Client running on http://0.0.0.0:{}".format(port)) | ||
| else: | ||
| click.echo("Client running on http://localhost:{}".format(port)) | ||
| import click | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.utilities import ask_question | ||
| @click.command() | ||
| @click.option('--yes', '-y', 'accept_confirmation', type=str, is_flag=True, default=False, | ||
| help="Flag to automatically accept all confirmation prompts") | ||
| def stop(accept_confirmation=False): | ||
| """Stop Gigantum Client. | ||
| This command will stop and remove all Gigantum managed containers. | ||
| \f | ||
| Args: | ||
| accept_confirmation(bool): Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| Returns: | ||
| None | ||
| """ | ||
| docker_obj = DockerInterface() | ||
| if ask_question("Stop all Gigantum managed containers? MAKE SURE YOU HAVE SAVED YOUR WORK FIRST!", | ||
| accept_confirmation): | ||
| # remove any lingering gigantum managed containers | ||
| docker_obj.cleanup_containers() | ||
| else: | ||
| click.echo("Stop command cancelled") | ||
| return |
| import sys | ||
| from docker.errors import APIError, ImageNotFound | ||
| import click | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.changelog import ChangeLog | ||
| from gigantumcli.utilities import ask_question, is_running_as_admin | ||
| @click.command() | ||
| @click.option('--edge', '-e', type=bool, is_flag=True, default=False, | ||
| help="Flag indicating if the edge version should be used. Note, you must have access " | ||
| "to this image and may need to log into DockerHub.") | ||
| @click.option('--tag', '-t', type=str, default=None, | ||
| help="Provide a tag to explicitly override the latest image tag when updating") | ||
| @click.option('--yes', '-y', 'accept_confirmation', type=str, is_flag=True, default=False, | ||
| help="Flag to automatically accept all confirmation prompts") | ||
| def update(edge: bool, tag=None, accept_confirmation=False): | ||
| """Check if an update is available for Gigantum Client. | ||
| This command will check if an update is available and display the latest changelog before | ||
| prompting for confirmation. If you proceed, the new Gigantum Client image will be pulled. The | ||
| process can take a few minutes depending on your internet speed. | ||
| \f | ||
| Args: | ||
| edge(bool): Flag indicating if the install is stable or edge | ||
| tag(str): Tag to pull if you wish to override `latest` | ||
| accept_confirmation(bool): Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| Returns: | ||
| None | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| click.echo("Do not run `gigantum install` as root.", err=True) | ||
| return | ||
| docker_obj = DockerInterface() | ||
| image_name = docker_obj.image_name(edge) | ||
| try: | ||
| cl = ChangeLog() | ||
| if not edge: | ||
| # Normal install, so do checks | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = cl.latest_tag() | ||
| # Get id of current labmanager install | ||
| try: | ||
| current_image = docker_obj.client.images.get("{}:latest".format(image_name)) | ||
| except ImageNotFound: | ||
| click.echo("Gigantum Client image not yet installed. Run 'gigantum install' first.") | ||
| raise click.Abort() | ||
| short_id = current_image.short_id.split(':')[1] | ||
| # Check if there is an update available | ||
| if not cl.is_update_available(short_id): | ||
| click.echo("Latest version already installed.") | ||
| sys.exit(0) | ||
| # Get Changelog info for the latest or specified version | ||
| try: | ||
| click.echo(cl.get_changelog(tag)) | ||
| except ValueError as err: | ||
| click.echo("Failed to get changelog information: {}".format(err), err=True) | ||
| raise click.Abort() | ||
| else: | ||
| # Edge build, set tag if needed | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = "latest" | ||
| # Make sure user wants to pull | ||
| if ask_question("Are you sure you want to update?", accept_confirmation): | ||
| # Pull | ||
| click.echo("\nDownloading and installing the Gigantum Client Docker Image. Please wait...\n") | ||
| image = docker_obj.client.images.pull(image_name, tag) | ||
| # Tag to latest locally | ||
| docker_obj.client.api.tag('{}:{}'.format(image_name, tag), image_name, 'latest') | ||
| else: | ||
| click.echo("Update cancelled.") | ||
| raise click.Abort() | ||
| except APIError: | ||
| click.echo("Failed to pull image! Verify your internet connection and try again.", err=True) | ||
| raise click.Abort() | ||
| short_id = image.short_id.split(':')[1] | ||
| click.echo("\nSuccessfully pulled {}:{}\n".format(image_name, short_id)) |
| import yaml | ||
| import os | ||
| import click | ||
| DEFAULT_CONFIG_FILE = """ | ||
| core: | ||
| # Should the demo project import automatically on first log in? | ||
| import_demo_on_first_login: true | ||
| # Default server will be automatically configured on first boot | ||
| default_server: "https://gigantum.com" | ||
| # Project Container Configuration | ||
| # These limits are set on each individual Project container. | ||
| container: | ||
| # If null, no limit | ||
| # To set enter string with a units identification char (e.g. 100000b, 1000k, 128m, 1g) | ||
| memory: null | ||
| # If null, no limit | ||
| # To set enter a float for the CPU allocation desired. e.g. 4 CPUs available, 1.5 limits container to 1.5 CPUs | ||
| cpu: null | ||
| # If null, do not set shared memory parameter when launching project container | ||
| # To set enter string with a units identification char (e.g. 100000b, 1000k, 128m, 1g) | ||
| # This parameter is only set when a Project is GPU enabled and being launched with nvidia docker runtime | ||
| gpu_shared_mem: 2G | ||
| # Authentication Configuration | ||
| auth: | ||
| # You *must* set to `browser` for multi-tenant mode | ||
| identity_manager: local | ||
| # Environment Management Configuration | ||
| # URLs can specify a non-default branch using the format <url>@<branch> | ||
| # You can replace gigantum default bases or augment by adding your own repository here | ||
| environment: | ||
| repo_url: | ||
| - "https://github.com/gigantum/base-images.git" | ||
| """ | ||
| class ConfigOverrideFile: | ||
| def __init__(self, working_dir: str) -> None: | ||
| self.working_dir = os.path.expanduser(working_dir) | ||
| self.config_file = os.path.join(self.working_dir, '.labmanager', 'config.yaml') | ||
| def config_exists(self) -> bool: | ||
| """Function to check if the config override file exists | ||
| Returns: | ||
| bool | ||
| """ | ||
| return os.path.isfile(self.config_file) | ||
| def is_browser_middleware_selected(self) -> bool: | ||
| """Function to check if the browser middleware has been selected | ||
| Returns: | ||
| bool | ||
| """ | ||
| if not self.config_exists(): | ||
| return False | ||
| with open(self.config_file, 'rt') as cf: | ||
| data = yaml.safe_load(cf) | ||
| if data.get("auth"): | ||
| if data["auth"].get("identity_manager") == "browser": | ||
| return True | ||
| return False | ||
| def set_auth_middleware_mode(self, mode: str) -> None: | ||
| """Method to set a server into "multi-tenant" mode by ensuring the middleware is set to browser | ||
| Returns: | ||
| None | ||
| """ | ||
| if mode not in ['local', 'browser', 'anon_review']: | ||
| raise click.UsageError("Unsupported auth mode: {}".format(mode)) | ||
| if not self.config_exists(): | ||
| self.write_default_config_override() | ||
| with open(self.config_file, 'rt') as cf: | ||
| data = yaml.safe_load(cf) | ||
| if data.get("auth"): | ||
| data["auth"]["identity_manager"] = mode | ||
| else: | ||
| data["auth"] = {"identity_manager": mode} | ||
| with open(self.config_file, 'wt') as cf: | ||
| yaml.safe_dump(data, cf) | ||
| def write_default_config_override(self) -> None: | ||
| """Writes the default config override file for managing the client as multi-tenant service | ||
| Returns: | ||
| """ | ||
| dst_dir = os.path.dirname(self.config_file) | ||
| os.makedirs(dst_dir, exist_ok=True) | ||
| with open(self.config_file, 'wt') as cf: | ||
| cf.write(DEFAULT_CONFIG_FILE) |
| import pytest | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from docker.errors import ImageNotFound | ||
| import os | ||
| import shutil | ||
| import time | ||
| @pytest.fixture() | ||
| def fixture_remove_client(): | ||
| """Fixture start fake project and client containers""" | ||
| docker_obj = DockerInterface() | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| img = docker_obj.client.images.get('gigantum/labmanager:latest') | ||
| docker_obj.client.images.remove(img.id, force=True) | ||
| except ImageNotFound: | ||
| pass | ||
| @pytest.fixture() | ||
| def fixture_temp_work_dir(): | ||
| """Fixture create a temporary working directory""" | ||
| temp_dir = os.path.join('/tmp', 'testing-working-dir') | ||
| if not os.path.isdir(temp_dir): | ||
| os.makedirs(temp_dir) | ||
| yield temp_dir | ||
| # Stop and remove Client container | ||
| docker_obj = DockerInterface() | ||
| time.sleep(.1) | ||
| for container in docker_obj.client.containers.list(all=True): | ||
| if container.name == "gigantum.labmanager": | ||
| container.remove(force=True) | ||
| time.sleep(.1) | ||
| # Remove temp dir | ||
| shutil.rmtree(temp_dir) |
| from click.testing import CliRunner | ||
| import os | ||
| import yaml | ||
| import pytest | ||
| from gigantumcli.tests.fixtures import fixture_temp_work_dir | ||
| from gigantumcli.commands.config import config | ||
| class TestConfig(object): | ||
| def test_init_fresh_workdir(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| result = runner.invoke(config, ['init', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config file written" in result.output | ||
| def test_init(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| dst_dir = os.path.dirname(os.path.join(fixture_temp_work_dir, '.labmanager')) | ||
| os.makedirs(dst_dir, exist_ok=True) | ||
| result = runner.invoke(config, ['init', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config file written" in result.output | ||
| def test_init_exists(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| result = runner.invoke(config, ['init', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config file written" in result.output | ||
| result = runner.invoke(config, ['init', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 1 | ||
| assert "A config file already exists" in result.output | ||
| def test_set_mode(self, fixture_temp_work_dir): | ||
| def get_mode() -> str: | ||
| with open(os.path.join(fixture_temp_work_dir, '.labmanager', 'config.yaml'), 'rt') as cf: | ||
| data = yaml.safe_load(cf) | ||
| return data["auth"]["identity_manager"] | ||
| runner = CliRunner() | ||
| result = runner.invoke(config, ['set-auth-mode', 'local', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config updated." in result.output | ||
| assert get_mode() == "local" | ||
| result = runner.invoke(config, ['set-auth-mode', 'multi-user', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config updated." in result.output | ||
| assert get_mode() == "browser" | ||
| result = runner.invoke(config, ['set-auth-mode', 'local', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Config updated." in result.output | ||
| assert get_mode() == "local" | ||
| result = runner.invoke(config, ['set-auth-mode', 'fake', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 2 | ||
| assert "invalid choice: fake." in result.output | ||
| assert get_mode() == "local" |
| from docker.errors import ImageNotFound | ||
| from click.testing import CliRunner | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.commands.install import install | ||
| from gigantumcli.tests.fixtures import fixture_temp_work_dir, fixture_remove_client | ||
| class TestInstall(object): | ||
| def test_install(self, fixture_remove_client): | ||
| runner = CliRunner() | ||
| docker_obj = DockerInterface() | ||
| # image should exist not exist before install | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker_obj.client.images.get('gigantum/labmanager:latest') | ||
| assert "Image should not exist" | ||
| except ImageNotFound: | ||
| pass | ||
| result = runner.invoke(install, []) | ||
| assert result.exit_code == 0 | ||
| assert "Successfully pulled" in result.output | ||
| # image should exist after install | ||
| docker_obj = DockerInterface() | ||
| docker_obj.client.images.get('gigantum/labmanager') | ||
| # Calling again should exit with a message since already installed | ||
| result = runner.invoke(install, []) | ||
| assert result.exit_code == 2 | ||
| assert "image already installed" in result.output | ||
| import pytest | ||
| from unittest import mock | ||
| from docker.errors import ImageNotFound | ||
| import getpass | ||
| import click | ||
| from click.testing import CliRunner | ||
| from gigantumcli.commands.start import start | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.tests.fixtures import fixture_temp_work_dir, fixture_remove_client | ||
| def mock_api_check(launch_browser, timeout): | ||
| return launch_browser | ||
| class TestStart(object): | ||
| def test_is_running_as_admin(self): | ||
| from gigantumcli.utilities import is_running_as_admin | ||
| assert is_running_as_admin() is False | ||
| def test_start(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| result = runner.invoke(start, ['-y', '-w', 60, '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Starting, please wait" in result.output | ||
| assert "Client running on http://localhost:10000" in result.output | ||
| docker = DockerInterface() | ||
| is_running = False | ||
| for container in docker.client.containers.list(): | ||
| if container.name in 'gigantum.labmanager': | ||
| is_running = True | ||
| break | ||
| assert is_running is True | ||
| def test_start_block_external(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| result = runner.invoke(start, ['-y', '-w', 60, '--external', '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 1 | ||
| assert "WARNING: When running the Client" in result.output | ||
| docker = DockerInterface() | ||
| is_running = False | ||
| for container in docker.client.containers.list(): | ||
| if container.name in 'gigantum.labmanager': | ||
| is_running = True | ||
| break | ||
| assert is_running is False | ||
| def test_start_different_port(self, fixture_temp_work_dir): | ||
| runner = CliRunner() | ||
| result = runner.invoke(start, ['-y', '-w', 60, '--port', 20000, '--working-dir', fixture_temp_work_dir]) | ||
| assert result.exit_code == 0 | ||
| assert "Starting, please wait" in result.output | ||
| assert "Client running on http://localhost:20000" in result.output | ||
| docker = DockerInterface() | ||
| is_running = False | ||
| for container in docker.client.containers.list(): | ||
| if container.name in 'gigantum.labmanager': | ||
| is_running = True | ||
| break | ||
| assert is_running is True |
| from click.testing import CliRunner | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.commands.stop import stop | ||
| from gigantumcli.tests.test_cleanup_containers import fixture_create_dummy_containers | ||
| class TestStop: | ||
| def test_stop_auto_confirm(self, fixture_create_dummy_containers): | ||
| runner = CliRunner() | ||
| result = runner.invoke(stop, ['-y']) | ||
| assert result.exit_code == 0 | ||
| assert result.output == """- Cleaning up container for Project: test-project-2 | ||
| - Cleaning up container for Project: test-project | ||
| - Cleaning up Gigantum Client container | ||
| - Cleaning up Gigantum Client container | ||
| """ | ||
| docker_obj = DockerInterface() | ||
| running_containers = docker_obj.client.containers.list() | ||
| assert len(running_containers) == 1 | ||
| assert running_containers[0].name == 'rando' |
| import pytest | ||
| from unittest import mock | ||
| from docker.errors import ImageNotFound | ||
| import getpass | ||
| import click | ||
| from click.testing import CliRunner | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.commands.update import update | ||
| from gigantumcli.tests.fixtures import fixture_temp_work_dir, fixture_remove_client | ||
| class TestUpdate(object): | ||
| def test_update(self, fixture_remove_client): | ||
| runner = CliRunner() | ||
| docker_obj = DockerInterface() | ||
| # image should exist not exist before install | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker_obj.client.images.get('gigantum/labmanager:latest') | ||
| assert "Image should not exist" | ||
| except ImageNotFound: | ||
| pass | ||
| # Pull old image | ||
| old_tag = "55f05c26" | ||
| docker_obj.client.images.pull("gigantum/labmanager", old_tag) | ||
| docker_obj.client.api.tag('{}:{}'.format("gigantum/labmanager", old_tag), "gigantum/labmanager", 'latest') | ||
| result = runner.invoke(update, ['-y']) | ||
| assert result.exit_code == 0 | ||
| assert "Successfully pulled" in result.output | ||
| # Latest should be a new image | ||
| current_image = docker_obj.client.images.get("{}:latest".format("gigantum/labmanager")) | ||
| short_id = current_image.short_id.split(':')[1] | ||
| print(short_id) | ||
| assert old_tag != short_id | ||
| [console_scripts] | ||
| gigantum = gigantumcli.cli:main | ||
| gigantum = gigantumcli.cli:cli | ||
| Metadata-Version: 2.1 | ||
| Name: gigantum | ||
| Version: 1.2.1 | ||
| Summary: CLI for the Gigantum Platform | ||
| Version: 1.3.0 | ||
| Summary: CLI for the Gigantum Client | ||
| Home-page: https://github.com/gigantum/gigantum-cli | ||
@@ -16,3 +16,3 @@ Author: Gigantum, Inc. | ||
| Simple user-facing command line interface (CLI) for installing and running the | ||
| Gigantum application locally | ||
| Gigantum Client locally | ||
@@ -22,4 +22,4 @@ ## Introduction | ||
| This Python package is provided as a method to install and run the Gigantum | ||
| application, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, and stop the application. | ||
| Client, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, stop, and configure the application. | ||
@@ -63,3 +63,3 @@ More detailed install instructions can be found at the Gigantum | ||
| - Ubuntu: | ||
| - (Recommended Method) Install using Docker's "helper" script, which | ||
| - Install using Docker's "helper" script, which | ||
| will perform all install steps for you (you may inspect the | ||
@@ -89,4 +89,3 @@ get-docker.sh script before running it): | ||
| - Note, Docker provides this warning when doing this, which in most | ||
| cases is not an issue (it is similar to the risks associated with sudo | ||
| access): | ||
| cases is not an issue: | ||
@@ -152,40 +151,20 @@ > WARNING: Adding a user to the "docker" group will grant the ability | ||
| ``` | ||
| $ gigantum [-h] [--tag <tag>] action | ||
| ``` | ||
| $ gigantum -h | ||
| Usage: gigantum [OPTIONS] COMMAND [ARGS]... | ||
| #### Actions | ||
| A Command Line Interface to manage the Gigantum Client. | ||
| - `install` | ||
| - **Run this command after installing the CLI for the first time.** | ||
| - Depending on your bandwidth, installing for the first time can take a while | ||
| as the Docker Image layers are downloaded. | ||
| - This command installs the Gigantum application Docker Image for the first | ||
| time and configures your working directory. | ||
| Options: | ||
| -h, --help Show this message and exit. | ||
| - `update` | ||
| - This command updates an existing installation to the latest version of the | ||
| application | ||
| - If you have the latest version, nothing happens, so it is safe to run this | ||
| command at any time. | ||
| - When you run `update`, the changelog for the new version is displayed and | ||
| you are asked to confirm the upload before it begins. | ||
| - Optionally, you can use the `--tag` option to install a specific version | ||
| instead of the latest | ||
| Commands: | ||
| config Manage the Client configuration file. | ||
| feedback Open the default web browser to provide feedback. | ||
| install Install the Gigantum Client Docker Image. | ||
| server Manage server connections for this Client instance. | ||
| start Start Gigantum Client. | ||
| stop Stop Gigantum Client. | ||
| update Check if an update is available for Gigantum Client. | ||
| ``` | ||
| - `start` | ||
| - This command starts the Gigantum application | ||
| - Once started, the application User Inteface is available at | ||
| [http://localhost:10000](http://localhost:10000) | ||
| - **Once you create your first LabBook, check your Gigantum working directory | ||
| for LabBook to make sure everything is configured properly. See the | ||
| `Gigantum Working Directory` section for more details.** | ||
| - `stop` | ||
| - This command currently stops and removes all Gigantum managed Docker | ||
| containers and performs a container prune operation. | ||
| - `feedback` | ||
| - This command opens a browser to discussion board where you can report bugs, | ||
| suggestions, desired features, etc. | ||
| ## Usage | ||
@@ -200,3 +179,3 @@ | ||
| The Gigantum working directory location changes based on your operating system: | ||
| The default working directory location changes based on your operating system: | ||
@@ -208,3 +187,3 @@ - **Windows**: `C:\Users\<username>\gigantum` | ||
| This directory follows a standard directory structure that organizes content by | ||
| user and namespace. A namespace is the "owner" of a Project, and typically the | ||
| user and namespace. A namespace is the "owner" of a Project or Dataset, and typically the | ||
| creator. The working directory is organized as illustrated below: | ||
@@ -214,6 +193,10 @@ | ||
| <Gigantum Working Directory> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ servers | ||
| |_ <server id> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ datasets | ||
| |_ <dataset name> | ||
| ``` | ||
@@ -226,9 +209,11 @@ | ||
| <Gigantum Working Directory> | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| |_ servers | ||
| |_ gigantum-com | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| ``` | ||
@@ -240,10 +225,5 @@ | ||
| To use the Gigantum application you must have a Gigantum user account. When you | ||
| run the application for the first time you can register. | ||
| run the application for the first time you can register with the default server gigantum.com. | ||
| Note that you'll get an extra warning about granting the application access to | ||
| your account when you sign in for the first time. This is an extra security | ||
| measure that occurs because the app is running on localhost and not a verified | ||
| domain. This is expected. | ||
| Once you login, your user identity is cached locally. This lets you run the | ||
| By default, once you login your user identity is cached locally. This lets you run the | ||
| application when disconnected from the internet and without needing to log in | ||
@@ -306,5 +286,6 @@ again. If you logout, you will not be able to use the application again until | ||
| Keywords: data-science,science,gigantum,open-science | ||
| Keywords: data-science,science,gigantum,open-science,machine-learning | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 4 - Beta | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
@@ -311,0 +292,0 @@ Classifier: Programming Language :: Python :: 3.6 |
| docker==4.3.1 | ||
| six==1.12.0 | ||
| PyYAML==5.4.1 | ||
| click==7.1.2 |
@@ -13,12 +13,25 @@ LICENSE | ||
| gigantumcli/__init__.py | ||
| gigantumcli/actions.py | ||
| gigantumcli/changelog.py | ||
| gigantumcli/cli.py | ||
| gigantumcli/config.py | ||
| gigantumcli/dockerinterface.py | ||
| gigantumcli/server.py | ||
| gigantumcli/utilities.py | ||
| gigantumcli/commands/__init__.py | ||
| gigantumcli/commands/config.py | ||
| gigantumcli/commands/feedback.py | ||
| gigantumcli/commands/install.py | ||
| gigantumcli/commands/server.py | ||
| gigantumcli/commands/start.py | ||
| gigantumcli/commands/stop.py | ||
| gigantumcli/commands/update.py | ||
| gigantumcli/tests/__init__.py | ||
| gigantumcli/tests/test_actions.py | ||
| gigantumcli/tests/fixtures.py | ||
| gigantumcli/tests/test_changelog.py | ||
| gigantumcli/tests/test_cleanup_containers.py | ||
| gigantumcli/tests/test_server.py | ||
| gigantumcli/tests/test_config.py | ||
| gigantumcli/tests/test_install.py | ||
| gigantumcli/tests/test_server.py | ||
| gigantumcli/tests/test_start.py | ||
| gigantumcli/tests/test_stop.py | ||
| gigantumcli/tests/test_update.py |
| # Gigantum CLI Version | ||
| __version__ = "1.2.1" | ||
| __version__ = "1.3.0" |
@@ -1,22 +0,2 @@ | ||
| # Copyright (c) 2017 FlashX, LLC | ||
| # | ||
| # 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. | ||
| import requests | ||
| import json | ||
@@ -23,0 +3,0 @@ |
+29
-74
@@ -1,87 +0,42 @@ | ||
| import argparse | ||
| from gigantumcli.actions import install, update, start, stop, feedback, list_servers, add_server, ExitCLI | ||
| import sys | ||
| import click | ||
| from gigantumcli.commands.install import install | ||
| from gigantumcli.commands.update import update | ||
| from gigantumcli.commands.start import start | ||
| from gigantumcli.commands.stop import stop | ||
| from gigantumcli.commands.feedback import feedback | ||
| from gigantumcli.commands.server import server | ||
| from gigantumcli.commands.config import config | ||
| def main(): | ||
| # Setup supported components and commands | ||
| actions = {"install": "Install the Gigantum Client Docker Image", | ||
| "update": "Update the Gigantum Client Docker Image", | ||
| "start": "Start the Client", | ||
| "stop": "Stop the Client", | ||
| "add-server": "Add a new Team or Enterprise server to this Client installation", | ||
| "list-servers": "List the available servers for this Client installation", | ||
| "feedback": "Open a web page to provide feedback" | ||
| } | ||
| CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) | ||
| help_str = "" | ||
| for action in actions: | ||
| help_str = "{} - {}: {}\n".format(help_str, action, actions[action]) | ||
| description_str = "Command Line Interface to use the local Gigantum Client application\n\n" | ||
| description_str = description_str + "The following actions are supported:\n\n{}".format(help_str) | ||
| @click.group(help="A Command Line Interface to manage the Gigantum Client.", context_settings=CONTEXT_SETTINGS) | ||
| def cli(): | ||
| pass | ||
| parser = argparse.ArgumentParser(description=description_str, | ||
| formatter_class=argparse.RawTextHelpFormatter) | ||
| parser.add_argument("--tag", "-t", | ||
| default=None, | ||
| metavar="<tag>", | ||
| help="Image Tag to override the 'latest' Docker Image when updating") | ||
| # Add commands from package | ||
| cli.add_command(install) | ||
| cli.add_command(update) | ||
| cli.add_command(start) | ||
| cli.add_command(stop) | ||
| cli.add_command(feedback) | ||
| cli.add_command(server) | ||
| cli.add_command(config) | ||
| parser.add_argument("--edge", "-e", | ||
| action='store_true', | ||
| help="Optional flag indicating if the edge version should be used." | ||
| " Applicable to install, update, and start commands. You must have access to this image.") | ||
| parser.add_argument("--wait", "-w", | ||
| metavar="<seconds>", | ||
| type=int, | ||
| default=60, | ||
| help="Number of seconds to wait for Client during `start`") | ||
| # Deprecated commands | ||
| @cli.command('add-server', hidden=True) | ||
| def add_server(): | ||
| raise click.UsageError("`gigantum add-server` has been deprecated. Did you mean `gigantum server add`?") | ||
| parser.add_argument("--yes", "-y", | ||
| action='store_true', | ||
| help="Optional flag to automatically accept confirmation prompts") | ||
| parser.add_argument("--working-dir", "-d", | ||
| metavar="<working directory>", | ||
| default="~/gigantum", | ||
| help="Optional parameter to specify a location for the gigantum working directory when starting" | ||
| "the Client, other than the default (~/gigantum)") | ||
| @cli.command('list-servers', hidden=True) | ||
| def list_servers(): | ||
| raise click.UsageError("`gigantum list-servers` has been deprecated. Did you mean `gigantum server list`?") | ||
| # Deprecated commands | ||
| parser.add_argument("action", help="Action to perform") | ||
| args = parser.parse_args() | ||
| if not args.edge: | ||
| image_name = 'gigantum/labmanager' | ||
| else: | ||
| image_name = 'gigantum/labmanager-edge' | ||
| try: | ||
| if args.action == "install": | ||
| install(image_name) | ||
| elif args.action == "update": | ||
| update(image_name, args.tag, args.yes) | ||
| elif args.action == "start": | ||
| start(image_name, timeout=args.wait, tag=args.tag, working_dir=args.working_dir, | ||
| accept_confirmation=args.yes) | ||
| elif args.action == "stop": | ||
| stop(args.yes) | ||
| elif args.action == "feedback": | ||
| feedback() | ||
| elif args.action == "add-server": | ||
| add_server(working_dir=args.working_dir) | ||
| elif args.action == "list-servers": | ||
| list_servers(working_dir=args.working_dir) | ||
| else: | ||
| raise ValueError("Unsupported action `{}` provided. Available actions: {}".format(args.action, | ||
| ", ".join(actions.keys()))) | ||
| except ExitCLI as err: | ||
| print(err) | ||
| sys.exit(1) | ||
| if __name__ == '__main__': | ||
| main() | ||
| cli() |
@@ -5,3 +5,3 @@ import socket | ||
| import docker | ||
| import re | ||
| import click | ||
| import subprocess | ||
@@ -12,8 +12,15 @@ import platform | ||
| from gigantumcli.utilities import ask_question, ExitCLI | ||
| from gigantumcli.utilities import ask_question | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| if platform.system() == 'Windows': | ||
| from pywintypes import error as TempDockerError | ||
| else: | ||
| class TempDockerError(OSError): | ||
| pass | ||
| class DockerInterface: | ||
| """Class to provide an interface to Docker""" | ||
| def __init__(self): | ||
@@ -29,7 +36,7 @@ """Constructor""" | ||
| self._print_running_help() | ||
| raise ExitCLI() | ||
| raise click.Abort() | ||
| else: | ||
| # Docker isn't installed | ||
| self._print_installing_help() | ||
| raise ExitCLI() | ||
| raise click.Abort() | ||
@@ -80,3 +87,4 @@ # Name of Docker volume used to share between containers | ||
| else: | ||
| raise ExitCLI("You must install Docker to use the Gigantum application") | ||
| click.echo("You must install Docker to use the Gigantum Client", err=True) | ||
| raise click.Abort() | ||
@@ -198,3 +206,3 @@ elif queried_system == 'Darwin': | ||
| def _get_docker_client(self, check_server_version=True, fallback=True): | ||
| def _get_docker_client(self): | ||
| """Return a docker client with proper version to match server API. | ||
@@ -209,15 +217,4 @@ | ||
| """ | ||
| return docker.from_env() | ||
| if check_server_version: | ||
| try: | ||
| docker_server_api_version = self._get_docker_server_api_version() | ||
| return docker.from_env(version=docker_server_api_version) | ||
| except ValueError as e: | ||
| if fallback: | ||
| return docker.from_env() | ||
| else: | ||
| raise e | ||
| else: | ||
| return docker.from_env() | ||
| def share_volume_exists(self): | ||
@@ -251,1 +248,62 @@ """Check if the container-container share volume exists | ||
| volume.remove() | ||
| def cleanup_containers(self) -> None: | ||
| """Method to clean up gigantum managed containers, stopping if needed. | ||
| Note: this method is the only place removal of containers should occur | ||
| Returns: | ||
| None | ||
| """ | ||
| docker = DockerInterface() | ||
| # Stop any project containers | ||
| for container in docker.client.containers.list(all=True): | ||
| if "gmlb-" in container.name: | ||
| _, user, owner, project_name = container.name.split('-', 3) | ||
| print('- Cleaning up container for Project: {}'.format(project_name)) | ||
| container.stop() | ||
| container.remove() | ||
| # Stop app container | ||
| try: | ||
| app_container = docker.client.containers.get("gigantum.labmanager-edge") | ||
| print('- Cleaning up Gigantum Client container') | ||
| app_container.stop() | ||
| app_container.remove() | ||
| except NotFound: | ||
| pass | ||
| except (requests.exceptions.ChunkedEncodingError, TempDockerError): | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| pass | ||
| try: | ||
| app_container = docker.client.containers.get("gigantum.labmanager") | ||
| print('- Cleaning up Gigantum Client container') | ||
| app_container.stop() | ||
| app_container.remove() | ||
| except NotFound: | ||
| pass | ||
| except (requests.exceptions.ChunkedEncodingError, TempDockerError): | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| pass | ||
| @staticmethod | ||
| def image_name(is_edge: bool) -> str: | ||
| """Helper method to get the image name | ||
| Args: | ||
| is_edge: | ||
| Returns: | ||
| str | ||
| """ | ||
| if is_edge: | ||
| image_name = 'gigantum/labmanager-edge' | ||
| else: | ||
| image_name = 'gigantum/labmanager' | ||
| return image_name |
@@ -6,4 +6,6 @@ from urllib.parse import urljoin, urlparse | ||
| import requests | ||
| from gigantumcli.utilities import ExitCLI, ask_question | ||
| from gigantumcli.utilities import ask_question | ||
| import urllib3 | ||
| import shutil | ||
| import click | ||
@@ -52,3 +54,4 @@ | ||
| # User decided not to proceed. | ||
| raise ExitCLI("SSL Verification failed on server located at {}.".format(url)) | ||
| click.echo("SSL Verification failed on server located at {}.".format(url)) | ||
| raise click.Abort() | ||
| except requests.exceptions.ConnectionError: | ||
@@ -80,4 +83,4 @@ pass | ||
| if not succeed: | ||
| raise ExitCLI("Failed to discover configuration for server located at" | ||
| " {}. Check server URL and try again.".format(url)) | ||
| raise click.UsageError("Failed to discover configuration for server located at" | ||
| " `{}`. Check server URL and try again.".format(url)) | ||
| return data, verify | ||
@@ -113,4 +116,4 @@ | ||
| if response.status_code != 200: | ||
| raise ExitCLI("Failed to load auth configuration " | ||
| "for server located at {}: {}".format(url, response.status_code)) | ||
| raise click.UsageError("Failed to load auth configuration " | ||
| "for server located at {}: {}".format(url, response.status_code)) | ||
| auth_data = response.json() | ||
@@ -144,2 +147,5 @@ | ||
| configured_servers = list() | ||
| id_len = 0 | ||
| name_len = 0 | ||
| url_len = 0 | ||
| for server_file in glob.glob(os.path.join(self.servers_dir, "*.json")): | ||
@@ -151,9 +157,65 @@ with open(server_file, 'rt') as f: | ||
| configured_servers.append((data['server']['id'], data['server']['name'], server_url)) | ||
| id_len = max(id_len, len(data['server']['id'])) | ||
| name_len = max(name_len, len(data['server']['name'])) | ||
| url_len = max(url_len, len(server_url)) | ||
| if should_print: | ||
| print('%-30s' % "Server ID", '%-30s' % "Server Name", '%-30s' % "Server Location") | ||
| id_len += 5 | ||
| name_len += 5 | ||
| url_len += 5 | ||
| print("Server ID".ljust(id_len), "Server Name".ljust(name_len), "Server Location".ljust(url_len)) | ||
| for server in configured_servers: | ||
| print('%-30s' % server[0], '%-30s' % server[1], '%-30s' % server[2]) | ||
| print(server[0].ljust(id_len), server[1].ljust(name_len), server[2].ljust(url_len)) | ||
| print("\n") | ||
| return configured_servers | ||
| def remove_server(self, server_id: str) -> None: | ||
| """Method to remove a server | ||
| Args: | ||
| server_id(str): ID of the server to remove | ||
| Returns: | ||
| None | ||
| """ | ||
| # Set CURRENT to a reasonable value if it is set to the server being removed | ||
| current_path = os.path.join(self.servers_dir, 'CURRENT') | ||
| with open(current_path, 'rt') as cf: | ||
| current_server = cf.read() | ||
| if current_server == server_id: | ||
| new_current_server = None | ||
| servers = self.list_servers() | ||
| # Try gigantum-com first | ||
| match = [s for s in servers if s[0] == "gigantum-com"] | ||
| if match: | ||
| new_current_server = "gigantum-com" | ||
| else: | ||
| other_servers = [s for s in servers if s[0] != server_id] | ||
| if len(other_servers) == 0: | ||
| raise ValueError("You must have another valid server configured (e.g. gigantum.com) before " | ||
| "removing this server") | ||
| # Just pick the first one | ||
| new_current_server = other_servers[0][0] | ||
| with open(current_path, 'wt') as cf: | ||
| cf.write(new_current_server) | ||
| # Remove user data dir | ||
| shutil.rmtree(os.path.join(self.working_dir, 'servers', server_id), ignore_errors=True) | ||
| # Remove dataset file cache (if it exists) | ||
| shutil.rmtree(os.path.join(self.working_dir, '.labmanager', 'datasets', server_id), ignore_errors=True) | ||
| # Remove sensitive file cache (if it exists | ||
| shutil.rmtree(os.path.join(self.working_dir, '.labmanager', 'secrets', server_id), ignore_errors=True) | ||
| # Remove cached JWKS | ||
| jwks_file = os.path.join(self.working_dir, '.labmanager', 'identity', "{}-jwks.json".format(server_id)) | ||
| if os.path.isfile(jwks_file): | ||
| os.remove(jwks_file) | ||
| # Remove server configuration | ||
| os.remove(os.path.join(self.servers_dir, "{}.json".format(server_id))) |
| import pytest | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.actions import _cleanup_containers | ||
| from gigantumcli.actions import stop | ||
@@ -11,4 +9,4 @@ | ||
| """Fixture start fake project and client containerss""" | ||
| docker = DockerInterface() | ||
| docker.client.images.pull("busybox", 'latest') | ||
| docker_obj = DockerInterface() | ||
| docker_obj.client.images.pull("busybox", 'latest') | ||
@@ -20,11 +18,11 @@ names = ['gigantum.labmanager-edge', 'gigantum.labmanager', 'gmlb-test-test-test-project', | ||
| for name in names: | ||
| containers.append(docker.client.containers.run(image="busybox:latest", | ||
| detach=True, | ||
| name=name, | ||
| init=True, | ||
| command="tail -f /dev/null")) | ||
| containers.append(docker_obj.client.containers.run(image="busybox:latest", | ||
| detach=True, | ||
| name=name, | ||
| init=True, | ||
| command="tail -f /dev/null")) | ||
| yield | ||
| # make sure all containers are moved in case of failures | ||
| for container in docker.client.containers.list(all=True): | ||
| for container in docker_obj.client.containers.list(all=True): | ||
| if container.name in names: | ||
@@ -37,6 +35,6 @@ container.stop() | ||
| def test_cleanup_containers_running(self, fixture_create_dummy_containers): | ||
| docker = DockerInterface() | ||
| _cleanup_containers() | ||
| docker_obj = DockerInterface() | ||
| docker_obj.cleanup_containers() | ||
| running_containers = docker.client.containers.list() | ||
| running_containers = docker_obj.client.containers.list() | ||
| assert len(running_containers) == 1 | ||
@@ -46,11 +44,11 @@ assert running_containers[0].name == 'rando' | ||
| def test_cleanup_containers_stopped(self, fixture_create_dummy_containers): | ||
| docker = DockerInterface() | ||
| docker_obj = DockerInterface() | ||
| # Stop some containers, simulating what happens when docker is kill while running | ||
| c1 = docker.client.containers.get('gmlb-test-test-test-project') | ||
| c1 = docker_obj.client.containers.get('gmlb-test-test-test-project') | ||
| c1.stop() | ||
| c2 = docker.client.containers.get('gigantum.labmanager') | ||
| c2 = docker_obj.client.containers.get('gigantum.labmanager') | ||
| c2.stop() | ||
| running_containers = docker.client.containers.list() | ||
| running_containers = docker_obj.client.containers.list() | ||
| running_containers = [x.name for x in running_containers] | ||
@@ -62,3 +60,3 @@ assert len(running_containers) == 3 | ||
| all_containers = docker.client.containers.list(all=True) | ||
| all_containers = docker_obj.client.containers.list(all=True) | ||
| all_containers = [x.name for x in all_containers] | ||
@@ -72,5 +70,5 @@ assert len(all_containers) >= 5 | ||
| _cleanup_containers() | ||
| docker_obj.cleanup_containers() | ||
| running_containers = docker.client.containers.list() | ||
| running_containers = docker_obj.client.containers.list() | ||
| running_containers = [x.name for x in running_containers] | ||
@@ -80,3 +78,3 @@ assert len(running_containers) == 1 | ||
| all_containers = docker.client.containers.list(all=True) | ||
| all_containers = docker_obj.client.containers.list(all=True) | ||
| all_containers = [x.name for x in all_containers] | ||
@@ -89,9 +87,1 @@ assert len(all_containers) >= 1 | ||
| assert 'gigantum.labmanager' not in all_containers | ||
| def test_stop_auto_confirm(self, fixture_create_dummy_containers): | ||
| docker = DockerInterface() | ||
| stop(True) | ||
| running_containers = docker.client.containers.list() | ||
| assert len(running_containers) == 1 | ||
| assert running_containers[0].name == 'rando' |
@@ -7,5 +7,5 @@ import pytest | ||
| import responses | ||
| import click | ||
| from gigantumcli.server import ServerConfig | ||
| from gigantumcli.utilities import ExitCLI | ||
@@ -18,2 +18,3 @@ | ||
| os.mkdir(unit_test_working_dir) | ||
| os.makedirs(os.path.join(unit_test_working_dir, '.labmanager', 'identity')) | ||
| yield ServerConfig(working_dir=unit_test_working_dir) | ||
@@ -33,3 +34,3 @@ shutil.rmtree(unit_test_working_dir) | ||
| with pytest.raises(ExitCLI): | ||
| with pytest.raises(click.UsageError): | ||
| server_config.add_server("test2.gigantum.com") | ||
@@ -58,6 +59,6 @@ | ||
| with pytest.raises(ExitCLI): | ||
| with pytest.raises(click.UsageError): | ||
| server_config.add_server("https://test2.gigantum.com/") | ||
| with pytest.raises(ExitCLI): | ||
| with pytest.raises(click.UsageError): | ||
| server_config.add_server("https://thiswillneverwork.gigantum.com/") | ||
@@ -195,1 +196,118 @@ | ||
| assert len(server_list) == 2 | ||
| @responses.activate | ||
| def test_remove_server_only_one(self, server_config): | ||
| responses.add(responses.GET, 'https://test2.gigantum.com/gigantum/.well-known/discover.json', | ||
| json={"id": 'another-server', | ||
| "name": "Another server", | ||
| "git_url": "https://test2.repo.gigantum.com/", | ||
| "git_server_type": "gitlab", | ||
| "hub_api_url": "https://test2.gigantum.com/api/v1/", | ||
| "object_service_url": "https://test2.api.gigantum.com/object-v1/", | ||
| "user_search_url": "https://user-search2.us-east-1.cloudsearch.amazonaws.com", | ||
| "lfs_enabled": True, | ||
| "auth_config_url": "https://test2.gigantum.com/gigantum/.well-known/auth.json"}, | ||
| status=200) | ||
| responses.add(responses.GET, 'https://test2.gigantum.com/gigantum/.well-known/auth.json', | ||
| json={"audience": "test2.api.gigantum.io", | ||
| "issuer": "https://test2-auth.gigantum.com", | ||
| "signing_algorithm": "RS256", | ||
| "public_key_url": "https://test2-auth.gigantum.com/.well-known/jwks.json", | ||
| "login_url": "https://test2.gigantum.com/client/login", | ||
| "login_type": "auth0", | ||
| "auth0_client_id": "0000000000000000"}, | ||
| status=200) | ||
| server_id = server_config.add_server("https://test2.gigantum.com/") | ||
| os.makedirs(os.path.join(server_config.working_dir, '.labmanager', 'servers'), exist_ok=True) | ||
| with open(os.path.join(server_config.working_dir, '.labmanager', 'servers', 'CURRENT'), 'wt') as cf: | ||
| cf.write("another-server") | ||
| assert server_id == 'another-server' | ||
| assert os.path.isfile(os.path.join(server_config.servers_dir, 'another-server.json')) | ||
| assert os.path.isdir(os.path.join(server_config.working_dir, 'servers', 'another-server')) | ||
| with pytest.raises(ValueError): | ||
| server_config.remove_server('another-server') | ||
| @responses.activate | ||
| def test_remove_server(self, server_config): | ||
| responses.add(responses.GET, 'https://test2.gigantum.com/gigantum/.well-known/discover.json', | ||
| json={"id": 'another-server', | ||
| "name": "Another server", | ||
| "git_url": "https://test2.repo.gigantum.com/", | ||
| "git_server_type": "gitlab", | ||
| "hub_api_url": "https://test2.gigantum.com/api/v1/", | ||
| "object_service_url": "https://test2.api.gigantum.com/object-v1/", | ||
| "user_search_url": "https://user-search2.us-east-1.cloudsearch.amazonaws.com", | ||
| "lfs_enabled": True, | ||
| "auth_config_url": "https://test2.gigantum.com/gigantum/.well-known/auth.json"}, | ||
| status=200) | ||
| responses.add(responses.GET, 'https://test2.gigantum.com/gigantum/.well-known/auth.json', | ||
| json={"audience": "test2.api.gigantum.io", | ||
| "issuer": "https://test2-auth.gigantum.com", | ||
| "signing_algorithm": "RS256", | ||
| "public_key_url": "https://test2-auth.gigantum.com/.well-known/jwks.json", | ||
| "login_url": "https://test2.gigantum.com/client/login", | ||
| "login_type": "auth0", | ||
| "auth0_client_id": "0000000000000000"}, | ||
| status=200) | ||
| responses.add(responses.GET, 'https://test3.gigantum.com/gigantum/.well-known/discover.json', | ||
| json={"id": 'my-server', | ||
| "name": "My Server 1", | ||
| "git_url": "https://test3.repo.gigantum.com/", | ||
| "git_server_type": "gitlab", | ||
| "hub_api_url": "https://test3.gigantum.com/api/v1/", | ||
| "object_service_url": "https://test3.api.gigantum.com/object-v1/", | ||
| "user_search_url": "https://user-search3.us-east-1.cloudsearch.amazonaws.com", | ||
| "lfs_enabled": True, | ||
| "auth_config_url": "https://test3.gigantum.com/gigantum/.well-known/auth.json"}, | ||
| status=200) | ||
| responses.add(responses.GET, 'https://test3.gigantum.com/gigantum/.well-known/auth.json', | ||
| json={"audience": "test3.api.gigantum.io", | ||
| "issuer": "https://test3-auth.gigantum.com", | ||
| "signing_algorithm": "RS256", | ||
| "public_key_url": "https://test3-auth.gigantum.com/.well-known/jwks.json", | ||
| "login_url": "https://test3.gigantum.com/client/login", | ||
| "login_type": "auth0", | ||
| "auth0_client_id": "0000000000000000"}, | ||
| status=200) | ||
| server_id = server_config.add_server("https://test2.gigantum.com/") | ||
| assert server_id == 'another-server' | ||
| server_id = server_config.add_server("https://test3.gigantum.com/") | ||
| assert server_id == 'my-server' | ||
| # mock some more stuff | ||
| server_file = os.path.join(server_config.servers_dir, "another-server.json") | ||
| with open(os.path.join(server_config.servers_dir, 'CURRENT'), 'wt') as cf: | ||
| cf.write("another-server") | ||
| cached_jwks = os.path.join(server_config.working_dir, '.labmanager', 'identity', | ||
| 'another-server-jwks.json') | ||
| with open(cached_jwks, 'wt') as jf: | ||
| jf.write("FAKE DATA") | ||
| test_user_data = os.path.join(server_config.working_dir, 'servers', 'another-server', | ||
| 'TEST_FILE') | ||
| with open(test_user_data, 'wt') as jf: | ||
| jf.write("FAKE DATA") | ||
| assert os.path.isfile(test_user_data) | ||
| assert os.path.isfile(cached_jwks) | ||
| assert os.path.isfile(server_file) | ||
| assert os.path.isdir(os.path.join(server_config.working_dir, 'servers', 'another-server')) | ||
| server_config.remove_server('another-server') | ||
| assert not os.path.isfile(test_user_data) | ||
| assert not os.path.isfile(cached_jwks) | ||
| assert not os.path.isfile(server_file) | ||
| assert not os.path.isdir(os.path.join(server_config.working_dir, 'servers', 'another-server')) | ||
| current_path = os.path.join(server_config.servers_dir, 'CURRENT') | ||
| with open(current_path, 'rt') as cf: | ||
| assert cf.read() == 'my-server' |
@@ -1,20 +0,1 @@ | ||
| # Copyright (c) 2017 FlashX, LLC | ||
| # | ||
| # 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. | ||
| from __future__ import print_function | ||
@@ -30,7 +11,2 @@ from six.moves import input | ||
| class ExitCLI(Exception): | ||
| """Custom Exception when exit with 1 should happen""" | ||
| pass | ||
| def ask_question(question, accept_confirmation=False): | ||
@@ -37,0 +13,0 @@ """Method to ask the user a yes/no question |
+44
-63
| Metadata-Version: 2.1 | ||
| Name: gigantum | ||
| Version: 1.2.1 | ||
| Summary: CLI for the Gigantum Platform | ||
| Version: 1.3.0 | ||
| Summary: CLI for the Gigantum Client | ||
| Home-page: https://github.com/gigantum/gigantum-cli | ||
@@ -16,3 +16,3 @@ Author: Gigantum, Inc. | ||
| Simple user-facing command line interface (CLI) for installing and running the | ||
| Gigantum application locally | ||
| Gigantum Client locally | ||
@@ -22,4 +22,4 @@ ## Introduction | ||
| This Python package is provided as a method to install and run the Gigantum | ||
| application, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, and stop the application. | ||
| Client, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, stop, and configure the application. | ||
@@ -63,3 +63,3 @@ More detailed install instructions can be found at the Gigantum | ||
| - Ubuntu: | ||
| - (Recommended Method) Install using Docker's "helper" script, which | ||
| - Install using Docker's "helper" script, which | ||
| will perform all install steps for you (you may inspect the | ||
@@ -89,4 +89,3 @@ get-docker.sh script before running it): | ||
| - Note, Docker provides this warning when doing this, which in most | ||
| cases is not an issue (it is similar to the risks associated with sudo | ||
| access): | ||
| cases is not an issue: | ||
@@ -152,40 +151,20 @@ > WARNING: Adding a user to the "docker" group will grant the ability | ||
| ``` | ||
| $ gigantum [-h] [--tag <tag>] action | ||
| ``` | ||
| $ gigantum -h | ||
| Usage: gigantum [OPTIONS] COMMAND [ARGS]... | ||
| #### Actions | ||
| A Command Line Interface to manage the Gigantum Client. | ||
| - `install` | ||
| - **Run this command after installing the CLI for the first time.** | ||
| - Depending on your bandwidth, installing for the first time can take a while | ||
| as the Docker Image layers are downloaded. | ||
| - This command installs the Gigantum application Docker Image for the first | ||
| time and configures your working directory. | ||
| Options: | ||
| -h, --help Show this message and exit. | ||
| - `update` | ||
| - This command updates an existing installation to the latest version of the | ||
| application | ||
| - If you have the latest version, nothing happens, so it is safe to run this | ||
| command at any time. | ||
| - When you run `update`, the changelog for the new version is displayed and | ||
| you are asked to confirm the upload before it begins. | ||
| - Optionally, you can use the `--tag` option to install a specific version | ||
| instead of the latest | ||
| Commands: | ||
| config Manage the Client configuration file. | ||
| feedback Open the default web browser to provide feedback. | ||
| install Install the Gigantum Client Docker Image. | ||
| server Manage server connections for this Client instance. | ||
| start Start Gigantum Client. | ||
| stop Stop Gigantum Client. | ||
| update Check if an update is available for Gigantum Client. | ||
| ``` | ||
| - `start` | ||
| - This command starts the Gigantum application | ||
| - Once started, the application User Inteface is available at | ||
| [http://localhost:10000](http://localhost:10000) | ||
| - **Once you create your first LabBook, check your Gigantum working directory | ||
| for LabBook to make sure everything is configured properly. See the | ||
| `Gigantum Working Directory` section for more details.** | ||
| - `stop` | ||
| - This command currently stops and removes all Gigantum managed Docker | ||
| containers and performs a container prune operation. | ||
| - `feedback` | ||
| - This command opens a browser to discussion board where you can report bugs, | ||
| suggestions, desired features, etc. | ||
| ## Usage | ||
@@ -200,3 +179,3 @@ | ||
| The Gigantum working directory location changes based on your operating system: | ||
| The default working directory location changes based on your operating system: | ||
@@ -208,3 +187,3 @@ - **Windows**: `C:\Users\<username>\gigantum` | ||
| This directory follows a standard directory structure that organizes content by | ||
| user and namespace. A namespace is the "owner" of a Project, and typically the | ||
| user and namespace. A namespace is the "owner" of a Project or Dataset, and typically the | ||
| creator. The working directory is organized as illustrated below: | ||
@@ -214,6 +193,10 @@ | ||
| <Gigantum Working Directory> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ servers | ||
| |_ <server id> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ datasets | ||
| |_ <dataset name> | ||
| ``` | ||
@@ -226,9 +209,11 @@ | ||
| <Gigantum Working Directory> | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| |_ servers | ||
| |_ gigantum-com | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| ``` | ||
@@ -240,10 +225,5 @@ | ||
| To use the Gigantum application you must have a Gigantum user account. When you | ||
| run the application for the first time you can register. | ||
| run the application for the first time you can register with the default server gigantum.com. | ||
| Note that you'll get an extra warning about granting the application access to | ||
| your account when you sign in for the first time. This is an extra security | ||
| measure that occurs because the app is running on localhost and not a verified | ||
| domain. This is expected. | ||
| Once you login, your user identity is cached locally. This lets you run the | ||
| By default, once you login your user identity is cached locally. This lets you run the | ||
| application when disconnected from the internet and without needing to log in | ||
@@ -306,5 +286,6 @@ again. If you logout, you will not be able to use the application again until | ||
| Keywords: data-science,science,gigantum,open-science | ||
| Keywords: data-science,science,gigantum,open-science,machine-learning | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 4 - Beta | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
@@ -311,0 +292,0 @@ Classifier: Programming Language :: Python :: 3.6 |
+40
-60
@@ -8,3 +8,3 @@ ## Gigantum CLI | ||
| Simple user-facing command line interface (CLI) for installing and running the | ||
| Gigantum application locally | ||
| Gigantum Client locally | ||
@@ -14,4 +14,4 @@ ## Introduction | ||
| This Python package is provided as a method to install and run the Gigantum | ||
| application, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, and stop the application. | ||
| Client, locally on your computer. It provides a simple command line | ||
| interface to install, update, start, stop, and configure the application. | ||
@@ -55,3 +55,3 @@ More detailed install instructions can be found at the Gigantum | ||
| - Ubuntu: | ||
| - (Recommended Method) Install using Docker's "helper" script, which | ||
| - Install using Docker's "helper" script, which | ||
| will perform all install steps for you (you may inspect the | ||
@@ -81,4 +81,3 @@ get-docker.sh script before running it): | ||
| - Note, Docker provides this warning when doing this, which in most | ||
| cases is not an issue (it is similar to the risks associated with sudo | ||
| access): | ||
| cases is not an issue: | ||
@@ -144,40 +143,20 @@ > WARNING: Adding a user to the "docker" group will grant the ability | ||
| ``` | ||
| $ gigantum [-h] [--tag <tag>] action | ||
| ``` | ||
| $ gigantum -h | ||
| Usage: gigantum [OPTIONS] COMMAND [ARGS]... | ||
| #### Actions | ||
| A Command Line Interface to manage the Gigantum Client. | ||
| - `install` | ||
| - **Run this command after installing the CLI for the first time.** | ||
| - Depending on your bandwidth, installing for the first time can take a while | ||
| as the Docker Image layers are downloaded. | ||
| - This command installs the Gigantum application Docker Image for the first | ||
| time and configures your working directory. | ||
| Options: | ||
| -h, --help Show this message and exit. | ||
| - `update` | ||
| - This command updates an existing installation to the latest version of the | ||
| application | ||
| - If you have the latest version, nothing happens, so it is safe to run this | ||
| command at any time. | ||
| - When you run `update`, the changelog for the new version is displayed and | ||
| you are asked to confirm the upload before it begins. | ||
| - Optionally, you can use the `--tag` option to install a specific version | ||
| instead of the latest | ||
| Commands: | ||
| config Manage the Client configuration file. | ||
| feedback Open the default web browser to provide feedback. | ||
| install Install the Gigantum Client Docker Image. | ||
| server Manage server connections for this Client instance. | ||
| start Start Gigantum Client. | ||
| stop Stop Gigantum Client. | ||
| update Check if an update is available for Gigantum Client. | ||
| ``` | ||
| - `start` | ||
| - This command starts the Gigantum application | ||
| - Once started, the application User Inteface is available at | ||
| [http://localhost:10000](http://localhost:10000) | ||
| - **Once you create your first LabBook, check your Gigantum working directory | ||
| for LabBook to make sure everything is configured properly. See the | ||
| `Gigantum Working Directory` section for more details.** | ||
| - `stop` | ||
| - This command currently stops and removes all Gigantum managed Docker | ||
| containers and performs a container prune operation. | ||
| - `feedback` | ||
| - This command opens a browser to discussion board where you can report bugs, | ||
| suggestions, desired features, etc. | ||
| ## Usage | ||
@@ -192,3 +171,3 @@ | ||
| The Gigantum working directory location changes based on your operating system: | ||
| The default working directory location changes based on your operating system: | ||
@@ -200,3 +179,3 @@ - **Windows**: `C:\Users\<username>\gigantum` | ||
| This directory follows a standard directory structure that organizes content by | ||
| user and namespace. A namespace is the "owner" of a Project, and typically the | ||
| user and namespace. A namespace is the "owner" of a Project or Dataset, and typically the | ||
| creator. The working directory is organized as illustrated below: | ||
@@ -206,6 +185,10 @@ | ||
| <Gigantum Working Directory> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ servers | ||
| |_ <server id> | ||
| |_ <logged in user's username> | ||
| |_ <namespace> | ||
| |_ labbooks | ||
| |_ <project name> | ||
| |_ datasets | ||
| |_ <dataset name> | ||
| ``` | ||
@@ -218,9 +201,11 @@ | ||
| <Gigantum Working Directory> | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| |_ servers | ||
| |_ gigantum-com | ||
| |_ sarah | ||
| |_ sarah | ||
| |_ labbooks | ||
| |_ my-first-labbook | ||
| |_ janet | ||
| |_ labbooks | ||
| |_ initial-analysis-1 | ||
| ``` | ||
@@ -232,10 +217,5 @@ | ||
| To use the Gigantum application you must have a Gigantum user account. When you | ||
| run the application for the first time you can register. | ||
| run the application for the first time you can register with the default server gigantum.com. | ||
| Note that you'll get an extra warning about granting the application access to | ||
| your account when you sign in for the first time. This is an extra security | ||
| measure that occurs because the app is running on localhost and not a verified | ||
| domain. This is expected. | ||
| Once you login, your user identity is cached locally. This lets you run the | ||
| By default, once you login your user identity is cached locally. This lets you run the | ||
| application when disconnected from the internet and without needing to log in | ||
@@ -242,0 +222,0 @@ again. If you logout, you will not be able to use the application again until |
+2
-0
@@ -5,1 +5,3 @@ # docker currently mandates a specific version range for requests (which we use) | ||
| six==1.12.0 | ||
| PyYAML==5.4.1 | ||
| click==7.1.2 |
+5
-3
@@ -24,3 +24,3 @@ from setuptools import setup | ||
| description='CLI for the Gigantum Platform', | ||
| description='CLI for the Gigantum Client', | ||
| long_description_content_type='text/markdown', | ||
@@ -34,3 +34,3 @@ long_description=long_description, | ||
| entry_points={ | ||
| 'console_scripts': ['gigantum=gigantumcli.cli:main'], | ||
| 'console_scripts': ['gigantum=gigantumcli.cli:cli'], | ||
| }, | ||
@@ -44,2 +44,3 @@ packages=['gigantumcli'], | ||
| 'Development Status :: 4 - Beta', | ||
| 'Programming Language :: Python :: 3.8', | ||
| 'Programming Language :: Python :: 3.7', | ||
@@ -54,4 +55,5 @@ 'Programming Language :: Python :: 3.6', | ||
| 'gigantum', | ||
| 'open-science' | ||
| 'open-science', | ||
| 'machine-learning' | ||
| ] | ||
| ) |
| import sys | ||
| import platform | ||
| from typing import Optional | ||
| from docker.errors import APIError, ImageNotFound, NotFound | ||
| import os | ||
| import webbrowser | ||
| import time | ||
| import requests | ||
| import uuid | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.changelog import ChangeLog | ||
| from gigantumcli.utilities import ask_question, ExitCLI, is_running_as_admin, get_nvidia_driver_version | ||
| from gigantumcli.server import ServerConfig | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| if platform.system() == 'Windows': | ||
| from pywintypes import error as TempDockerError | ||
| else: | ||
| class TempDockerError(OSError): | ||
| pass | ||
| def _cleanup_containers() -> None: | ||
| """Method to clean up gigantum managed containers, stopping if needed. | ||
| Note: this method is the only place removal of containers should occur | ||
| Returns: | ||
| None | ||
| """ | ||
| docker = DockerInterface() | ||
| # Stop any project containers | ||
| for container in docker.client.containers.list(all=True): | ||
| if "gmlb-" in container.name: | ||
| _, user, owner, project_name = container.name.split('-', 3) | ||
| print('- Cleaning up container for Project: {}'.format(project_name)) | ||
| container.stop() | ||
| container.remove() | ||
| # Stop app container | ||
| try: | ||
| app_container = docker.client.containers.get("gigantum.labmanager-edge") | ||
| print('- Cleaning up Gigantum Client container') | ||
| app_container.stop() | ||
| app_container.remove() | ||
| except NotFound: | ||
| pass | ||
| except (requests.exceptions.ChunkedEncodingError, TempDockerError): | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| pass | ||
| try: | ||
| app_container = docker.client.containers.get("gigantum.labmanager") | ||
| print('- Cleaning up Gigantum Client container') | ||
| app_container.stop() | ||
| app_container.remove() | ||
| except NotFound: | ||
| pass | ||
| except (requests.exceptions.ChunkedEncodingError, TempDockerError): | ||
| # Temporary fix due to docker 2.5.0.0 and docker-py failing when container doesn't exist | ||
| # See https://github.com/docker/docker-py/issues/2696 | ||
| pass | ||
| def install(image_name): | ||
| """Method to install the Gigantum Image | ||
| Args: | ||
| image_name(str): Image name, including repository and namespace (e.g. gigantum/labmanager) | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| raise ExitCLI("Do not run `gigantum install` as root.") | ||
| docker = DockerInterface() | ||
| try: | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker.client.images.get(image_name) | ||
| raise ExitCLI("** Gigantum Client image already installed. Run `gigantum update` instead.") | ||
| except ImageNotFound: | ||
| # Pull for the first time | ||
| print("\nDownloading and installing the Gigantum Client Docker Image. Please wait...\n") | ||
| cl = ChangeLog() | ||
| tag = cl.latest_tag() | ||
| image = docker.client.images.pull(image_name, tag) | ||
| docker.client.api.tag('{}:{}'.format(image_name, tag), image_name, 'latest') | ||
| except APIError as err: | ||
| msg = "ERROR: failed to pull image! Verify your internet connection and try again." | ||
| raise ExitCLI(msg) | ||
| short_id = image.short_id.split(':')[1] | ||
| print("\nSuccessfully pulled {}:{}\n".format(image_name, short_id)) | ||
| def update(image_name, tag=None, accept_confirmation=False): | ||
| """Method to update the existing image, warning about changes before accepting | ||
| Args: | ||
| image_name(str): Image name, including repository and namespace (e.g. gigantum/labmanager) | ||
| tag(str): Tag to pull if you wish to override `latest` | ||
| accept_confirmation(bool): Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| Returns: | ||
| None | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| raise ExitCLI("Do not run `gigantum update` as root.") | ||
| docker = DockerInterface() | ||
| try: | ||
| cl = ChangeLog() | ||
| if "edge" not in image_name: | ||
| # Normal install, so do checks | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = cl.latest_tag() | ||
| # Get id of current labmanager install | ||
| try: | ||
| current_image = docker.client.images.get("{}:latest".format(image_name)) | ||
| except ImageNotFound: | ||
| raise ExitCLI("Gigantum Client image not yet installed. Run 'gigantum install' first.") | ||
| short_id = current_image.short_id.split(':')[1] | ||
| # Check if there is an update available | ||
| if not cl.is_update_available(short_id): | ||
| print("Latest version already installed.") | ||
| sys.exit(0) | ||
| # Get Changelog info for the latest or specified version | ||
| try: | ||
| print(cl.get_changelog(tag)) | ||
| except ValueError as err: | ||
| raise ExitCLI(err) | ||
| else: | ||
| # Edge build, set tag if needed | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = "latest" | ||
| # Make sure user wants to pull | ||
| if ask_question("Are you sure you want to update?", accept_confirmation): | ||
| # Pull | ||
| print("\nDownloading and installing the Gigantum Client Docker Image. Please wait...\n") | ||
| image = docker.client.images.pull(image_name, tag) | ||
| # Tag to latest locally | ||
| docker.client.api.tag('{}:{}'.format(image_name, tag), image_name, 'latest') | ||
| else: | ||
| raise ExitCLI("Update cancelled") | ||
| except APIError: | ||
| msg = "ERROR: failed to pull image! Verify your internet connection and try again." | ||
| raise ExitCLI(msg) | ||
| short_id = image.short_id.split(':')[1] | ||
| print("\nSuccessfully pulled {}:{}\n".format(image_name, short_id)) | ||
| def _check_for_api(launch_browser: bool = False, timeout: int = 5): | ||
| """Check for the API to be live for up to `timeout` seconds, then optionally launch a browser window | ||
| Args: | ||
| launch_browser(bool): flag indicating if the browser should be launched on success == True | ||
| timeout(int): Number of seconds to wait for the API before returning | ||
| Returns: | ||
| bool: flag indicating if the API is ready | ||
| """ | ||
| time.sleep(1) | ||
| success = False | ||
| for _ in range(timeout): | ||
| try: | ||
| resp = requests.get("http://localhost:10000/api/ping?v={}".format(uuid.uuid4().hex)) | ||
| if resp.status_code == 200: | ||
| success = True | ||
| break | ||
| except requests.exceptions.ConnectionError: | ||
| # allow connection errors, which mean the API isn't up yet. | ||
| pass | ||
| # Sleep for 1 sec and increment counter | ||
| time.sleep(1) | ||
| if success is True and launch_browser is True: | ||
| time.sleep(1) | ||
| # If here, things look OK. Start browser | ||
| webbrowser.open_new("http://localhost:10000") | ||
| return success | ||
| def start(image_name: str, timeout: int, tag: Optional[str] = None, working_dir: str = "~/gigantum", | ||
| accept_confirmation: bool = False) -> None: | ||
| """Method to start the application | ||
| Args: | ||
| image_name: Image name, including repository and namespace (e.g. gigantum/labmanager) | ||
| timeout: Number of seconds to wait for API to come up | ||
| tag: Tag to run, defaults to latest | ||
| working_dir: Location to mount as the Gigantum working directory | ||
| accept_confirmation: Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| """ | ||
| # Make sure user is not root | ||
| if is_running_as_admin(): | ||
| raise ExitCLI("Do not run `gigantum start` as root.") | ||
| print("Verifying Docker is available...") | ||
| # Check if Docker is running | ||
| docker = DockerInterface() | ||
| if not tag: | ||
| # Trying to update to the latest version | ||
| tag = 'latest' | ||
| image_name_with_tag = "{}:{}".format(image_name, tag) | ||
| # Check if working dir exists | ||
| working_dir = os.path.expanduser(working_dir) | ||
| if not os.path.exists(working_dir): | ||
| os.makedirs(working_dir) | ||
| # Check if application has been installed | ||
| try: | ||
| docker.client.images.get(image_name_with_tag) | ||
| except ImageNotFound: | ||
| if ask_question("The Gigantum Client Docker image not found. Would you like to install it now?", | ||
| accept_confirmation): | ||
| install(image_name) | ||
| else: | ||
| raise ExitCLI("Downloading the Gigantum Client Docker image is required to start the Client. " | ||
| "Please run `gigantum install`.") | ||
| # Check to see if already running | ||
| try: | ||
| if _check_for_api(launch_browser=False, timeout=1): | ||
| print("Client already running on http://localhost:10000") | ||
| _check_for_api(launch_browser=True) | ||
| raise ExitCLI("If page does not load, restart by running `gigantum stop` and then `gigantum start` again") | ||
| # remove any lingering gigantum managed containers | ||
| _cleanup_containers() | ||
| except NotFound: | ||
| # If here, the API isn't running and an older container isn't lingering, so just move along. | ||
| pass | ||
| # Start | ||
| port_mapping = {'10000/tcp': 10000} | ||
| # Make sure the container-container share volume exists | ||
| if not docker.share_volume_exists(): | ||
| docker.create_share_volume() | ||
| volume_mapping = {docker.share_vol_name: {'bind': '/mnt/share', 'mode': 'rw'}} | ||
| print('Host directory for Gigantum files: {}'.format(working_dir)) | ||
| if platform.system() == 'Windows': | ||
| # windows docker has some eccentricities | ||
| # no user ids, we specify a WINDOWS_HOST env var, and need to rewrite the paths for | ||
| # bind-mounting inside the Client (see `dockerize_mount_path()` for details) | ||
| rewritten_path = docker.dockerize_mount_path(working_dir, image_name_with_tag) | ||
| environment_mapping = {'HOST_WORK_DIR': rewritten_path, | ||
| 'WINDOWS_HOST': 1} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'rw'} | ||
| elif platform.system() == 'Darwin': | ||
| # For macOS, use the cached mode for improved performance | ||
| environment_mapping = {'HOST_WORK_DIR': working_dir, | ||
| 'LOCAL_USER_ID': os.getuid()} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'cached'} | ||
| else: | ||
| # For anything else, just use default mode. | ||
| environment_mapping = {'HOST_WORK_DIR': working_dir, | ||
| 'LOCAL_USER_ID': os.getuid(), | ||
| 'NVIDIA_DRIVER_VERSION': get_nvidia_driver_version()} | ||
| volume_mapping[working_dir] = {'bind': '/mnt/gigantum', 'mode': 'rw'} | ||
| volume_mapping['/var/run/docker.sock'] = {'bind': '/var/run/docker.sock', 'mode': 'rw'} | ||
| container = docker.client.containers.run(image=image_name_with_tag, | ||
| detach=True, | ||
| name=image_name.replace("/", "."), | ||
| init=True, | ||
| ports=port_mapping, | ||
| volumes=volume_mapping, | ||
| environment=environment_mapping) | ||
| print("Starting, please wait...") | ||
| time.sleep(1) | ||
| # Make sure volumes have mounted properly, by checking for the log file for up to timeout seconds | ||
| success = False | ||
| for _ in range(timeout): | ||
| if os.path.exists(os.path.join(working_dir, '.labmanager', 'logs', 'labmanager.log')): | ||
| success = True | ||
| break | ||
| # Sleep for 1 sec and increment counter | ||
| time.sleep(1) | ||
| if not success: | ||
| msg = "\n\nWorking directory failed to mount! Have you granted Docker access to your user directory?" | ||
| msg = msg + " \nIn both Docker for Mac and Docker for Windows this should be shared by default, but may require" | ||
| msg = msg + " a confirmation from the user." | ||
| msg = msg + "\n\nRun `gigantum stop`, verify your OS and Docker versions are supported, the allowed Docker" | ||
| msg = msg + " volume share locations include `{}`, and try again.".format(working_dir) | ||
| msg = msg + "\n\nIf this problem persists, contact support." | ||
| # Stop and remove the container | ||
| container.stop() | ||
| container.remove() | ||
| raise ExitCLI(msg) | ||
| # Wait for API to be live before opening the user's browser | ||
| if not _check_for_api(launch_browser=True, timeout=timeout): | ||
| msg = "\n\nTimed out waiting for Gigantum Client web API! Try restarting Docker and then start again." + \ | ||
| "\nOr, increase time-out with --wait option (default is 60 seconds)." | ||
| # Stop and remove the container | ||
| container.stop() | ||
| container.remove() | ||
| raise ExitCLI(msg) | ||
| def stop(accept_confirmation=False): | ||
| """Method to stop all containers | ||
| Args: | ||
| accept_confirmation(bool): Optional Flag indicating if you should skip the confirmation and auto-accept | ||
| Returns: | ||
| None | ||
| """ | ||
| if ask_question("Stop all Gigantum managed containers? MAKE SURE YOU HAVE SAVED YOUR WORK FIRST!", | ||
| accept_confirmation): | ||
| # remove any lingering gigantum managed containers | ||
| _cleanup_containers() | ||
| else: | ||
| raise ExitCLI("Stop command cancelled") | ||
| def add_server(working_dir: str = "~/gigantum"): | ||
| """Method to add a server to this Client's configuration | ||
| Args: | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| print("\n\nEnter the server URL to add (e.g. https://gigantum.mycompany.com): ") | ||
| server_url = input() | ||
| server_config = ServerConfig(working_dir=working_dir) | ||
| server_config.add_server(server_url) | ||
| print("\nServer successfully added. The server will now be available on your Client login page.") | ||
| def list_servers(working_dir: str = "~/gigantum"): | ||
| """Method to list servers this Client is configured to use | ||
| Args: | ||
| working_dir(str): Working dir for the client | ||
| Returns: | ||
| None | ||
| """ | ||
| server_config = ServerConfig(working_dir=working_dir) | ||
| server_config.list_servers(should_print=True) | ||
| def feedback(): | ||
| """Method to throw up a browser to provide feedback | ||
| Returns: | ||
| None | ||
| """ | ||
| feedback_url = "https://feedback.gigantum.com" | ||
| print("You can provide feedback here: {}".format(feedback_url)) | ||
| webbrowser.open_new(feedback_url) |
| import pytest | ||
| from unittest import mock | ||
| import shutil | ||
| import os | ||
| from docker.errors import ImageNotFound | ||
| import getpass | ||
| from gigantumcli.utilities import ExitCLI | ||
| from gigantumcli.dockerinterface import DockerInterface | ||
| from gigantumcli.actions import start, install, update | ||
| @pytest.fixture() | ||
| def fixture_remove_client(): | ||
| """Fixture start fake project and client containers""" | ||
| docker = DockerInterface() | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| img = docker.client.images.get('gigantum/labmanager:latest') | ||
| docker.client.images.remove(img.id, force=True) | ||
| except ImageNotFound: | ||
| pass | ||
| @pytest.fixture() | ||
| def fixture_temp_work_dir(): | ||
| """Fixture create a temporary working directory""" | ||
| temp_dir = os.path.join('/tmp', 'testing-working-dir') | ||
| if not os.path.isdir(temp_dir): | ||
| os.makedirs(temp_dir) | ||
| yield temp_dir | ||
| # Remove temp dir | ||
| shutil.rmtree(temp_dir) | ||
| # Stop and remove Client container | ||
| docker = DockerInterface() | ||
| for container in docker.client.containers.list(all=True): | ||
| if container.name == "gigantum.labmanager": | ||
| container.stop() | ||
| container.remove() | ||
| def mock_api_check(launch_browser, timeout): | ||
| return launch_browser | ||
| def mock_is_running_as_admin(): | ||
| return True | ||
| class TestActions(object): | ||
| def test_is_running_as_admin(self): | ||
| from gigantumcli.utilities import is_running_as_admin | ||
| assert is_running_as_admin() is False | ||
| def test_update(self, fixture_remove_client): | ||
| docker = DockerInterface() | ||
| # image should exist not exist before install | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker.client.images.get('gigantum/labmanager:latest') | ||
| assert "Image should not exist" | ||
| except ImageNotFound: | ||
| pass | ||
| # Pull old image | ||
| old_tag = "55f05c26" | ||
| docker.client.images.pull("gigantum/labmanager", old_tag) | ||
| docker.client.api.tag('{}:{}'.format("gigantum/labmanager", old_tag), "gigantum/labmanager", 'latest') | ||
| update("gigantum/labmanager", accept_confirmation=True) | ||
| # Latest should be a new image | ||
| current_image = docker.client.images.get("{}:latest".format("gigantum/labmanager")) | ||
| short_id = current_image.short_id.split(':')[1] | ||
| print(short_id) | ||
| assert old_tag != short_id | ||
| def test_install(self, fixture_remove_client): | ||
| docker = DockerInterface() | ||
| # image should exist not exist before install | ||
| try: | ||
| # Check to see if the image has already been pulled | ||
| docker.client.images.get('gigantum/labmanager:latest') | ||
| assert "Image should not exist" | ||
| except ImageNotFound: | ||
| pass | ||
| install('gigantum/labmanager') | ||
| # image should exist after install | ||
| docker = DockerInterface() | ||
| docker.client.images.get('gigantum/labmanager') | ||
| # Calling again should exit with a message since already installed | ||
| with pytest.raises(ExitCLI): | ||
| install('gigantum/labmanager') | ||
| @pytest.mark.skipif(getpass.getuser() == 'circleci', reason="Cannot run this test in CircleCI, needs access " | ||
| "to docker machine file system") | ||
| def test_start_with_install_non_default_work_dir(self, fixture_temp_work_dir): | ||
| with mock.patch('gigantumcli.actions._check_for_api', mock_api_check): | ||
| start('gigantum/labmanager', 60, working_dir=fixture_temp_work_dir, accept_confirmation=True) | ||
| docker = DockerInterface() | ||
| is_running = False | ||
| for container in docker.client.containers.list(): | ||
| if container.name in 'gigantum.labmanager': | ||
| is_running = True | ||
| break | ||
| assert is_running is True |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
125221
21.14%38
52%1795
36.61%