Beginning a CLI client application using the client library.

This commit is contained in:
Michael Woods
2025-02-17 23:48:48 -05:00
parent cff2a22078
commit 780c1eb051
16 changed files with 123 additions and 7 deletions

View File

@@ -6,3 +6,4 @@ BTrees
transaction transaction
ZEO ZEO
podman podman
click

View File

@@ -1 +1 @@
VERSION="0.3.0-alpha" VERSION="0.4.0-alpha"

View File

@@ -0,0 +1,45 @@
import click
from packetserver.client.cli.config import get_config, default_app_dir, config_path
from packetserver.client.cli.constants import DEFAULT_DB_FILE
import ZODB
import ZODB.FileStorage
import sys
import os
import os.path
from pathlib import Path
from packetserver.client import Client
from packetserver.client.users import get_user_by_username, UserWrapper
VERSION="0.1.0-alpha"
@click.group()
@click.option('--conf', default=config_path(), help='path to configfile')
@click.option('--server', '-s', default='', help="server radio callsign to connect to (required)")
@click.option('--agwpe', '-a', default='localhost', help="AGWPE TNC server address to connect to (config file)")
@click.option('--port', '-p', default=8000, help="AGWPE TNC server port to connect to (config file)")
@click.option('--callsign', '-c', default='', help="radio callsign[+ssid] of this client station (config file)")
@click.version_option(VERSION,"--version", "-v")
@click.pass_context
def cli(ctx, conf, server, agwpe, port, callsign):
"""Command line interface for the PacketServer client and server API."""
cfg = get_config(config_file_path=conf)
storage = ZODB.FileStorage.FileStorage(os.path.join(cfg['cli']['directory'], DEFAULT_DB_FILE))
db = ZODB.DB(storage)
ctx.ensure_object(dict)
ctx.obj['CONFIG'] = cfg
ctx.obj['bbs'] = server
ctx.obj['db'] = db
@click.command()
@click.option('--username', '-u', default='', help="the username (CALLSIGN) to lookup on the bbs")
@click.pass_context
def user(ctx):
"""Query users on the BBS and customize personal settings."""
pass
cli.add_command(user)
if __name__ == '__main__':
cli()

View File

View File

@@ -0,0 +1,24 @@
import os
import os.path
from configparser import ConfigParser
from pathlib import Path
from packetserver.client.cli.constants import DEFAULT_APP_DIR, DEFAULT_CONFIG_FILE
def default_app_dir() -> str:
return os.path.join(str(Path.home()), DEFAULT_APP_DIR)
def config_path(app_path=default_app_dir()) -> str:
return os.path.join(app_path, DEFAULT_CONFIG_FILE)
def get_config(config_file_path=config_path()) -> ConfigParser:
config = ConfigParser()
if os.path.isfile(config_file_path):
config.read(config_file_path)
if not 'cli' in config.sections():
config.add_section('cli')
if 'directory' not in config['cli']:
config['cli']['directory'] = default_app_dir()
return config

View File

@@ -0,0 +1,5 @@
import os.path
DEFAULT_APP_DIR = ".packetserver"
DEFAULT_CONFIG_FILE = "cli.ini"
DEFAULT_DB_FILE = "cli-client.zopedb"

View File

View File

View File

View File

View File

View File

@@ -0,0 +1,7 @@

View File

