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

Add GUI call functionality to scauto CLI #147

Merged
merged 5 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/flake8.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ jobs:
with:
python-version: 3

- name: Install krb5-config deps
run: sudo apt-get update -y
&& sudo apt-get upgrade -y
&& sudo apt-get install -y
libkrb5-dev
gcc

- name: Install dependencies
run: python3 -m pip install tox

Expand Down
217 changes: 182 additions & 35 deletions SCAutolib/cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,53 @@
from pathlib import Path
from sys import exit

from collections import OrderedDict

from SCAutolib import logger, exceptions, schema_user
from SCAutolib.controller import Controller
from SCAutolib.enums import ReturnCode


@click.group()
def check_conf_path(conf):
return click.Path(exists=True, resolve_path=True)(conf)


# In Help output, force the subcommand list to match the order
# listed in this file. Solution was found here:
# https://github.com/pallets/click/issues/513#issuecomment-301046782
class NaturalOrderGroup(click.Group):
"""
Command group trying to list subcommands in the order they were added.
Example use::

@click.group(cls=NaturalOrderGroup)

If passing dict of commands from other sources, ensure they are of type
OrderedDict and properly ordered, otherwise order of them will be random
and newly added will come to the end.
"""
def __init__(self, name=None, commands=None, **attrs):
if commands is None:
commands = OrderedDict()
elif not isinstance(commands, OrderedDict):
commands = OrderedDict(commands)
click.Group.__init__(self, name=name,
commands=commands,
**attrs)

def list_commands(self, ctx):
"""
List command names as they are in commands dict.

If the dict is OrderedDict, it will preserve the order commands
were added.
"""
return self.commands.keys()


