wip: shell integration, fixing cli

This commit is contained in:
mrjk 2023-10-08 14:25:22 -04:00
parent dcb27eecc1
commit b8df31f036
15 changed files with 753 additions and 502 deletions

View File

@ -12,7 +12,7 @@ from . import exceptions as error
from .catalog import Catalog
from .framework import DictItem, scoped_ident
from .idents import Idents
from .lib.utils import import_module, open_yaml
from .lib.utils import import_module, open_yaml, get_root_pkg_dir
from .meta import NAME, VERSION
from .providers import Providers
@ -181,10 +181,26 @@ class App:
ret = {svc.name: svc.list_cmds() for svc in self.catalog.services.values()}
return ret
def shell_enable(self):
def shell_enable(self, **kwargs):
"Enable shell"
return self.catalog.shell_enable()
return self.catalog.shell_enable(**kwargs)
def shell_disable(self):
def shell_disable(self, **kwargs):
"Disable shell"
return self.catalog.shell_disable()
return self.catalog.shell_disable(**kwargs)
def shell_install(self, shell="bash"):
"Install iam in shell"
assets_dir = os.path.join(get_root_pkg_dir("iam"), "assets")
if shell == "bash":
config_file = ".bashrc"
template_file = os.path.join(assets_dir, "init_bash.sh")
else:
raise Exception(f"Unsupported shell: {shell}")
return f"source {template_file}"

180
iam/assets/init_bash.sh Normal file
View File

@ -0,0 +1,180 @@
# Workllow
#
# API
# i <ident>
# i status <ident>
# i switch <ident>
# i disable <ident>
# i help
# i FORWARD
# ${debian_chroot:+($debian_chroot)}\u@\h:\w\$
#IAM_BIN=/home/jez/volumes/data/prj/jez/lab/iam-python/.direnv/python-3.11.3/bin/iam
IAM_BIN=$(command -v iam)
IAM_IDENT=
_usage ()
{
echo "iam shell wrapper"
cat << EOF
Usage:
i [status] Show shell status
i switch IDENT Enable shell identity
i disable Disable shell identity
i kill Disable and kill shell identity
i help Show this help
Informations:
IAM_BIN=${IAM_BIN}
SHELL_IDENT=${SHELL_IDENT:-_}
EOF
}
i ()
{
# set -x
# Get status
target_ident=_
current_ident=${SHELL_IDENT:-_}
available_idents=$($IAM_BIN shell idents -O words)
# Internal vars
remaining_args=
idents_pattern=:${available_idents// /:}:
index=0
action=
while [[ -n "${1-}" ]] ; do
local arg=$1
case "$arg" in
status|st)
action=status
shift 1
if [[ -n "${1}" ]]; then
target_ident=$1
shift 1
fi
;;
switch|enable|en)
action=switch
shift 1
if [[ -n "${1}" ]]; then
target_ident=$1
shift 1
fi
;;
disable|dis|q)
action=disable
shift 1
;;
kill|k)
action=kill
shift 1
;;
stop|K)
action=kill_all
shift 1
;;
help|--help|-h)
action=help
shift 1
;;
*)
if [[ "$idents_pattern" == *":$arg:"* ]]; then
action=switch
target_ident=$arg
shift 1
else
remaining_args="$remaining_args $arg"
shift 1
fi
;;
esac
((index++))
done
if [[ -z "$action" ]]; then
action="idents"
fi
# echo "Current Ident: $current_ident"
# echo "Target Ident: $target_ident"
# echo "Action: $action"
case "$action" in
idents)
echo "Iam path: ${IAM_BIN}"
echo "Current ident: ${SHELL_IDENT:-<None>}"
echo "Available identities: $($IAM_BIN shell idents -O words)"
;;
switch)
# if [[ "$current_ident" != '_' ]]; then
# eval $IAM_BIN shell disable $current_ident
# fi
# $IAM_BIN shell switch $target_ident
if [[ "$target_ident" != '_' ]]; then
>&2 echo "DEBUG: eval: $IAM_BIN shell switch $target_ident"
eval "$($IAM_BIN shell switch $target_ident)"
fi
;;
disable)
if [[ "$current_ident" != '_' ]]; then
>&2 echo "DEBUG: eval: $IAM_BIN shell switch"
eval "$($IAM_BIN shell disable)"
fi
;;
kill)
if [[ "$current_ident" != '_' ]]; then
>&2 echo "DEBUG: eval: $IAM_BIN shell switch --kill"
eval "$($IAM_BIN shell disable --kill)"
fi
;;
kill_all)
eval "$($IAM_BIN shell kill -a)"
;;
status)
echo "IAM_BIN=${IAM_BIN}"
echo "SHELL_IDENT=${SHELL_IDENT:-_}"
#echo $IAM_BIN shell status $current_ident
;;
help)
_usage
;;
esac
set +x
return
# Source or switch
if $source_output; then
out=($IAM_BIN $@)
echo "SOURCE: $out"
else
echo $IAM_BIN $@
fi
set +x
}
#i2 $@

View File