@@ -119,14 +119,25 @@ def send_job_quick(client: Client, bbs_callsign: str, cmd: Union[str, list], db:
def get_job_id(client: Client, bbs_callsign: str, job_id: int, get_data=True) -> JobWrapper: def get_job_id(client: Client, bbs_callsign: str, job_id: int, get_data=True) -> JobWrapper:
req = Request.blank() req = Request.blank()
req.path = f"job/{job_id}" req.path = f"job/{job_id}"
req.set_var('data', get_data)
req.method = Request.Method.GET req.method = Request.Method.GET
response = client.send_receive_callsign(req, bbs_callsign) response = client.send_receive_callsign(req, bbs_callsign)
if response.status_code != 200: if response.status_code != 200:
raise RuntimeError(f"Sending job failed: {response.status_code}: {response.payload}") raise RuntimeError(f"GET job {job_id} failed: {response.status_code}: {response.payload}")
return JobWrapper(response.payload) return JobWrapper(response.payload)
def get_user_jobs(): # TODO def get_user_jobs(client: Client, bbs_callsign: str, get_data=True) -> list[JobWrapper]:
pass req = Request.blank()
req.path = f"job/user"
req.set_var('data', get_data)
req.method = Request.Method.GET
response = client.send_receive_callsign(req, bbs_callsign)
if response.status_code != 200:
raise RuntimeError(f"GET user jobs failed: {response.status_code}: {response.payload}")
jobs = []
for j in response.payload:
jobs.append(JobWrapper(j))
return jobs
class JobSession: class JobSession:
def __init__(self, client: Client, bbs_callsign: str, default_timeout: int = 300, stutter: int = 2): def __init__(self, client: Client, bbs_callsign: str, default_timeout: int = 300, stutter: int = 2):

View File

@@ -25,7 +25,7 @@ from threading import Thread
from packetserver.server.jobs import get_orchestrator_from_config, Job, JobStatus from packetserver.server.jobs import get_orchestrator_from_config, Job, JobStatus
from packetserver.runner import RunnerStatus, RunnerFile, Orchestrator, Runner from packetserver.runner import RunnerStatus, RunnerFile, Orchestrator, Runner
VERSION="0.2.0-alpha" VERSION="0.4.0-alpha"
def init_bulletins(root: PersistentMapping): def init_bulletins(root: PersistentMapping):
if 'bulletins' not in root: if 'bulletins' not in root:
@@ -103,7 +103,7 @@ class Server:
if 'runner' in conn.root.config['jobs_config']: if 'runner' in conn.root.config['jobs_config']:
val = str(conn.root.config['jobs_config']['runner']).lower().strip() val = str(conn.root.config['jobs_config']['runner']).lower().strip()
if val in ['podman']: if val in ['podman']:
logging.debug("Enabling podman orchestrator") logging.debug(f"Enabling {val} orchestrator")
self.orchestrator = get_orchestrator_from_config(conn.root.config['jobs_config']) self.orchestrator = get_orchestrator_from_config(conn.root.config['jobs_config'])
self.app = pe.app.Application() self.app = pe.app.Application()

View File

@@ -18,7 +18,7 @@ import gzip
import tarfile import tarfile
import time import time
import json import json
from packetserver.runner.podman import TarFileExtractor, PodmanOrchestrator, PodmanRunner, PodmanOptions from packetserver.common.util import TarFileExtractor
from packetserver.runner import Orchestrator, Runner, RunnerStatus, RunnerFile from packetserver.runner import Orchestrator, Runner, RunnerStatus, RunnerFile
from enum import Enum from enum import Enum
from io import BytesIO from io import BytesIO
@@ -38,6 +38,7 @@ def get_orchestrator_from_config(cfg: dict) -> Union[Orchestrator, PodmanOrchest
if 'runner' in cfg: if 'runner' in cfg:
val = cfg['runner'].lower().strip() val = cfg['runner'].lower().strip()
if val == "podman": if val == "podman":
from packetserver.runner.podman import PodmanOrchestrator, PodmanOptions
image = cfg.get('image', 'debian') image = cfg.get('image', 'debian')
opts = PodmanOptions(default_timeout=300, max_timeout=3600, image_name=image, max_active_jobs=5, opts = PodmanOptions(default_timeout=300, max_timeout=3600, image_name=image, max_active_jobs=5,
container_keepalive=300, name_prefix="packetserver_") container_keepalive=300, name_prefix="packetserver_")

22
src/packetserver/setup.py Normal file
View File

@@ -0,0 +1,22 @@
from setuptools import setup, find_packages
setup(
name='packetserver',
version='VERSION="0.4.0-alpha',
packages=find_packages(),
include_package_data=True,
install_requires=[
'click',
'pyham_pe',
'msgpack',
'pyham_ax25',
'ZODB',
'ZEO',
'podman'
],
entry_points={
'console_scripts': [
'packcli = packetserver.client.cli:cli',
],
},
)