@click.group(cls=NaturalOrderGroup)
@click.option("--conf", "-c",
default="./conf.json",
type=click.Path(exists=True, resolve_path=True),
show_default=True,
help="Path to JSON configuration file.")
@click.option('--force', "-f", is_flag=True, default=False, show_default=True,
Expand All @@ -29,35 +67,13 @@ def cli(ctx, force, verbose, conf):
logger.setLevel(verbose)
ctx.ensure_object(dict) # Create a dict to store the context
ctx.obj["FORCE"] = force # Store the force option in the context
ctx.obj["CONTROLLER"] = Controller(conf)


@click.command()
@click.option("--ca-type", "-t",
required=False,
default='all',
type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
show_default=True,
help="Type of the CA to be configured. If not set, all CA's "
"from the config file would be configured")
@click.pass_context
def setup_ca(ctx, ca_type):
"""
Configure the CA's in the config file. If more than one CA is
specified, specified CA type would be configured.
"""
cnt = ctx.obj["CONTROLLER"]
if ca_type == 'all':
cnt.setup_local_ca(force=ctx.obj["FORCE"])
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
elif ca_type == 'local':
cnt.setup_local_ca(force=ctx.obj["FORCE"])
elif ca_type == 'ipa':
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
exit(ReturnCode.SUCCESS.value)
parsed_conf = None
if ctx.invoked_subcommand != "gui":
parsed_conf = check_conf_path(conf)
ctx.obj["CONTROLLER"] = Controller(parsed_conf)


@click.command()
@cli.command()
@click.option("--gdm", "-g",
required=False,
default=False,
Expand Down Expand Up @@ -85,7 +101,32 @@ def prepare(ctx, gdm, install_missing, graphical):
exit(ReturnCode.SUCCESS.value)


@click.command()
@cli.command()
@click.option("--ca-type", "-t",
required=False,
default='all',
type=click.Choice(['all', 'local', 'ipa'], case_sensitive=False),
show_default=True,
help="Type of the CA to be configured. If not set, all CA's "
"from the config file would be configured")
@click.pass_context
def setup_ca(ctx, ca_type):
"""
Configure the CA's in the config file. If more than one CA is
specified, specified CA type would be configured.
"""
cnt = ctx.obj["CONTROLLER"]
if ca_type == 'all':
cnt.setup_local_ca(force=ctx.obj["FORCE"])
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
elif ca_type == 'local':
cnt.setup_local_ca(force=ctx.obj["FORCE"])
elif ca_type == 'ipa':
cnt.setup_ipa_client(force=ctx.obj["FORCE"])
exit(ReturnCode.SUCCESS.value)


@cli.command()
@click.argument("name",
required=True,
default=None)
Expand Down Expand Up @@ -154,7 +195,7 @@ def setup_user(ctx, name, card_dir, card_type, passwd, pin, user_type):
exit(ReturnCode.SUCCESS.value)


@click.command()
@cli.command()
@click.pass_context
def cleanup(ctx):
"""
Expand All @@ -165,7 +206,113 @@ def cleanup(ctx):
exit(ReturnCode.SUCCESS.value)


cli.add_command(setup_ca)
cli.add_command(prepare)
cli.add_command(setup_user)
cli.add_command(cleanup)
@cli.group(cls=NaturalOrderGroup, chain=True)
@click.option("--install-missing", "-i",
required=False,
default=False,
is_flag=True,
help="Install missing packages")
@click.pass_context
def gui(ctx, install_missing):
""" Run GUI Test commands """
pass


@gui.command()
def init():
""" Initialize GUI for testing """
return "init"


@gui.command()
@click.option("--no",
required=False,
default=False,
is_flag=True,
help="Reverse the action")
@click.argument("name")
def assert_text(name, no):
""" Check if a word is found on the screen """
if no:
return f"assert_no_text:{name}"
return f"assert_text:{name}"


@gui.command()
@click.argument("name")
def click_on(name):
""" Click on object containing word """
return f"click_on:{name}"


@gui.command()
@click.option("--no",
required=False,
default=False,
is_flag=True,
help="Reverse the action")
def check_home_screen(no):
""" Check if screen appears to be the home screen """
if no:
return "check_no_home_screen"
return "check_home_screen"


@gui.command()
@click.argument("keys")
def kb_send(keys):
""" Send key(s) to keyboard """
return f"kb_send:{keys}"


@gui.command()
@click.argument("keys")
def kb_write(keys):
""" Send string to keyboard """
return f"kb_write:{keys}"


@gui.command()
def done():
""" cleanup after testing """
return "done"


@gui.result_callback()
@click.pass_context
def run_all(ctx, actions, install_missing):
""" Run all cli actions in order """
ctx.obj["CONTROLLER"].setup_graphical(install_missing, True)

from SCAutolib.models.gui import GUI
gui = GUI(from_cli=True)
for action in actions:
if "init" in action:
gui.__enter__()
if "assert_text" in action:
assert_text = action.split(":", 1)[1]
gui.assert_text(assert_text)
if "assert_no_text" in action:
assert_text = action.split(":", 1)[1]
gui.assert_no_text(assert_text)
if "click_on" in action:
click_on = action.split(":", 1)[1]
gui.click_on(click_on)
if "check_home_screen" in action:
GeorgePantelakis marked this conversation as resolved.
Show resolved Hide resolved
gui.check_home_screen()
if "check_no_home_screen" in action:
gui.check_home_screen(False)
if "kb_send" in action:
params = action.split(":", 1)[1].split()[0]
gui.kb_send(params)
if "kb_write" in action:
GeorgePantelakis marked this conversation as resolved.
Show resolved Hide resolved
params = action.split(":", 1)[1].split()[0]
gui.kb_write(params)
gui.kb_send('enter')
if "done" in action:
gui.__exit__(None, None, None)
ctx.obj["CONTROLLER"].cleanup()


if __name__ == "__main__":
cli()
77 changes: 46 additions & 31 deletions SCAutolib/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Controller:
def conf_path(self):
return self._lib_conf_path

def __init__(self, config: Union[Path, str], params: {} = None):
def __init__(self, config: Union[Path, str] = None, params: {} = None):
"""
Constructor will parse and check input configuration file. If some
required fields in the configuration are missing, CLI parameters
Expand All @@ -55,15 +55,18 @@ def __init__(self, config: Union[Path, str], params: {} = None):
# Check params

# Parse config file
self._lib_conf_path = config.absolute() if isinstance(config, Path) \
else Path(config).absolute()

with self._lib_conf_path.open("r") as f:
tmp_conf = json.load(f)
if tmp_conf is None:
raise exceptions.SCAutolibException(
"Data are not loaded correctly.")
self.lib_conf = self._validate_configuration(tmp_conf, params)
self.lib_conf = None
if config:
self._lib_conf_path = config.absolute() if isinstance(config, Path) \
else Path(config).absolute()

with self._lib_conf_path.open("r") as f:
tmp_conf = json.load(f)
if tmp_conf is None:
raise exceptions.SCAutolibException(
"Data are not loaded correctly.")
self.lib_conf = self._validate_configuration(tmp_conf, params)

self.users = []
for d in (LIB_DIR, LIB_BACKUP, LIB_DUMP, LIB_DUMP_USERS, LIB_DUMP_CAS,
LIB_DUMP_CARDS, LIB_DUMP_CONFS):
Expand Down Expand Up @@ -147,12 +150,6 @@ def setup_system(self, install_missing: bool, gdm: bool, graphical: bool):

packages = ["opensc", "httpd", "sssd", "sssd-tools", "gnutls-utils",
"openssl", "nss-tools"]
if gdm:
packages.append("gdm")

if graphical:
# ffmpeg-free is in EPEL repo
packages += ["tesseract", "ffmpeg-free"]

# Prepare for virtual cards
if any(c["card_type"] == CardType.virtual
Expand Down Expand Up @@ -181,21 +178,7 @@ def setup_system(self, install_missing: bool, gdm: bool, graphical: bool):
raise exceptions.SCAutolibException(msg)

if graphical:
if not isDistro('fedora'):
run(['dnf', 'groupinstall', 'Server with GUI', '-y',
'--allowerasing'])
run(['pip', 'install', 'python-uinput'])
else:
# Fedora doesn't have server with GUI group so installed gdm
# manually and also python3-uinput should be installed from RPM
run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
# disable subscription message
run(['systemctl', '--global', 'mask',
'org.gnome.SettingsDaemon.Subscription.target'])
# disable welcome message
self.dconf_file.create()
self.dconf_file.save()
run('dconf update')
self.setup_graphical(install_missing, gdm)

if not isDistro('fedora'):
run(['dnf', 'groupinstall', "Smart Card Support", '-y',
Expand All @@ -216,6 +199,38 @@ def setup_system(self, install_missing: bool, gdm: bool, graphical: bool):
dump_to_json(user.User(username="root",
password=self.lib_conf["root_passwd"]))

def setup_graphical(self, install_missing: bool, gdm: bool):
packages = ["gcc", "tesseract", "ffmpeg-free"]

if gdm:
packages.append("gdm")

missing = _check_packages(packages)
if install_missing and missing:
_install_packages(missing)
elif missing:
msg = "Can't continue with graphical. Some packages are missing: " \
f"{', '.join(missing)}"
logger.critical(msg)
raise exceptions.SCAutolibException(msg)

if not isDistro('fedora'):
run(['dnf', 'groupinstall', 'Server with GUI', '-y',
'--allowerasing'])
run(['pip', 'install', 'python-uinput'])
else:
# Fedora doesn't have server with GUI group so installed gdm
# manually and also python3-uinput should be installed from RPM
run(['dnf', 'install', 'gdm', 'python3-uinput', '-y'])
# disable subscription message
run(['systemctl', '--global', 'mask',
'org.gnome.SettingsDaemon.Subscription.target'])
# disable welcome message
if not self.dconf_file.exists():
self.dconf_file.create()
self.dconf_file.save()
run('dconf update')

def setup_local_ca(self, force: bool = False):
"""
Setup local CA based on configuration from the configuration file. All
Expand Down
6 changes: 6 additions & 0 deletions SCAutolib/models/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ def remove(self):
f"Removed file {self._conf_file}."
)

def exists(self):
"""
Checks if a file exists. Returns boolean.
"""
return self._conf_file.exists()

def set(self, key: str, value: Union[int, str, bool], section: str = None,
separator: str = "="):
"""
Expand Down
Loading
Loading