@ -9,6 +9,7 @@ from .idents import Ident
from .lib.utils import format_render, jinja_render, uniq
logger = logging.getLogger(__name__)
cli_logger = logging.getLogger("iam.cli")
IamException = error.IamException
@ -47,7 +48,18 @@ class ServiceCommand(DictItem):
default_attrs = {
"desc": "Service without description",
"cmd": "",
"cmd": "", # Direct commands to launch
"shell": "", # Will be run in a shell (or specific shell below)
"shell_sh": "",
"shell_bash": "",
"shell_zsh": "",
"shell_fish": "",
"shell_bash": "",
"source": False, # True for enable commands !
}
# Overrides
@ -55,12 +67,15 @@ class ServiceCommand(DictItem):
def payload_transform(self, name, kwargs=None):
"Transform short form into long form"
self.service = self.parent
payload = self._payload
if not isinstance(payload, dict):
payload = {"cmd": payload}
self._payload = payload
def cmd_name(self):
return self.name.replace("_", " ")
class Service(DictItem):
"Hold provider services"
@ -83,9 +98,10 @@ class Service(DictItem):
self.catalog = self.parent.catalog
def init(self):
ret = {}
for cmd_name, cmd in self.commands.items():
ret[cmd_name] = ServiceCommand(cmd_name, cmd)
ret[cmd_name] = ServiceCommand(cmd_name, cmd, parent=self)
self.commands = ret
# def require_resolved_deps(func):
@ -200,7 +216,7 @@ class Service(DictItem):
out = cmd.format(**env_var)
except KeyError as err:
msg = f"Missing variable: {err} for service: {res.key}"
# print (msg)
logger.warning (msg)
out = msg
# raise IamException(msg)
@ -213,6 +229,13 @@ class Service(DictItem):
# def _list_cmds(self):
def get_cmds(self):
"Get all services commands objects"
return [cmd for name, cmd in self.commands.items() if not name.startswith("shell")]
def list_cmds(self):
"List all services commands"
@ -226,11 +249,15 @@ class Service(DictItem):
for cmd_name, conf in self.commands.items():
target = ret2["cmds"]
if cmd_name.startswith(shell_prefix):
target = ret2["shell"]
cmd_name = cmd_name[len(shell_prefix) :]
# cmd_name = cmd_name[len(shell_prefix):]
name = f"{cmd_name}_{prefix}"
else:
name = cmd_name.replace("_", " ")
name = f"{prefix} {cmd_name}"
target[name] = conf.desc
return ret2
@ -253,9 +280,76 @@ class Services(DictCtrl):
items_class = Service
RESERVED_CMD_PREFIX=["kind", "res", "svc", "shell", "run"]
def prepare(self):
self.catalog = self.parent
def get_svc_command(self, cmd):
"Return the command to be run"
# Prepare context
cmd_parts = cmd
if isinstance(cmd, str):
cmd_parts = cmd.split(" ")
cmd_req = " ".join(cmd_parts)
# Retrieve command list
cmds = self.list_cmds()
cmd_names = [cmd.name for cmd in cmds ]
# Find best matching command
cmd_split_idx = None
cmd = None
curr=None
for index, part in enumerate(cmd_parts):
curr = f"{curr} {part}" if curr is not None else part
if curr in cmd_names:
cmd_idx = cmd_names.index(curr)
cmd = cmds[cmd_idx]
cmd_split_idx = index + 1
break
# Validate result
if not cmd:
_choices = ','.join(cmd_names)
_msg = f"No such services command named: {cmd_req}, please choose one of: {_choices}"
raise error.UnknownServiceCommand(_msg)
args = cmd_parts[cmd_split_idx:]
return cmd, args
def list_cmds(self):
"List all services commands"
ret = []
for name, service in self.items():
cmds = service.get_cmds()
ret.extend(cmds)
# Check for invalid configs
conf = []
for cmd in ret:
prefix = cmd.name.split(" ")[0]
if prefix in self.RESERVED_CMD_PREFIX:
_msg = f'Forbidden prefix! {cmd}'
raise Exception (_msg)
conf.append(cmd.name )
dump = uniq(conf)
if conf != dump:
_msg = f'Duplicates values! {conf}'
raise Exception (_msg)
return ret
def get_linked_resources(self):
"""
Like get method, but only grab enabled services
@ -798,18 +892,36 @@ class Catalog:
# return self.services.get_loading_order(reverse=reverse)
# Shell helpers
# ================
def shell_enable(self):
def shell_enable(self, run_start=True):
"Enable shell"
return self._shell_action()
def shell_disable(self):
actions = ["shell_enable"]
if run_start:
actions.insert(0, "shell_start")
# logger.warning("Shell Start")
# logger.warning("Shell Enable")
return self._shell_action(actions)
def shell_disable(self, run_stop=False):
"Disable shell"
return self._shell_action(reverse=True)
def _shell_action(self, reverse=False):
actions = ["shell_disable"]
if run_stop:
actions.insert(0, "shell_stop")
# cli_logger.info("Shell Stop")
# cli_logger.info("Shell Disable")
return self._shell_action(actions)
def _shell_action(self, action_names, reverse=False):
"Run command order"
ident = self.ident
@ -819,12 +931,10 @@ class Catalog:
order, services = self.services.get_loading_order(reverse=reverse)
# Prepare context
action_name = "shell_enable"
log_action_name = "Enable"
if reverse:
action_name = "shell_disable"
log_action_name = "Disable"
logger.info(f"{log_action_name} identity: {ident.name}")
cli_logger.info(f"Identity {ident.name} action: {','.join(action_names)}")
# Execute on each plugins commands
output_code = []
@ -833,38 +943,102 @@ class Catalog:
res = services[srv_name].res
# Load service command
command = service.commands.get(action_name, None)
if not command:
continue
for action_name in action_names:
command = service.commands.get(action_name, None)
if not command:
continue
# Build loop with at least ONE item
cmd = command.cmd
if not cmd:
continue
# Build loop with at least ONE item
cmd = command.cmd
if not cmd:
continue
# Create context var dict
ctx_vars = dict()
ctx_vars.update(service.input)
ctx_vars.update(vars)
ctx_vars.update(res.resolve_inputs(vars=ctx_vars))
# Create context var dict
ctx_vars = dict()
ctx_vars.update(service.input)
ctx_vars.update(vars)
ctx_vars.update(res.resolve_inputs(vars=ctx_vars))
loops = res.loop_extend() or [res]
res_vars = [x.resolve_inputs(vars=ctx_vars) for x in loops]
loops = res.loop_extend() or [res]
res_vars = [x.resolve_inputs(vars=ctx_vars) for x in loops]
ctx_vars.update(
{
"item": res_vars[0],
"loop": res_vars,
}
)
ctx_vars.update(
{
"item": res_vars[0],
"loop": res_vars,
}
)
logger.debug(f"{log_action_name} service: {res.name}")
logger.debug(f"{log_action_name} service: {res.name}")
# pprint (ctx_vars)
cmd = jinja_render(cmd, ctx_vars)
output_code.append(f"# Loading of {res.name} ({service.name})")
output_code.append(f"# =====================")
output_code.append(cmd)
output_code.append("\n")
# pprint (ctx_vars)
cmd = jinja_render(cmd, ctx_vars)
output_code.append(f"# Loading: {action_name} {res.name} ({service.name})")
output_code.append(f"# =====================")
output_code.append(cmd)
output_code.append("\n")
return "\n".join(output_code)
def run_svc_cmd(self, cmd):
"Run a command against the services"
cmd, args = self.services.get_svc_command(cmd)
# pprint (cmd.cmd)
pprint (args)
service = cmd.service
pprint (service)
pprint (service.__dict__)
# pprint (dir(service))
res = service.get_linked_resource()
# pprint (res)
vars = self.context.get_vars()
ctx_vars = dict()
ctx_vars.update(vars)
ctx_vars.update(service.inputs)
# ctx_vars.update(res.resolve_inputs(vars=ctx_vars))
# loops = res.loop_extend() or [res]
# res_vars = [x.resolve_inputs(vars=ctx_vars) for x in loops]
# ctx_vars.update(
# {
# "item": res_vars[0],
# "loop": res_vars,
# }
# )
pprint (ctx_vars)
if cmd.cmd:
mode = "cmd"
payload = cmd.cmd
real_cmd = jinja_render(payload, ctx_vars)
elif cmd.shell:
mode = "shell"
payload = cmd.shell
real_cmd = jinja_render(payload, ctx_vars)
else:
raise Exception("MIssing cmd or shell in config !")
print ("RUN CMD", mode, "\n\n\n====\n", real_cmd)

View File

@ -45,7 +45,7 @@ else:
import rich_click as click
from rich_click import RichGroup
from rich import box, print
from rich import box #, print
from rich.console import Console
# Overrides defaults
from rich.pretty import pprint
@ -67,6 +67,8 @@ else:
logger = logging.getLogger()
cli_logger = logging.getLogger("iam.cli")
# Global vars
# ======================
@ -97,7 +99,7 @@ DEFAULT_LOG_LEVEL = 30 # warning
DEBUG = os.environ.get("IAM_DEBUG", "False")
DEFAULT_IDENT = os.environ.get("IAM_IDENT", os.environ.get("SHELL_IDENT", ""))
DEFAULT_FORMAT = os.environ.get("IAM_FORMAT", "table")
DEFAULT_HELP_OPTIONS=["-h", "--help"]
# list_view.formats_enum
@ -110,7 +112,7 @@ _cmd2_options = [click.option("--cmd2-opt")]
CONTEXT_SETTINGS = dict(
show_default=True,
help_option_names=["-h", "--help"],
help_option_names=DEFAULT_HELP_OPTIONS,
auto_envvar_prefix=APP_NAME.upper(),
)
@ -219,7 +221,8 @@ def get_var(name, default=None):
# @click.group(cls=NestedHelpGroup, context_settings=CONTEXT_SETTINGS)
@click.group(cls=NestedHelpGroup)
@click.group(cls=NestedHelpGroup
)
@global_options
@format_options
@click.version_option()
@ -259,7 +262,7 @@ def cli_app(
# Define loggers
loggers = {
"iam": {"level": log_level},
"iam.cli": {"level": "INFO"},
"iam.cli": {"level": "DEBUG", "handlers": ["info"]},
}
# Instanciate logger
@ -275,8 +278,6 @@ def cli_app(
"table_settings": table_box_params,
}
pprint(cli_config)
render_item = lambda *args, conf={}, **kwargs: item_view.render(
*args, conf={**render_config, **prune(conf)}, **kwargs
)
@ -630,7 +631,7 @@ def svc_show(ctx, name, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=N
def svc_commands(ctx, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=None):
"Show service"
columns = ["name", "type", "desc"]
columns = ["service", "name", "type", "desc"]
data = []
for svc_name, svc in ctx.obj.app.list_services().items():
@ -641,10 +642,7 @@ def svc_commands(ctx, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=Non
for cmd_name, conf in items.items():
# target[cmd_name] = conf
data.append([cmd_name, source, conf])
# pprint(ret)
# pprint(ret, expand_all=True)
data.append([svc_name, cmd_name, source, conf])
# Render data
ctx.obj.render_list(
@ -659,7 +657,7 @@ def svc_commands(ctx, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=Non
)
@cli__svc.command("run")
@cli__svc.command("run_v1")
@click.argument("name")
@click.argument("command")
@format_options
@ -687,23 +685,12 @@ def cli__shell():
@format_options
@click.pass_context
def shell_idents(
ctx, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=None, reverse=False
ctx, fmt_name=None, fmt_fields=None, fmt_sort=None, verbose=None,
):
"Shell identities"
columns = ["name"]
data = [[x] for x in ctx.obj.app.get_idents()]
# print(" ".join(ret))
# return
# columns = ["order", "name"]
# data = []
# services, _ = ctx.obj.app.catalog.services.get_loading_order(reverse=reverse)
# for index, name in enumerate(services):
# # table.add_row(str(index), name)
# data.append([str(index), name])
# Render data
ctx.obj.render_list(
data,
@ -748,44 +735,191 @@ def shell_order(
)
@cli__shell.command("enable")
@click.argument("ident")
# @format_options
@click.option(
"skip_start", "--no-start", "-b", is_flag=True, show_default=False, default=False, help="Disable background process start"
)
@click.pass_context
def shell_enable(ctx, ident, verbose=None):
def shell_enable(ctx, skip_start=True, verbose=None):
"Enable identity in shell"
app = ctx.obj.app
print(app.shell_enable(run_start=not skip_start))
@cli__shell.command("disable")
@click.option(
"run_stop", "--kill", "-k", is_flag=True, show_default=False, default=False, help="Kill background process on leave"
)
@click.pass_context
def shell_disable(ctx, run_stop=False, verbose=None):
"Disable identity in shell"
ret = ctx.obj.app.shell_disable(run_stop=run_stop)
print(ret)
@cli__shell.command("kill")
@click.option(
"all_idents", "--all", "-a", is_flag=True, show_default=False, default=False, help="Kill on all users"
)
@click.pass_context
def shell_kill(ctx, all_idents=False, verbose=None):
"Disable identity in shell"
ret = []
app = ctx.obj.app
if not all_idents:
ret.append(app.shell_enable(run_start=False))
ret.append(app.shell_disable(run_stop=True))
else:
for ident in app.idents.names():
cli_logger.warning(f"Kill ident {ident}")
app.init_ident(ident)
ret.append(app.shell_enable(run_start=False))
ret.append(app.shell_disable(run_stop=True))
print('\n'.join(ret))
@cli__shell.command("switch")
@click.argument("ident", required=False)
# @format_options
@click.option(
"skip_start", "--no-start", "-b", is_flag=True, show_default=False, default=False, help="Disable background process start"
)
@click.option(
"run_stop", "--kill", "-k", is_flag=True, show_default=False, default=False, help="Kill background process on leave"
)
@click.pass_context
def shell_switch(ctx, ident="", run_stop=False, skip_start=True, verbose=None):
"Enable identity in shell"
app = ctx.obj.app
# Disable existing
# logger.info(f"Disabling ident: {app.ident_name}")
ret1 = app.shell_disable()
print("-- %< --" * 8)
print(ret1)
print("-- %< --" * 8)
# Enable new
# logger.info(f"Enabling ident: {ident}")
app.init_ident(ident)
ret2 = app.shell_enable()
# Output
print("-- %< --" * 8)
print(ret2)
print("-- %< --" * 8)
src_ident = app.catalog.ident.name
dst_ident = ident
@cli__shell.command("disable")
# @format_options
if src_ident == dst_ident:
print (">&2 echo 'No need to change'")
else:
ret = []
# Disable existing
# logger.info(f"Disabling ident: {app.ident_name}")
ret.append(app.shell_disable(run_stop=run_stop))
# Enable new
# logger.info(f"Enabling ident: {ident}")
app.init_ident(dst_ident)
ret.append(app.shell_enable(run_start=not skip_start))
# Output
print('\n'.join(ret))
class TmpGroup(click.Group):
def format_help(self, ctx, formatter):
val = "List of available commands!"
formatter.write(val)
@cli_app.command("run",
# cls=TmpGroup,
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
help_option_names=[]
)
)
@click.argument('cmd_params', nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def shell_disable(ctx, verbose=None):
"Disable identity in shell"
def cli_app_run(ctx, cmd_params):
ret = ctx.obj.app.shell_disable()
print("Will run cmd:", cmd_params)
print("-- %< --" * 8)
# Check first help parameter only
show_help = False
show_list = False
for idx, param in enumerate(cmd_params):
if idx == 0:
if param in DEFAULT_HELP_OPTIONS:
show_help = True
if param in ["list", "ls"]:
show_list = True
if show_help:
ctx = click.get_current_context()
click.echo(ctx.get_help())
return
# Get instanciated app
app = ctx.obj.app
if show_list:
ret = app.catalog.services.list_cmds()
data = [[x.name, x.desc] for x in ret]
# Render data
ctx.obj.render_list(
data,
["name", "desc"],
conf={
"table_title": f"Services commands",
# "fmt_name": fmt_name,
# "fmt_sort": fmt_sort,
# "fmt_fields": fmt_fields,
},
)
return
# Run the output
ret = app.catalog.run_svc_cmd(cmd=cmd_params)
pprint (ret, expand_all=True)
# @cli_app.command("run2",
# context_settings=dict(
# ignore_unknown_options=True,
# allow_extra_args=True,
# )
# )
# @click.pass_context
# def cli_app_run(ctx):
# print("Hello world")
# pprint (ctx.args)
# return
@cli__shell.command("install")
@click.pass_context
def shell_install(ctx, verbose=None):
"Install iam in your shell"
ret = ctx.obj.app.shell_install()
# print("-- %< --" * 8)
print(ret)
print("-- %< --" * 8)
# print("-- %< --" * 8)
# Exception handler
@ -796,8 +930,8 @@ def clean_terminate(err):
# Choose dead end way
if APP_EXCEPTION is not None and isinstance(err, APP_EXCEPTION):
err_name = err.__class__.__name__
logger.error(err)
logger.critical("MyApp exited with error: %s", err_name)
# logger.error(err)
logger.critical("%s: %s" % (err_name, err))
sys.exit(1)

View File

@ -1,192 +0,0 @@
import enum
import logging
from enum import Enum
from pprint import pformat, pprint
from types import SimpleNamespace
from iam.framework import (empty, get_app_logger, iterate_any, open_yaml,
prune, to_csv, to_json, to_yaml)
from rich import box
from rich.console import Console
from rich.pretty import pprint
from rich.prompt import Prompt
from rich.table import Table
from .lib.cli_views import ViewItem, ViewKind, ViewList
# Iam Implementations
# ===============================
MISSING_PKG = []
try:
import hcl2
except ModuleNotFoundError:
MISSING_PKG.append("hcl2")
try:
import yaml
except ModuleNotFoundError:
MISSING_PKG.append("yaml")
# Main views
# ======================
# def view_list(data, columns, title=None, fmt=None, **kwargs):
# "Show list view"
# fmt = fmt or OutputFormat.DEFAULT
# ret = None
# _kwargs = {
# key[len(fmt.value) + 1 :]: val
# for key, val in kwargs.items()
# if key.startswith(fmt.value)
# }
# # print ("YOOO", fmt, OutputFormat.YAML)
# if fmt == OutputFormat.YAML:
# ret = to_yaml(restructure_list_to_dict(data, columns))
# elif fmt == OutputFormat.JSON:
# ret = to_json(restructure_list_to_dict(data, columns), nice=True)
# elif fmt == OutputFormat.PYTHON:
# ret = pformat(restructure_list_to_dict(data, columns), indent=2)
# elif fmt == OutputFormat.CSV:
# ret = to_csv(restructure_list_to_csv(data, columns))
# elif fmt == OutputFormat.ENV:
# ret = to_vars(restructure_list_to_env(data, columns), export=True)
# elif fmt == OutputFormat.VARS:
# ret = to_vars(restructure_list_to_env(data, columns))
# elif fmt == OutputFormat.RICH_TABLE:
# ret = to_rich_table(data, columns=columns, title=title, **_kwargs)
# else:
# raise Exception(f"Unmanagable format: {fmt}")
# console.print(ret)
# def view_show(data, columns=None, title=None):
# "Show single view"
# ret = None
# _kwargs = {
# key[len(fmt.value) + 1 :]: val
# for key, val in kwargs.items()
# if key.startswith(fmt.value)
# }
# # print ("YOOO", fmt, OutputFormat.YAML)
# if fmt == OutputFormat.YAML:
# ret = to_yaml(data)
# elif fmt == OutputFormat.JSON:
# ret = to_json(data, indent=2)
# elif fmt == OutputFormat.PYTHON:
# ret = pformat(data, indent=2)
# elif fmt == OutputFormat.CSV:
# ret = to_csv(data)
# elif fmt == OutputFormat.RICH_TABLE:
# data = list(zip(*data))
# data.insert(0, ["Field", "Value"])
# ret = to_rich_table(data, **_kwargs)
# else:
# raise Exception(f"Unmanagable format: {fmt}")
# return ret
# # DEPRECATED
# # ======================
# def output_list(payload, fmt=None, **kwargs):
# "Render output format"
# assert False, "DEPRECATED"
# fmt = fmt or OutputFormat.YAML
# # Requested format
# # payload = [
# # ["Header1", "Header2"], # First line is always headers
# # [["val1", "val2"]], # First row
# # [["val1", "val2"]], # Second row, etc ...
# # ]
# ret = None
# # _kwargs = {key.lstrip(f"{fmt.value}_"): val for key, val in kwargs.items() if key.startswith(fmt.value)}
# _kwargs = {
# key[len(fmt.value) + 1 :]: val
# for key, val in kwargs.items()
# if key.startswith(fmt.value)
# }
# # print ("YOOO", fmt, OutputFormat.YAML)
# if fmt == OutputFormat.YAML:
# ret = to_yaml(payload)
# elif fmt == OutputFormat.JSON:
# ret = to_json(payload, indent=2)
# elif fmt == OutputFormat.PYTHON:
# ret = pformat(payload, indent=2)
# elif fmt == OutputFormat.CSV:
# ret = to_csv(payload)
# elif fmt == OutputFormat.RICH_TABLE:
# # pprint (kwargs)
# # pprint (_kwargs)
# return to_rich_table(payload, **_kwargs)
# else:
# raise Exception(f"Unmanagable format list: {fmt}")
# assert isinstance(ret, str)
# return ret
# def output_show(payload, fmt=None, **kwargs):
# "Show one item"
# assert False, "DEPRECATED"
# # Requested format
# # payload = [
# # ["Header1", "Header2"], # First line is always headers
# # [["val1", "val2"]], # First row ONLY
# # [["val1", "val2"]], # Second row, etc ...
# # ]
# ret = None
# _kwargs = {
# key[len(fmt.value) + 1 :]: val
# for key, val in kwargs.items()
# if key.startswith(fmt.value)
# }
# # print ("YOOO", fmt, OutputFormat.YAML)
# if fmt == OutputFormat.YAML:
# ret = to_yaml(payload)
# elif fmt == OutputFormat.JSON:
# ret = to_json(payload, indent=2)
# elif fmt == OutputFormat.PYTHON:
# ret = pformat(payload, indent=2)
# elif fmt == OutputFormat.CSV:
# ret = to_csv(payload)
# elif fmt == OutputFormat.RICH_TABLE:
# # Return data
# payload = list(zip(*payload))
# payload.insert(0, ["Field", "Value"])
# ret = to_rich_table(payload, **_kwargs)
# else:
# raise Exception(f"Unmanagable format show: {fmt}")
# return ret

View File

@ -16,3 +16,7 @@ class UnknownResourceKind(IamException):
class MissingConfigFiles(IamException):
"Raised when iam can't find any valid configuration file"
class UnknownServiceCommand(IamException):
"Raised when a command is not matched against services"

View File

@ -42,12 +42,12 @@ def get_app_logger(loggers=None, level="WARNING", colors=False, format="default"
formatters = {
"default": {
"()": fclass,
"format": "[%(levelname)s] %(message)s",
"format": "[%(levelname)8s] %(message)s",
# 'datefmt': '%Y-%m-%d %H:%M:%S',
},
"extended": {
"()": fclass,
"format": "[%(levelname)s] %(name)s: %(message)s",
"format": "[%(levelname)8s] %(name)s: %(message)s",
"datefmt": "%H:%M:%S",
},
"audit": {
@ -57,7 +57,7 @@ def get_app_logger(loggers=None, level="WARNING", colors=False, format="default"
},
"debug": {
"()": fclass,
"format": "%(msecs)03d [%(levelname)s] %(name)s: %(message)s [%(filename)s/%(funcName)s:%(lineno)d]",
"format": "%(msecs)03d [%(levelname)8s] %(name)s: %(message)s [%(filename)s/%(funcName)s:%(lineno)d]",
"datefmt": "%H:%M:%S",
},
}
@ -81,15 +81,24 @@ def get_app_logger(loggers=None, level="WARNING", colors=False, format="default"
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr", # Default is stderr
},
"info": {
"level": "INFO",
"formatter": format,
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr", # Default is stderr
},
},
# Where logs come from
"loggers": {
# Used to catch ALL logs
"": { # root logger
"handlers": ["default"],
"level": "WARNING",
"propagate": False,
},
# # Used to catch all logs of myapp and sublibs
# 'myapp': {
# 'handlers': ['default'],
@ -297,6 +306,9 @@ class DictCtrl(_DictBase):
def keys(self):
return self._items.keys()
def names(self):
return list(self._items.keys())
def has(self, name):
"Return true or false if an item exists"
return True if name in self._items else False

View File

@ -16,6 +16,12 @@ except ModuleNotFoundError:
MISSING_PKGS.append("Table")
# TODO:
# toml/hcl support
# duckdb support => https://stackoverflow.com/a/70538527
# MISSING_PKGS = []
# try:
# from rich.console import Console
@ -311,33 +317,32 @@ class _RootView:
}
)
fmt_ = SimpleNamespace(**fmt_config)
print("FORMAT CONFIG")
pprint(conf)
pprint(fmt_)
# print("FORMAT CONFIG")
# pprint(conf)
# pprint(fmt_)
assert fmt_.name, f"Format name can't be None: {fmt_.name}"
# check module presence
pprint(self.formats_modules)
mod_spec = self.formats_modules.get(fmt_.name, None)
print("MOD_NAME", fmt_.name, mod_spec)
if mod_spec:
# Extract specs
mod_package = None
mod_name = mod_spec
if isinstance(mod_spec, (list, set, tuple)):
mod_name = mod_spec[0]
mod_package = mod_spec[1]
# if mod_spec:
# # Extract specs
# mod_package = None
# mod_name = mod_spec
# if isinstance(mod_spec, (list, set, tuple)):
# mod_name = mod_spec[0]
# mod_package = mod_spec[1]
# # Check module presence
# # print("CHECKING MODE NAME", mod_name)
# ret = is_module_present(mod_name)
# if not ret:
# _msg = f"Missing python module '{mod_name}' to support '{fmt_.name}' output."
# if mod_package:
# _msg = f"{_msg} Please first install package: {mod_package}"
# logger.warning(_msg)
# # raise Exception(_msg)
# Check module presence
print("CHECKING MODE NAME", mod_name)
ret = is_module_present(mod_name)
if not ret:
_msg = f"Missing python module '{mod_name}' to support '{fmt_.name}' output."
if mod_package:
_msg = f"{_msg} Please first install package: {mod_package}"
logger.warning(_msg)
# raise Exception(_msg)
# Fetch renderer method
# ---------------------------

View File

@ -1,3 +1,4 @@
import sys
import importlib
import json
import logging
@ -235,9 +236,23 @@ def iterate_any(payload):
# raise Exception(f"Could not iterate over: {payload}")
def get_root_pkg_dir(name):
"""Return the dir where the actual paasify source code lives"""
assert not "." in name, "Only resrved for root pacakges"
# Look into each paths
for cand in sys.path:
target = os.path.join(cand, name)
if os.path.isdir(target):
return target
return None
def get_pkg_dir(name):
"""Return the dir where the actual paasify source code lives"""
"""Return the dir where the actual paasify source code lives, it loads the module !!!"""
# pylint: disable=import-outside-toplevel
mod = import_module_pkg(name)

View File

@ -49,11 +49,11 @@ plugin_base = {
"desc": "Disable shell ident",
"cmd": "unset SHELL_IDENT",
},
"new": {
"id new": {
"desc": "Create shell identy",
"cmd": "add_ident {{ param }}",
},
"delete": {
"id delete": {
"desc": "Delete shell identy",
"cmd": "rm_ident {{ param }}",
},

View File

@ -134,12 +134,12 @@ providers:
unset MINIO_ACCESS_KEY MINIO_SECRET_KEY
new_alias:
minio new alias:
desc: Create alias
cmd: |
mc alias set {{ident}} {{api_url}} {{minio_user}} {{minio_password}}
rm_alias:
minio delete alias:
desc: Remove alias
cmd: |
mc alias rm {{ident}}

View File

@ -14,9 +14,10 @@ providers:
commands:
new:
ssh new:
desc: Create new SSH key
cmd: |
shell: |
SSH_KEY_ALG={{ssh_key_alg}}
SSH_KEY_VERSION="$(date +'%Y%m%d')"
@ -30,7 +31,9 @@ providers:
-N "{{ssh_key_secret}}" \
-C "$SSH_KEY_COMMENT"
delete:
ssh delete:
desc: Delete existing SSH key
cmd: |
find $HOME/.ssh/{ident}/ -name "{user}_*"
@ -119,28 +122,92 @@ providers:
commands:
shell_start:
desc: Start ssh-agent
cmd: |
socket=$HOME/.local/state/ssh-agent/{{user}}
start=true
running=2
if [[ ! -e "$socket.env" ]]; then
running=2
elif [[ -e "$socket" ]]; then
running=$(SSH_AUTH_SOCK=$socket ssh-add -l &>/dev/null; echo $rc)
fi
if [[ "$running" -eq 2 ]]; then
# Start agent
>&2 echo "Start ssh-agent for {{ident}}"
mkdir -p "${socket%/*}"
ssh-agent -a $socket -t {{ssh_agent_tmout}} > $socket.env
fi
unset socket start running
# if [[ -d "/run/user/$(id -u)" ]]; then
# socket=/run/user/$(id -u)/ssh-agent/{{user}}
# else
# fi
shell_enable:
desc: Enable ssh-agent
cmd: |
export SSH_AUTH_SOCK={{ssh_agent_socket_dir}}/{{user}}
ssh-agent -a $SSH_AUTH_SOCK -t {{ssh_agent_tmout}}
# SSH_AGENT_PID= ???
socket=$HOME/.local/state/ssh-agent/{{user}}
if [[ -e "$socket.env" ]]; then
# >&2 echo "Enable ssh-agent for {{ident}}"
source "$socket.env" >/dev/null
fi
unset socket
shell_disable:
desc: Disable ssh-agent
cmd: ssh-agent -k && unset SSH_AUTH_SOCK
cmd: |
unset SSH_AUTH_SOCK SSH_AGENT_PID
shell_stop:
desc: Kill ssh-agent
cmd: |
socket=$HOME/.local/state/ssh-agent/{{user}}
if [[ -e "$socket.env" ]]; then
# >&2 echo "Enable ssh-agent for {{ident}}"
source "$socket.env" >/dev/null
# fi
# if [[ -n "$SSH_AGENT_PID" ]]; then
>&2 echo "Kill ssh-agent for {{ident}}"
eval "(ssh-agent -k)" >/dev/null
[[ -e "$socket.env" ]] && rm "$socket.env" || true
fi
unset socket
# env_file="$HOME/.local/state/ssh-agent/{{user}}.env"
# if [[ -f "$env_file" ]]; then
# source "$env_file"
# fi
# if [[ -f "$env_file" ]]; then
# rm "$env_file"
# fi
local.ssh_agent_keys:
desc: Local ssh-agent keys
commands:
enable:
ssh add:
desc: Unload keys into ssh-agent
cmd: ssh-agent -d {ssh_key_file}
disable:
ssh rm:
desc: Load keys into ssh-agent
cmd: |
ssh-add {% for item in loop %} {{item.ssh_key_file}} {% endfor %}
@ -263,15 +330,16 @@ providers:
commands:
shell_enable:
desc: Enable git identity
desc: Enable PS1
cmd: |
OLD_PS1=$PS1
export PS1="\[\033[0;34m\]({{ident}})\[\033[00m\] ${PS1}"
export OLD_PS1=$PS1
export PS1="\033[0;34m\]({{ident}})\033[00m\] ${PS1}"
shell_disable:
desc: Disable git identity
desc: Disable PS1
cmd: |
export PS1=$OLD_PS1
export PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
# export PS1="$OLD_PS1"

169
poetry.lock generated
View File

@ -1,34 +1,5 @@
# This file is automatically @generated by Poetry 1.6.0 and should not be changed by hand.
[[package]]
name = "attrs"
version = "23.1.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.7"
files = [
{file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
{file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
]
[package.extras]
cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
dev = ["attrs[docs,tests]", "pre-commit"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
[[package]]
name = "autopage"
version = "0.5.1"
description = "A library to provide automatic paging for console output"
optional = false
python-versions = ">=3.6"
files = [
{file = "autopage-0.5.1-py3-none-any.whl", hash = "sha256:0fbf5efbe78d466a26753e1dee3272423a3adc989d6a778c700e89a3f8ff0d88"},
{file = "autopage-0.5.1.tar.gz", hash = "sha256:01be3ee61bb714e9090fcc5c10f4cf546c396331c620c6ae50a2321b28ed3199"},
]
[[package]]
name = "black"
version = "23.9.1"
@ -87,47 +58,6 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "cliff"
version = "4.3.0"
description = "Command Line Interface Formulation Framework"
optional = false
python-versions = ">=3.8"
files = [
{file = "cliff-4.3.0-py3-none-any.whl", hash = "sha256:db3dc8774f47db9aa86796921ff158d0f023630261c2746c4fff12584b75f5b2"},
{file = "cliff-4.3.0.tar.gz", hash = "sha256:fc5b6ebc8fb815332770b2485ee36c09753937c37cce4f3227cdd4e10b33eacc"},
]
[package.dependencies]
autopage = ">=0.4.0"
cmd2 = ">=1.0.0"
importlib-metadata = ">=4.4"
PrettyTable = ">=0.7.2"
PyYAML = ">=3.12"
stevedore = ">=2.0.1"
[[package]]
name = "cmd2"
version = "2.4.3"
description = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "cmd2-2.4.3-py3-none-any.whl", hash = "sha256:f1988ff2fff0ed812a2d25218a081b0fa0108d45b48ba2a9272bb98091b654e6"},
{file = "cmd2-2.4.3.tar.gz", hash = "sha256:71873c11f72bd19e2b1db578214716f0d4f7c8fa250093c601265a9a717dee52"},
]
[package.dependencies]
attrs = ">=16.3.0"
pyperclip = ">=1.6"
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""}
wcwidth = ">=0.1.7"
[package.extras]
dev = ["codecov", "doc8", "flake8", "invoke", "mypy", "nox", "pytest (>=4.6)", "pytest-cov", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme", "twine (>=1.11)"]
test = ["codecov", "coverage", "gnureadline", "pytest (>=4.6)", "pytest-cov", "pytest-mock"]
validate = ["flake8", "mypy", "types-pkg-resources"]
[[package]]
name = "colorama"
version = "0.4.6"
@ -170,25 +100,6 @@ files = [
[package.dependencies]
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
[[package]]
name = "importlib-metadata"
version = "6.8.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
{file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
[[package]]
name = "isort"
version = "5.12.0"
@ -350,17 +261,6 @@ files = [
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "pbr"
version = "5.11.1"
description = "Python Build Reasonableness"
optional = false
python-versions = ">=2.6"
files = [
{file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"},
{file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"},
]
[[package]]
name = "platformdirs"
version = "3.11.0"
@ -376,23 +276,6 @@ files = [
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "prettytable"
version = "3.9.0"
description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
optional = false
python-versions = ">=3.8"
files = [
{file = "prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8"},
{file = "prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34"},
]
[package.dependencies]
wcwidth = "*"
[package.extras]
tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
[[package]]
name = "pyaml"
version = "23.9.6"
@ -424,16 +307,6 @@ files = [
[package.extras]
plugins = ["importlib-metadata"]
[[package]]
name = "pyperclip"
version = "1.8.2"
description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)"
optional = false
python-versions = "*"
files = [
{file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"},
]
[[package]]
name = "pyreadline3"
version = "3.4.1"
@ -551,20 +424,6 @@ rich = ">=10.7.0"
[package.extras]
dev = ["pre-commit"]
[[package]]
name = "stevedore"
version = "5.1.0"
description = "Manage dynamic plugins for Python applications"
optional = false
python-versions = ">=3.8"
files = [
{file = "stevedore-5.1.0-py3-none-any.whl", hash = "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d"},
{file = "stevedore-5.1.0.tar.gz", hash = "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"},
]
[package.dependencies]
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
name = "typer"
version = "0.9.0"
@ -597,33 +456,7 @@ files = [
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
]
[[package]]
name = "wcwidth"
version = "0.2.8"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"},
{file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"},
]
[[package]]
name = "zipp"
version = "3.17.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "6c7761768920ef58522d19560d56ce366cca74dc275b4e019acf10838923d63d"
content-hash = "1747400a30523d50e0ccaf55386f60f05b4d6e5e222aeb8a737d4e78b702a210"

View File

@ -20,11 +20,13 @@ pyxdg = "^0.28"
coloredlogs = "^15.0.1"
rich = "^13.6.0"
rich-click = "^1.6.1"
colorama = "^0.4.6"
[tool.poetry.scripts]
iam2 = "iam.cli:run"
iam = "iam.cli_click:run"
iam2 = "iam.cli_typer:run"
[tool.poetry.group.dev.dependencies]
black = "^23.9.1"