Changes.
This commit is contained in:
@@ -5,6 +5,7 @@ from packetserver.client import Client
|
|||||||
from packetserver.common.constants import yes_values
|
from packetserver.common.constants import yes_values
|
||||||
from packetserver.common import Request, Response
|
from packetserver.common import Request, Response
|
||||||
from packetserver.client.cli.util import format_list_dicts, exit_client
|
from packetserver.client.cli.util import format_list_dicts, exit_client
|
||||||
|
from packetserver.client.cli.job import job
|
||||||
import ZODB
|
import ZODB
|
||||||
import ZODB.FileStorage
|
import ZODB.FileStorage
|
||||||
import ax25
|
import ax25
|
||||||
@@ -25,21 +26,28 @@ VERSION="0.1.0-alpha"
|
|||||||
@click.option('--agwpe', '-a', default='', help="AGWPE TNC server address to connect to (config file)")
|
@click.option('--agwpe', '-a', default='', help="AGWPE TNC server address to connect to (config file)")
|
||||||
@click.option('--port', '-p', default=0, help="AGWPE TNC server port to connect to (config file)")
|
@click.option('--port', '-p', default=0, 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.option('--callsign', '-c', default='', help="radio callsign[+ssid] of this client station (config file)")
|
||||||
|
@click.option('--keep-log', '-k', is_flag=True, default=False, help="Save local copy of request log after session ends?")
|
||||||
@click.version_option(VERSION,"--version", "-v")
|
@click.version_option(VERSION,"--version", "-v")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, conf, server, agwpe, port, callsign):
|
def cli(ctx, conf, server, agwpe, port, callsign, keep_log):
|
||||||
"""Command line interface for the PacketServer client and server API."""
|
"""Command line interface for the PacketServer client and server API."""
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
cfg = get_config(config_file_path=conf)
|
cfg = get_config(config_file_path=conf)
|
||||||
|
|
||||||
|
ctx.obj['keep_log'] = False
|
||||||
|
if keep_log:
|
||||||
|
ctx.obj['keep_log'] = True
|
||||||
|
else:
|
||||||
|
if cfg['cli'].get('keep_log', fallback='n') in yes_values:
|
||||||
|
ctx.obj['keep_log'] = True
|
||||||
|
|
||||||
if callsign.strip() != '':
|
if callsign.strip() != '':
|
||||||
ctx.obj['callsign'] = callsign.strip().upper()
|
ctx.obj['callsign'] = callsign.strip().upper()
|
||||||
else:
|
else:
|
||||||
if 'callsign' in cfg['cli']:
|
if 'callsign' in cfg['cli']:
|
||||||
ctx.obj['callsign'] = cfg['cli']['callsign']
|
ctx.obj['callsign'] = cfg['cli']['callsign']
|
||||||
else:
|
else:
|
||||||
click.echo("You must provide client station's callsign.", err=True)
|
ctx.obj['callsign'] = click.prompt('Please enter your station callsign (with ssid if needed)', type=str)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not ax25.Address.valid_call(ctx.obj['callsign']):
|
if not ax25.Address.valid_call(ctx.obj['callsign']):
|
||||||
click.echo(f"Provided client callsign '{ctx.obj['callsign']}' is invalid.", err=True)
|
click.echo(f"Provided client callsign '{ctx.obj['callsign']}' is invalid.", err=True)
|
||||||
@@ -51,8 +59,7 @@ def cli(ctx, conf, server, agwpe, port, callsign):
|
|||||||
if 'server' in cfg['cli']:
|
if 'server' in cfg['cli']:
|
||||||
ctx.obj['server'] = cfg['cli']['server']
|
ctx.obj['server'] = cfg['cli']['server']
|
||||||
else:
|
else:
|
||||||
click.echo("Remote BBS server callsign must be specified.", err=True)
|
ctx.obj['server'] = click.prompt('Please enter the bbs station callsign (with ssid if needed)', type=str)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not ax25.Address.valid_call(ctx.obj['server']):
|
if not ax25.Address.valid_call(ctx.obj['server']):
|
||||||
click.echo(f"Provided remote server callsign '{ctx.obj['server']}' is invalid.", err=True)
|
click.echo(f"Provided remote server callsign '{ctx.obj['server']}' is invalid.", err=True)
|
||||||
@@ -76,7 +83,7 @@ def cli(ctx, conf, server, agwpe, port, callsign):
|
|||||||
|
|
||||||
storage = ZODB.FileStorage.FileStorage(os.path.join(cfg['cli']['directory'], DEFAULT_DB_FILE))
|
storage = ZODB.FileStorage.FileStorage(os.path.join(cfg['cli']['directory'], DEFAULT_DB_FILE))
|
||||||
db = ZODB.DB(storage)
|
db = ZODB.DB(storage)
|
||||||
client = Client(ctx.obj['agwpe_server'], ctx.obj['port'], ctx.obj['callsign'], keep_log=True)
|
client = Client(ctx.obj['agwpe_server'], ctx.obj['port'], ctx.obj['callsign'], keep_log=ctx.obj['keep_log'])
|
||||||
try:
|
try:
|
||||||
client.start()
|
client.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -99,13 +106,13 @@ def query_server(ctx):
|
|||||||
resp = client.send_receive_callsign(req, ctx.obj['bbs'])
|
resp = client.send_receive_callsign(req, ctx.obj['bbs'])
|
||||||
if resp is None:
|
if resp is None:
|
||||||
click.echo(f"No response from {ctx.obj['bbs']}")
|
click.echo(f"No response from {ctx.obj['bbs']}")
|
||||||
exit_client(client, 1)
|
exit_client(ctx.obj, 1)
|
||||||
else:
|
else:
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
exit_client(client, 1, message=f"Error contacting server: {resp.payload}")
|
exit_client(ctx.obj, 1, message=f"Error contacting server: {resp.payload}")
|
||||||
else:
|
else:
|
||||||
click.echo(json.dumps(resp.payload, indent=2))
|
click.echo(json.dumps(resp.payload, indent=2))
|
||||||
exit_client(client, 0)
|
exit_client(ctx.obj, 0)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@@ -119,10 +126,10 @@ def user(ctx, list_users, output_format, username):
|
|||||||
client = ctx.obj['client']
|
client = ctx.obj['client']
|
||||||
# validate args
|
# validate args
|
||||||
if list_users and (username.strip() != ""):
|
if list_users and (username.strip() != ""):
|
||||||
exit_client(client,1, "Can't specify a username while listing all users.")
|
exit_client(ctx.obj,1, "Can't specify a username while listing all users.")
|
||||||
|
|
||||||
if not list_users and (username.strip() == ""):
|
if not list_users and (username.strip() == ""):
|
||||||
exit_client(client,1, message="Must provide either a username or --list-users flag.")
|
exit_client(ctx.obj,1, message="Must provide either a username or --list-users flag.")
|
||||||
|
|
||||||
output_objects = []
|
output_objects = []
|
||||||
try:
|
try:
|
||||||
@@ -131,15 +138,16 @@ def user(ctx, list_users, output_format, username):
|
|||||||
else:
|
else:
|
||||||
output_objects.append(users.get_user_by_username(client, ctx.obj['bbs'], username))
|
output_objects.append(users.get_user_by_username(client, ctx.obj['bbs'], username))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exit_client(client,1, str(e))
|
exit_client(ctx.obj,1, str(e))
|
||||||
finally:
|
finally:
|
||||||
client.stop()
|
client.stop()
|
||||||
|
|
||||||
click.echo(format_list_dicts([x.pretty_dict() for x in output_objects], output_format=output_format.lower()))
|
click.echo(format_list_dicts([x.pretty_dict() for x in output_objects], output_format=output_format.lower()))
|
||||||
exit_client(client, 0)
|
exit_client(ctx.obj, 0)
|
||||||
|
|
||||||
cli.add_command(user)
|
cli.add_command(user)
|
||||||
cli.add_command(query_server)
|
cli.add_command(query_server)
|
||||||
|
cli.add_command(job, name='job')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
cli()
|
cli()
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
"""CLI client for dealing with jobs."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
import click
|
||||||
|
from persistent.mapping import default
|
||||||
|
from packetserver.client import Client
|
||||||
|
from packetserver.client.jobs import JobSession
|
||||||
|
import datetime
|
||||||
|
from packetserver.client.cli.util import exit_client
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.pass_context
|
||||||
|
def job(ctx):
|
||||||
|
"""Runs commands on the BBS server if jobs are enabled on it."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def start():
|
||||||
|
"""Start a job on the BBS server."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def get():
|
||||||
|
"""Retrieve a job"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--transcript", "-T", default="", help="File to write command transcript to if desired.")
|
||||||
|
@click.pass_context
|
||||||
|
def quick_session(ctx, transcript):
|
||||||
|
"""Start a session to submit multiple commands and receive responses immediately"""
|
||||||
|
session_transcript = []
|
||||||
|
client = ctx.obj['client']
|
||||||
|
bbs = ctx.obj['bbs']
|
||||||
|
js = JobSession(client, bbs, stutter=2)
|
||||||
|
db_enabled = True
|
||||||
|
while True:
|
||||||
|
cmd = click.prompt("CMD", prompt_suffix=" >")
|
||||||
|
cmd = cmd.strip()
|
||||||
|
session_transcript = session_transcript.append((datetime.datetime.now(),"c",cmd))
|
||||||
|
next_db = False
|
||||||
|
if db_enabled:
|
||||||
|
next_db = True
|
||||||
|
db_enabled = False
|
||||||
|
if cmd == "":
|
||||||
|
continue
|
||||||
|
if cmd == "/exit":
|
||||||
|
break
|
||||||
|
elif cmd == "/db":
|
||||||
|
click.echo("DB requested for next command.")
|
||||||
|
db_enabled = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
job_result = js.send_quick(['bash', '-c', cmd], db=next_db)
|
||||||
|
output = job_result.output_str + "\n" + "Errors: " + job_result.errors_str
|
||||||
|
session_transcript = session_transcript.append((datetime.datetime.now(), "r", output))
|
||||||
|
click.echo(output)
|
||||||
|
except Exception as e:
|
||||||
|
session_transcript = session_transcript.append((datetime.datetime.now(), "e", e))
|
||||||
|
click.echo(f"ERROR! {str(e)}", err=True)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if transcript.strip() != "":
|
||||||
|
with open(transcript.strip(), 'w') as tran_file:
|
||||||
|
for l in session_transcript:
|
||||||
|
tran_file.write(f"{l[1]}:{l[0].isoformat()}: {l[2]}{os.linesep}")
|
||||||
|
finally:
|
||||||
|
exit_client(ctx.obj, 0)
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import json
|
|||||||
import click
|
import click
|
||||||
from packetserver.client import Client
|
from packetserver.client import Client
|
||||||
import sys
|
import sys
|
||||||
|
import ZODB
|
||||||
|
from persistent.mapping import PersistentMapping
|
||||||
|
import datetime
|
||||||
|
|
||||||
def format_list_dicts(dicts: list[dict], output_format: str = "table") -> str:
|
def format_list_dicts(dicts: list[dict], output_format: str = "table") -> str:
|
||||||
if output_format == "table":
|
if output_format == "table":
|
||||||
@@ -22,7 +25,24 @@ def format_list_dicts(dicts: list[dict], output_format: str = "table") -> str:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Unsupported format type.")
|
raise ValueError("Unsupported format type.")
|
||||||
|
|
||||||
def exit_client(client: Client, return_code: int, message=""):
|
def write_request_log(db: ZODB.DB, client: Client):
|
||||||
|
with db.transaction() as db_trans:
|
||||||
|
if not 'request_log' in db_trans.root():
|
||||||
|
db_trans['request_log'] = PersistentMapping()
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
db_trans['request_log'][now.isoformat()] = client.request_log
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def exit_client(context: dict, return_code: int, message=""):
|
||||||
|
client = context['client']
|
||||||
|
db = context['db']
|
||||||
|
client.stop()
|
||||||
|
|
||||||
|
if context['keep_log']:
|
||||||
|
write_request_log(db, client)
|
||||||
|
|
||||||
|
db.close()
|
||||||
client.stop()
|
client.stop()
|
||||||
if return_code == 0:
|
if return_code == 0:
|
||||||
is_err = False
|
is_err = False
|
||||||
|
|||||||
Reference in New Issue
Block a user