Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

gigantum

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gigantum - npm Package Compare versions

Comparing version
1.2.1
to
1.3.0
gigantumcli/commands/__init__.py
+66
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
+1
-1
[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 @@

@@ -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

@@ -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

@@ -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