Compare commits

...

2 commits

Author SHA1 Message Date
c00aa232fc
Moved files, refactored calls, added credentials warning
All checks were successful
/ pylint (push) Successful in 11s
/ mypy (push) Successful in 12s
/ build-artifacts (push) Successful in 7s
/ lint-and-typing (push) Successful in 14s
/ publish-artifacts (push) Successful in 9s
2024-07-06 00:28:37 +02:00
58aea47921
Refactored code to be more concise 2024-07-06 00:16:41 +02:00
16 changed files with 86 additions and 69 deletions

View file

@ -1 +0,0 @@
.temp/*

View file

@ -1,7 +0,0 @@
from ...models import Command
from .descriptors import mapping
commands: dict[str, Command] = {}
for descriptor in mapping:
commands.update({ descriptor.cmd: descriptor })

View file

@ -0,0 +1,4 @@
from ...models import Command
from .mappings import mapping
commands: dict[str, Command] = { c.cmd: c for c in mapping }

View file

@ -4,15 +4,18 @@ from getpass import getpass
from ...models import Credentials from ...models import Credentials
from ...service import storage from ...service import storage
from ...service.communicate import DockgeConnection from ...service.communicate import DockgeConnection
from ..utils import stack_formatter, status_formatter, generic_formatter, get_credential_parser from ..utils import stack_formatter, status_formatter, generic_formatter, credential_parser_factory
class ExecutionCommands(): class FunctionBindings():
""" """
Helper class that provides all the static methods in an organized way Helper class that provides all the static methods in an organized way
This is an abstraction layer of the CLI, as those functions only do little preprocessing before calling the actural DockgeConnection This is an abstraction layer of the CLI, as those functions only do little preprocessing before calling the actural DockgeConnection
""" """
@staticmethod @staticmethod
def __setup(): def __setup():
"""
Creates a connection and logs into Dockge
"""
con = DockgeConnection() con = DockgeConnection()
con.connect_and_login() con.connect_and_login()
return con return con
@ -36,8 +39,9 @@ class ExecutionCommands():
""" """
login command binding login command binding
""" """
print(f"WARNING! These credentials will be saved unencrypted in {storage._file.absolute()}")
if len(extra_args) > 0: if len(extra_args) > 0:
credentials = get_credential_parser().parse_args(extra_args, namespace=Credentials) credentials = credential_parser_factory().parse_args(extra_args, namespace=Credentials)
storage.put("username", credentials.username, encoded=True) storage.put("username", credentials.username, encoded=True)
storage.put("password", credentials.password, encoded=True) storage.put("password", credentials.password, encoded=True)
return return
@ -64,7 +68,7 @@ class ExecutionCommands():
""" """
list command binding list command binding
""" """
con = ExecutionCommands.__setup() con = FunctionBindings.__setup()
stack_formatter(con.list_stacks()) stack_formatter(con.list_stacks())
con.disconnect() con.disconnect()
@ -73,9 +77,7 @@ class ExecutionCommands():
""" """
status command binding status command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
status_formatter(con.list_stack(extra_args[0])) status_formatter(con.list_stack(extra_args[0]))
con.disconnect() con.disconnect()
@ -84,9 +86,7 @@ class ExecutionCommands():
""" """
restart command binding restart command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
generic_formatter(con.restart(extra_args[0])) generic_formatter(con.restart(extra_args[0]))
con.disconnect() con.disconnect()
@ -95,9 +95,7 @@ class ExecutionCommands():
""" """
update command binding update command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
generic_formatter(con.update(extra_args[0])) generic_formatter(con.update(extra_args[0]))
con.disconnect() con.disconnect()
@ -106,9 +104,7 @@ class ExecutionCommands():
""" """
stop command binding stop command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
generic_formatter(con.stop(extra_args[0])) generic_formatter(con.stop(extra_args[0]))
con.disconnect() con.disconnect()
@ -117,9 +113,7 @@ class ExecutionCommands():
""" """
start command binding start command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
generic_formatter(con.start(extra_args[0])) generic_formatter(con.start(extra_args[0]))
con.disconnect() con.disconnect()
@ -128,9 +122,7 @@ class ExecutionCommands():
""" """
down command binding down command binding
""" """
if extra_args is None: con = FunctionBindings.__setup()
raise ValueError
con = ExecutionCommands.__setup()
generic_formatter(con.down(extra_args[0])) generic_formatter(con.down(extra_args[0]))
con.disconnect() con.disconnect()

View file

@ -1,6 +1,6 @@
from typing import List from typing import List
from ...models import Command from ...models import Command
from .bindings import ExecutionCommands from .functions import FunctionBindings
mapping: List[Command] = [ mapping: List[Command] = [
Command( Command(
@ -8,83 +8,83 @@ mapping: List[Command] = [
description="Sets and gets the URI of the dockge instance. Remove any unnecessary subdomains/protocols from the URI", description="Sets and gets the URI of the dockge instance. Remove any unnecessary subdomains/protocols from the URI",
args=1, args=1,
optional=True, optional=True,
bind=ExecutionCommands.host func=FunctionBindings.host
), ),
Command( Command(
cmd="login", cmd="login",
description="Logs into a given dockge account, either with an interactive dialogue or by passing --user and --password", description="Logs into a given dockge account, either with an interactive dialogue or by passing --user and --password",
args=2, args=2,
optional=True, optional=True,
bind=ExecutionCommands.login func=FunctionBindings.login
), ),
Command( Command(
cmd="logout", cmd="logout",
description="Removes the credentials from the local storage.", description="Removes the credentials from the local storage.",
args=0, args=0,
optional=False, optional=False,
bind=ExecutionCommands.logout func=FunctionBindings.logout
), ),
Command( Command(
cmd="list", cmd="list",
description="Lists all available stacks with their status", description="Lists all available stacks with their status",
args=0, args=0,
optional=False, optional=False,
bind=ExecutionCommands.list func=FunctionBindings.list
), ),
Command( Command(
cmd="status", cmd="status",
description="Returns the status of one stack", description="Returns the status of one stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.status func=FunctionBindings.status
), ),
Command( Command(
cmd="restart", cmd="restart",
description="Restarts a given stack", description="Restarts a given stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.restart func=FunctionBindings.restart
), ),
Command( Command(
cmd="start", cmd="start",
description="Starts a given stack", description="Starts a given stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.start func=FunctionBindings.start
), ),
Command( Command(
cmd="stop", cmd="stop",
description="Stops a given stack", description="Stops a given stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.stop func=FunctionBindings.stop
), ),
Command( Command(
cmd="down", cmd="down",
description="Stop & Downs a given stack", description="Stop & Downs a given stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.down func=FunctionBindings.down
), ),
Command( Command(
cmd="update", cmd="update",
description="Updates a stack", description="Updates a stack",
args=1, args=1,
optional=False, optional=False,
bind=ExecutionCommands.update func=FunctionBindings.update
), ),
Command( Command(
cmd="exit", cmd="exit",
description="Exits the CLI - this will reset all settings, including credentials and host", description="Exits the CLI - this will reset all settings, including credentials and host",
args=0, args=0,
optional=False, optional=False,
bind=ExecutionCommands.exit func=FunctionBindings.exit
), ),
Command( Command(
cmd="help", cmd="help",
description="Displays helping hints for commands", description="Displays helping hints for commands",
args=1, args=1,
optional=True, optional=True,
bind=ExecutionCommands.help func=FunctionBindings.help
) )
] ]

View file

@ -3,7 +3,7 @@ import sys
from .. import __version__ from .. import __version__
from ..models import Arguments from ..models import Arguments
from .commandprovider.factory import commands from .commands import commands
def parse_arguments(): def parse_arguments():
""" """

View file

@ -1,4 +1,4 @@
from .commandprovider.factory import commands from .commands import commands
def display_help(extra_args): def display_help(extra_args):
""" """
@ -32,4 +32,4 @@ def run(command, args):
display_help(args) display_help(args)
return return
c.bind(args) c.func(args)

View file

@ -2,7 +2,7 @@ import argparse
from tabulate import tabulate from tabulate import tabulate
from ..models import StackStatus from ..models import StackStatus
def get_credential_parser(): def credential_parser_factory():
""" """
Creates a new parser for login credentials Creates a new parser for login credentials
""" """
@ -23,7 +23,7 @@ def stack_formatter(stacks):
table, headers = [], ["Stackname", "Status"] table, headers = [], ["Stackname", "Status"]
for key, val in stacks["stackList"].items(): for key, val in stacks["stackList"].items():
table.append([key, StackStatus(val['status']).name]) table.append([key, StackStatus(val['status']).name.lower()])
print(tabulate(table, headers=headers, tablefmt="github"), "\n") print(tabulate(table, headers=headers, tablefmt="github"), "\n")

View file

@ -1,3 +1,3 @@
from .codes import StackStatus from .codes import StackStatus
from .commands import Command from .command import Command
from .parser import Arguments, Credentials from .parser import Arguments, Credentials

View file

@ -2,9 +2,8 @@ from enum import Enum
class StackStatus(Enum): class StackStatus(Enum):
""" """
mapping codes for status vs text mapping for plaintext vs statuscode
""" """
# pylint: disable=invalid-name INACTIVE = 1
inactive = 1 RUNNING = 3
running = 3 EXITED = 4
exited = 4

View file

@ -6,7 +6,7 @@ class Command(BaseModel):
Basic command structure for the CLI to automatically generate valid commands Basic command structure for the CLI to automatically generate valid commands
""" """
cmd: str cmd: str
bind: Callable func: Callable
args: int args: int
optional: bool optional: bool
description: str description: str

1
dockge_cli/service/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.storage/*

View file

@ -32,7 +32,7 @@ class DockgeConnection:
def _init_events(self): def _init_events(self):
@self._sio.event @self._sio.event
def connect(): def connect():
self.connect() self.login()
print("Connected!") print("Connected!")
@self._sio.event @self._sio.event
@ -70,10 +70,11 @@ class DockgeConnection:
""" """
Connect to the websocket Connect to the websocket
""" """
# Dockge uses Socket.io for the websockets, so this URI and params are always the same
self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket']) self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket'])
self.connect() self.login()
def connect(self): def login(self):
""" """
Log into dockge using basicauth Log into dockge using basicauth
Retries 5 times when timeouts occur Retries 5 times when timeouts occur
@ -84,6 +85,11 @@ class DockgeConnection:
data = None data = None
retry, count = True, 0 retry, count = True, 0
if not storage.exists("username"):
raise ValueError("Missing username")
if not storage.exists("password"):
raise ValueError("Missing password")
while retry and count < 5: while retry and count < 5:
try: try:
data = self._sio.call( data = self._sio.call(
@ -91,7 +97,7 @@ class DockgeConnection:
{ {
"username": storage.get("username", encoded=True), "username": storage.get("username", encoded=True),
"password": storage.get("password", encoded=True), "password": storage.get("password", encoded=True),
"token":"" "token": ""
}, },
timeout=5 timeout=5
) )

View file

@ -3,28 +3,44 @@ import pathlib
import base64 import base64
import yaml import yaml
_storagepath = pathlib.Path(__file__).parents[1] / ".temp" _storagepath = pathlib.Path(__file__).parent / ".storage"
_storagepath.mkdir(exist_ok=True, parents=True)
_file = _storagepath / "storage.yaml" _file = _storagepath / "storage.yaml"
_storagepath.mkdir(exist_ok=True, parents=True) def create_file_when_missing():
def fileexists():
""" """
Checks if storage file does exist, creates it when necessary Checks if storage file does exist, creates it when necessary
""" """
if _file.exists():
return
with open(_file, 'a', encoding="utf-8"):
os.utime(_file, None)
def exists(key: str) -> bool:
"""
Checks if a given key exists in the storage file
"""
if not _file.exists(): if not _file.exists():
with open(_file, 'a', encoding="utf-8"): return False
os.utime(_file, None)
with open(_file, "r", encoding="utf-8") as file:
content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader)
return key in content
def put(key: str, value: str, encoded=False): def put(key: str, value: str, encoded=False):
""" """
Puts a given value with a given key into the storage file Puts a given value with a given key into the storage file
Encodes the data as base64 when encoded is set to true Encodes the data as base64 when encoded is set to true
""" """
fileexists() if not _file.exists():
with open(_file, "r+", encoding="utf-8") as file: create_file_when_missing()
with open(_file, "r", encoding="utf-8") as file:
content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {} content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
content.update({ key: str(base64.b64encode(value.encode()), "utf-8") if encoded else value }) content.update({ key: str(base64.b64encode(value.encode()), "utf-8") if encoded else value })
with open(_file, "w+", encoding="utf-8") as file: with open(_file, "w+", encoding="utf-8") as file:
yaml.dump(content, file, Dumper=yaml.SafeDumper) yaml.dump(content, file, Dumper=yaml.SafeDumper)
@ -32,10 +48,13 @@ def remove(key: str):
""" """
Removed a given key from the storage file Removed a given key from the storage file
""" """
fileexists() if not _file.exists():
create_file_when_missing()
with open(_file, "r", encoding="utf-8") as file: with open(_file, "r", encoding="utf-8") as file:
content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {} content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
content.pop(key, None) content.pop(key, None)
with open(_file, "w+", encoding="utf-8") as file: with open(_file, "w+", encoding="utf-8") as file:
yaml.dump(content, file, Dumper=yaml.SafeDumper) yaml.dump(content, file, Dumper=yaml.SafeDumper)
@ -47,9 +66,11 @@ def get(key: str, encoded=False):
value: str | None = None value: str | None = None
if not _file.exists(): if not _file.exists():
return None return None
with open(_file, "r", encoding="utf-8") as file: with open(_file, "r", encoding="utf-8") as file:
content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader)
value = content.get(key, None) value = content.get(key, None)
if value is None: if value is None:
return None return None
return base64.b64decode(value.encode()).decode() if encoded else value return base64.b64decode(value.encode()).decode() if encoded else value
@ -58,4 +79,6 @@ def clear():
""" """
Deletes the storage file Deletes the storage file
""" """
if not _file.exists():
return
_file.unlink() _file.unlink()

View file

@ -1,6 +1,6 @@
[project] [project]
name = "dockge_cli" name = "dockge_cli"
version = "0.1.0-c.2" version = "0.1.0-c.3"
dependencies = [ dependencies = [
"pyyaml~=6.0.1", "pyyaml~=6.0.1",
"pydantic~=2.8.0", "pydantic~=2.8.0",