Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CTX-6123: Added --verbose flag for all commands that could have detai… #241

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
11 changes: 7 additions & 4 deletions coretex/cli/commands/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
from ..modules.utils import onBeforeCommandExecute, checkEnvironment
from ..modules.update import activateAutoUpdate, getNodeStatus
from ...utils import docker
from ...configuration import UserConfiguration, NodeConfiguration, InvalidConfiguration, ConfigurationNotFound
from ...configuration import NodeConfiguration, InvalidConfiguration, ConfigurationNotFound


@click.command()
@click.option("--image", type = str, help = "Docker image url")
@click.option("--verbose", "verbose", is_flag = True, help = "Shows detailed output of command execution.")
@onBeforeCommandExecute(node_module.initializeNodeConfiguration)
def start(image: Optional[str]) -> None:
def start(image: Optional[str], verbose: bool = False) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to have verbose defined explicitly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

nodeConfig = NodeConfiguration.load()

if node_module.isRunning():
Expand Down Expand Up @@ -65,7 +66,8 @@ def start(image: Optional[str]) -> None:


@click.command()
def stop() -> None:
@click.option("--verbose", "verbose", is_flag = True, help = "Shows detailed output of command execution.")
def stop(verbose: bool = False) -> None:
nodeConfig = NodeConfiguration.load()

if not node_module.isRunning():
Expand All @@ -78,8 +80,9 @@ def stop() -> None:
@click.command()
@click.option("-y", "autoAccept", is_flag = True, help = "Accepts all prompts.")
@click.option("-n", "autoDecline", is_flag = True, help = "Declines all prompts.")
@click.option("--verbose", "verbose", is_flag = True, help = "Shows detailed output of command execution.")
@onBeforeCommandExecute(node_module.initializeNodeConfiguration)
def update(autoAccept: bool, autoDecline: bool) -> None:
def update(autoAccept: bool, autoDecline: bool, verbose: bool = False) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to have verbose defined explicitly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

if autoAccept and autoDecline:
ui.errorEcho("Only one of the flags (\"-y\" or \"-n\") can be used at the same time.")
return
Expand Down
9 changes: 8 additions & 1 deletion coretex/cli/modules/intercept.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

import click

from .ui import errorEcho
from ...configuration import CONFIG_DIR


class ClickExceptionInterceptor(click.Group):

Expand All @@ -37,5 +40,9 @@ def invoke(self, ctx: click.Context) -> Any:
self.handleException(ctx, exc)

def handleException(self, ctx: click.Context, exc: BaseException) -> None:
click.echo(click.style(str(exc), fg = "red"))
logPath = CONFIG_DIR / "logs"
logFiles = list(logPath.glob("*.log"))
latestLogFile = max(logFiles, key = lambda f: f.stat().st_mtime)

errorEcho(f"Exception: {str(exc)}.\nYou can see detailed logs here: {latestLogFile}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An error occured. You can see the detailed logs at {filepath}

logging.getLogger("cli").debug(exc, exc_info = exc)
35 changes: 20 additions & 15 deletions coretex/cli/modules/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

from . import ui
from .utils import isGPUAvailable
from .ui import progressEcho, successEcho
from ...node import NodeMode
from ..settings import CLISettings
from ...cryptography import rsa
from ...networking import networkManager, NetworkRequestError
from ...utils import CommandException, docker
Expand All @@ -47,26 +50,27 @@ class ImageType(Enum):

def pull(image: str) -> None:
try:
ui.progressEcho(f"Fetching image {image}...")
docker.imagePull(image)
ui.successEcho(f"Image {image} successfully fetched.")
progressEcho(f"Fetching image {image}...")
docker.imagePull(image, CLISettings.verbose)

successEcho(f"Image {image} successfully fetched.")
except BaseException as ex:
logging.getLogger("cli").debug(ex, exc_info = ex)
raise NodeException("Failed to fetch latest node version.")


def isRunning() -> bool:
return docker.containerRunning(config_defaults.DOCKER_CONTAINER_NAME)
return docker.containerRunning(config_defaults.DOCKER_CONTAINER_NAME, CLISettings.verbose)


def exists() -> bool:
return docker.containerExists(config_defaults.DOCKER_CONTAINER_NAME)
return docker.containerExists(config_defaults.DOCKER_CONTAINER_NAME, CLISettings.verbose)


def start(dockerImage: str, nodeConfig: NodeConfiguration) -> None:
try:
ui.progressEcho("Starting Coretex Node...")
docker.createNetwork(config_defaults.DOCKER_CONTAINER_NETWORK)
docker.createNetwork(config_defaults.DOCKER_CONTAINER_NETWORK, CLISettings.verbose)

environ = {
"CTX_API_URL": os.environ["CTX_API_URL"],
Expand Down Expand Up @@ -102,7 +106,8 @@ def start(dockerImage: str, nodeConfig: NodeConfiguration) -> None:
nodeConfig.sharedMemory,
nodeConfig.cpuCount,
environ,
volumes
volumes,
CLISettings.verbose
)

ui.successEcho("Successfully started Coretex Node.")
Expand All @@ -113,8 +118,8 @@ def start(dockerImage: str, nodeConfig: NodeConfiguration) -> None:

def clean() -> None:
try:
docker.removeContainer(config_defaults.DOCKER_CONTAINER_NAME)
docker.removeNetwork(config_defaults.DOCKER_CONTAINER_NETWORK)
docker.removeContainer(config_defaults.DOCKER_CONTAINER_NAME, CLISettings.verbose)
docker.removeNetwork(config_defaults.DOCKER_CONTAINER_NETWORK, CLISettings.verbose)
except BaseException as ex:
logging.getLogger("cli").debug(ex, exc_info = ex)
raise NodeException("Failed to clean inactive Coretex Node.")
Expand All @@ -133,7 +138,7 @@ def deactivateNode(id: Optional[int]) -> None:
def stop(nodeId: Optional[int] = None) -> None:
try:
ui.progressEcho("Stopping Coretex Node...")
docker.stopContainer(config_defaults.DOCKER_CONTAINER_NAME)
docker.stopContainer(config_defaults.DOCKER_CONTAINER_NAME, CLISettings.verbose)

if nodeId is not None:
deactivateNode(nodeId)
Expand Down Expand Up @@ -178,16 +183,16 @@ def getTagFromImageUrl(image: str) -> str:
return "latest"


def shouldUpdate(image: str) -> bool:
def shouldUpdate(image: str, ) -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comma

repository = getRepoFromImageUrl(image)
try:
imageJson = docker.imageInspect(image)
imageJson = docker.imageInspect(image, CLISettings.verbose)
except CommandException:
# imageInspect() will raise an error if image doesn't exist locally
return True

try:
manifestJson = docker.manifestInspect(image)
manifestJson = docker.manifestInspect(image, CLISettings.verbose)
except CommandException:
return False

Expand Down Expand Up @@ -358,7 +363,7 @@ def _configureInitScript() -> str:


def checkResourceLimitations() -> None:
_, ramLimit = docker.getResourceLimits()
_, ramLimit = docker.getResourceLimits(CLISettings.verbose)

if ramLimit < config_defaults.MINIMUM_RAM:
raise RuntimeError(
Expand All @@ -372,7 +377,7 @@ def configureNode(advanced: bool) -> NodeConfiguration:
ui.highlightEcho("[Node Configuration]")
nodeConfig = NodeConfiguration({}) # create new empty node config

cpuLimit, ramLimit = docker.getResourceLimits()
cpuLimit, ramLimit = docker.getResourceLimits(CLISettings.verbose)
swapLimit = docker.getDockerSwapLimit()

nodeConfig.name = ui.clickPrompt("Node name", type = str)
Expand Down
15 changes: 15 additions & 0 deletions coretex/cli/modules/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import requests

from . import ui
import platform

from ..settings import CLISettings
from ...configuration import DEFAULT_VENV_PATH
from ...utils.process import command

Expand Down Expand Up @@ -181,6 +184,18 @@ def onBeforeCommandExecute(
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
@wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
ctxInfoDict = click.get_current_context().to_info_dict()
try:
params = ctxInfoDict["command"]["commands"][click.get_current_context().invoked_subcommand]["params"]
for param in params:
if param["name"] == "verbose" and param["flag_value"] == True:
CLISettings.verbose = True
except:
pass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set verbose to false here


# if ctx.params.get('verbose'):
# VERBOSE = True
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this comment be removed?


if click.get_current_context().invoked_subcommand in excludeSubcommands:
return f(*args, **kwargs)

Expand Down
1 change: 1 addition & 0 deletions coretex/cli/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .settings import CLISettings
3 changes: 3 additions & 0 deletions coretex/cli/settings/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class CLISettings:

verbose = False
78 changes: 48 additions & 30 deletions coretex/utils/docker.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Copyright (C) 2023 Coretex LLC

# This file is part of Coretex.ai

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from typing import Dict, Any, List, Tuple, Optional
from pathlib import Path

Expand All @@ -12,60 +29,60 @@ class DockerConfigurationException(Exception):
pass


def isDockerAvailable() -> None:
def isDockerAvailable(verbose: Optional[bool] = False) -> None:
try:
# Run the command to check if Docker exists and is available
command(["docker", "ps"], ignoreStdout = True, ignoreStderr = True)
command(["docker", "ps"], ignoreStdout = not verbose, ignoreStderr = True)
except CommandException:
raise RuntimeError("Docker not available. Please check that it is properly installed and running on your system.")


def networkExists(name: str) -> bool:
def networkExists(name: str, verbose: Optional[bool] = False) -> bool:
# This function inspects the specified Docker network using the
# 'docker network inspect' command. If the command exits with a return code
# of 0, indicating success, the function returns True, meaning the network exists.
# If the command exits with a non-zero return code, indicating failure,
# the function returns False, meaning the network doesn't exist.
try:
command(["docker", "network", "inspect", name], ignoreStdout = True, ignoreStderr = True)
command(["docker", "network", "inspect", name], ignoreStdout = not verbose, ignoreStderr = True)
return True
except:
return False


def containerRunning(name: str) -> bool:
def containerRunning(name: str, verbose: Optional[bool] = False) -> bool:
try:
_, output, _ = command(["docker", "ps", "--format", "{{.Names}}"], ignoreStderr = True, ignoreStdout = True)
_, output, _ = command(["docker", "ps", "--format", "{{.Names}}"], ignoreStdout = not verbose, ignoreStderr = True)
return name in output.splitlines()
except:
return False


def containerExists(name: str) -> bool:
def containerExists(name: str, verbose: Optional[bool] = False) -> bool:
try:
_, output, _ = command(["docker", "ps", "-a", "--format", "{{.Names}}"], ignoreStderr = True, ignoreStdout = True)
_, output, _ = command(["docker", "ps", "-a", "--format", "{{.Names}}"], ignoreStdout = not verbose, ignoreStderr = True)
return name in output.splitlines()
except:
return False


def createNetwork(name: str) -> None:
def createNetwork(name: str, verbose: Optional[bool] = False) -> None:
if networkExists(name):
removeNetwork(name)

command(["docker", "network", "create", "--driver", "bridge", name], ignoreStdout = True)
command(["docker", "network", "create", "--driver", "bridge", name], ignoreStdout = not verbose)


def removeNetwork(name: str) -> None:
command(["docker", "network", "rm", name], ignoreStdout = True, ignoreStderr = True)
def removeNetwork(name: str, verbose: Optional[bool] = False) -> None:
command(["docker", "network", "rm", name], ignoreStdout = not verbose, ignoreStderr = True)


def removeImage(image: str) -> None:
command(["docker", "image", "rm", image], ignoreStdout = True, ignoreStderr = True)
def removeImage(image: str, verbose: Optional[bool] = False) -> None:
command(["docker", "image", "rm", image], ignoreStdout = not verbose, ignoreStderr = True)


def removeDanglingImages(repository: str, tag: str) -> None:
_, output, _ = command(["docker", "image", "ls", repository, "--format", "json"], ignoreStdout = True, ignoreStderr = True)
def removeDanglingImages(repository: str, tag: str, verbose: Optional[bool] = False) -> None:
_, output, _ = command(["docker", "image", "ls", repository, "--format", "json"], ignoreStdout = not verbose, ignoreStderr = True)
images = output.strip().split("\n")

for image in images:
Expand All @@ -77,8 +94,8 @@ def removeDanglingImages(repository: str, tag: str) -> None:
removeImage(jsonImg["ID"])


def imagePull(image: str) -> None:
command(["docker", "image", "pull", image])
def imagePull(image: str, verbose: Optional[bool] = False) -> None:
command(["docker", "image", "pull", image], ignoreStdout = not verbose)


def start(
Expand All @@ -90,7 +107,8 @@ def start(
shm: int,
cpuCount: int,
environ: Dict[str, str],
volumes: List[Tuple[str, str]]
volumes: List[Tuple[str, str]],
verbose: Optional[bool] = False
) -> None:

# https://github.com/moby/moby/issues/14215#issuecomment-115959661
Expand Down Expand Up @@ -120,28 +138,28 @@ def start(

# Docker image must always be the last parameter of docker run command
runCommand.append(image)
command(runCommand, ignoreStdout = True, ignoreStderr = True)
command(runCommand, ignoreStdout = not verbose, ignoreStderr = True)


def stopContainer(name: str) -> None:
command(["docker", "stop", name], ignoreStdout = True, ignoreStderr = True)
def stopContainer(name: str, verbose: Optional[bool] = False) -> None:
command(["docker", "stop", name], ignoreStdout = not verbose, ignoreStderr = True)


def removeContainer(name: str) -> None:
command(["docker", "rm", name], ignoreStdout = True, ignoreStderr = True)
def removeContainer(name: str, verbose: Optional[bool] = False) -> None:
command(["docker", "rm", name], ignoreStdout = not verbose, ignoreStderr = True)


def manifestInspect(image: str) -> Dict[str, Any]:
_, output, _ = command(["docker", "manifest", "inspect", image, "--verbose"], ignoreStdout = True)
def manifestInspect(image: str, verbose: Optional[bool] = False) -> Dict[str, Any]:
_, output, _ = command(["docker", "manifest", "inspect", image, "--verbose"], ignoreStdout = not verbose)
jsonOutput = json.loads(output)
if not isinstance(jsonOutput, dict):
raise TypeError(f"Invalid function result type \"{type(jsonOutput)}\". Expected: \"dict\"")

return jsonOutput


def imageInspect(image: str) -> Dict[str, Any]:
_, output, _ = command(["docker", "image", "inspect", image], ignoreStdout = True, ignoreStderr = True)
def imageInspect(image: str, verbose: Optional[bool] = False) -> Dict[str, Any]:
_, output, _ = command(["docker", "image", "inspect", image], ignoreStdout = not verbose, ignoreStderr = True)
jsonOutput = json.loads(output)
if not isinstance(jsonOutput, list):
raise TypeError(f"Invalid json.loads() result type \"{type(jsonOutput)}\". Expected: \"list\"")
Expand All @@ -152,8 +170,8 @@ def imageInspect(image: str) -> Dict[str, Any]:
return jsonOutput[0]


def getResourceLimits() -> Tuple[int, int]:
_, output, _ = command(["docker", "info", "--format", "{{json .}}"], ignoreStdout = True, ignoreStderr = True)
def getResourceLimits(verbose: Optional[bool] = False) -> Tuple[int, int]:
_, output, _ = command(["docker", "info", "--format", "{{json .}}"], ignoreStdout = not verbose, ignoreStderr = True)
jsonOutput = json.loads(output)

return jsonOutput["NCPU"], round(jsonOutput["MemTotal"] / (1024 ** 3))
Expand Down
Loading