Skip to content

Commit

Permalink
Minor fixes and revised documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Otavio Napoli committed May 23, 2021
1 parent 6217a3e commit f43abba
Show file tree
Hide file tree
Showing 286 changed files with 66,494 additions and 363 deletions.
4 changes: 4 additions & 0 deletions app/cli/cliapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
os.environ['CLAP_PATH'] = path_extend('~', '.clap')


class ArgumentError(Exception):
pass


class Defaults(metaclass=Singleton):
def __init__(self):
self.verbosity: int = 0
Expand Down
457 changes: 216 additions & 241 deletions app/cli/modules/cluster.py

Large diffs are not rendered by default.

92 changes: 47 additions & 45 deletions app/cli/modules/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from clap.node_manager import NodeManager, NodeRepositoryController
from clap.repository import RepositoryFactory
from clap.utils import path_extend, float_time_to_string, get_logger, \
Singleton, defaultdict_to_dict
Singleton, defaultdict_to_dict, str_at_middle
from providers.provider_ansible_aws import AnsibleAWSProvider
from app.cli.cliapp import clap_command, Defaults

Expand Down Expand Up @@ -85,19 +85,18 @@ def node(repository):

@node.command('start')
@click.argument('instance', nargs=-1, type=str)
@click.option('-st', '--start-timeout', default=600, help='Timeout to start nodes',
show_default=True)
@click.option('-cr', '--connection-tries', default=15,
help='Number of the maximum number of SSH connection tries for '
'check if node is alive. If zero, nodes is not checked',
show_default=True)
@click.option('-rt', '--retry-delay', default=30,
help='Delay time to try another SSH connection',
show_default=True)
@click.option('-t', '--terminate_not_alive', default=False,
@click.option('-st', '--start-timeout', default=600, show_default=True,
help='Timeout to start nodes')
@click.option('-cr', '--connection-tries', default=15, show_default=True,
help="Number of SSH connection tries to check if node is alive. "
"If the value is set to zero, only the node's information is "
"updated and no login attempts(via SSH) is performed.")
@click.option('-rt', '--retry-delay', default=30, show_default=True,
help='Time between an unsuccessful connection and another')
@click.option('-t', '--terminate_not_alive', default=False, show_default=True,
is_flag=True,
help='Terminate nodes if no SSH connection were possible. '
'Connection-tries parameter must be higher than 0',
show_default=True, is_flag=True)
'Connection-tries parameter must be higher than 0')
def node_start(instance, start_timeout, connection_tries, retry_delay,
terminate_not_alive):
""" Control and manage nodes.
Expand Down Expand Up @@ -168,9 +167,8 @@ def node_start(instance, start_timeout, connection_tries, retry_delay,
@click.argument('node_id', nargs=-1)
@click.option('-t', '--tags', default=None, type=str, multiple=True,
help='Filter nodes by tags. There are two formats: <key> or <key>=<val>')
@click.option('-d', '--detailed',
help='Show detailed node information',
default=0, show_default=True, count=True)
@click.option('-d', '--detailed', default=0, show_default=True, count=True,
help='Show detailed node information')
@click.option('-i', '--indent', default=4, show_default=True, nargs=1, type=int,
help="Indentation level")
@click.option('-q', '--quiet', default=False, is_flag=True, show_default=True,
Expand Down Expand Up @@ -215,13 +213,13 @@ def node_list(node_id, tags, detailed, indent, quiet):
@click.argument('node_id', nargs=-1)
@click.option('-t', '--tags', default=None, type=str, multiple=True,
help='Filter nodes by tags. There are two formats: <key> or <key>=<val>')
@click.option('-f', '--force', default=True,
help='Remove nodes from repository even if fails', show_default=True,
is_flag=True)
@click.option('-f', '--force', default=True, is_flag=True, show_default=True,
help='Remove nodes from repository even if stop operation fails')
def node_stop(node_id, tags, force):
""" Stop a node (terminating it) and remove it from node repository.
The NODE_ID argument is a list of strings (optional) and can filter nodes to stop by their node ids
The NODE_ID argument is a list of strings (optional) and can filter nodes to
stop by their node ids
"""
if not node_id and not tags:
print('Stopped 0 nodes')
Expand All @@ -244,16 +242,14 @@ def node_stop(node_id, tags, force):

@node.command('alive')
@click.argument('node_id', nargs=-1)
# help='Nodes to be checked. If not provided, check all nodes in repository')
@click.option('-t', '--tags', default=None, type=str, multiple=True,
help='Filter nodes by tags. There are two formats: <key> or <key>=<val>')
@click.option('-cr', '--connection-tries', default=15,
help='Number of the maximum number of SSH connection tries for '
'check if node is alive. If zero, nodes is not checked',
show_default=True)
@click.option('-rt', '--retry-delay', default=30,
help='Delay time to try another SSH connection',
show_default=True)
@click.option('-cr', '--connection-tries', default=15, show_default=True,
help="Number of SSH connection tries to check if node is alive. "
"If the value is set to zero, only the node's information is "
"updated and no login attempts(via SSH) is performed.")
@click.option('-rt', '--retry-delay', default=30, show_default=True,
help='Time between an unsuccessful connection and another')
def node_alive(node_id, tags, connection_tries, retry_delay):
""" Check if nodes are alive (a successful SSH connection can be established).
Expand Down Expand Up @@ -286,19 +282,19 @@ def node_alive(node_id, tags, connection_tries, retry_delay):
@click.argument('node_id', nargs=-1)
@click.option('-t', '--tags', default=None, type=str, multiple=True,
help='Filter nodes by tags. There are two formats: <key> or <key>=<val>')
@click.option('-st', '--start-timeout', default=600,
help='Timeout to resume nodes', show_default=True)
@click.option('-cr', '--connection-tries', default=15,
help='Number of the maximum number of SSH connection tries for '
'check if node is alive. If zero, nodes is not checked',
show_default=True)
@click.option('-rt', '--retry-delay', default=30,
help='Delay time to try another SSH connection',
show_default=True)
@click.option('-st', '--start-timeout', default=600, show_default=True,
help='Timeout to resume nodes')
@click.option('-cr', '--connection-tries', default=15, show_default=True,
help="Number of SSH connection tries to check if node is alive. "
"If the value is set to zero, only the node's information is "
"updated and no login attempts(via SSH) is performed.")
@click.option('-rt', '--retry-delay', default=30, show_default=True,
help='Time between an unsuccessful connection and another')
def node_resume(node_id, tags, start_timeout, connection_tries, retry_delay):
""" Resume nodes (if possible).
The NODE_ID argument is a list of strings (optional) and can filter nodes to resume by their node ids
The NODE_ID argument is a list of strings (optional) and can filter nodes to
resume by their node ids
"""
node_manager = get_node_manager()

Expand Down Expand Up @@ -329,7 +325,8 @@ def node_resume(node_id, tags, start_timeout, connection_tries, retry_delay):
def node_pause(node_id, tags):
""" Pause nodes (if possible).
The NODE_ID argument is a list of strings (optional) and can filter nodes to pause by their node ids
The NODE_ID argument is a list of strings (optional) and can filter nodes to
pause by their node ids
"""
node_manager = get_node_manager()

Expand Down Expand Up @@ -370,7 +367,7 @@ def node_connect(node_id):
def node_add_tag(node_id, tags):
""" Add tags to a set of nodes.
The NODE_ID argument is a list of strings (optional) and can filter nodes to add tags by their node ids
The NODE_ID argument is a list of node_ids to add tags.
"""
node_manager = get_node_manager()
final_tags = dict()
Expand All @@ -393,7 +390,7 @@ def node_add_tag(node_id, tags):
def node_remove_tags(node_id, tags):
""" Remove tags from a set of nodes.
The NODE_ID argument is a list of strings (optional) and can filter nodes to remove tags by their node ids
The NODE_ID argument is a list of node_ids to remove tags.
"""
node_manager = get_node_manager()
removeds = node_manager.remove_tags(node_id, tags)
Expand Down Expand Up @@ -450,7 +447,8 @@ def node_execute(node_id, tags, command, additional, timeout):
if not result['ok']:
print(f"{node_id[:8]}: Error executing command in node. {result['error']}")
continue
print('-' * 80)
print(str_at_middle(node_id, 80, '-'))
print(f'return code {node_id[:8]}: {result["return_code"]}')
print(f'stdout {node_id[:8]}: '.join(result['stdout_lines']))
print(f'stderr {node_id[:8]}: '.join(result['stderr_lines']))

Expand Down Expand Up @@ -508,10 +506,10 @@ def list_instance_templates(template, detailed, indent, quiet):
help='Filter nodes by tags. There are two formats: <key> or <key>=<val>')
@click.option('-e', '--extra', default=None, type=str, multiple=True,
help='Extra variables to be passed. Format: <key>=<val>',
show_default=True)
show_default=False)
@click.option('-nv', '--node-vars', default=None, type=str, multiple=True,
help='Host variables to be passed. Format: <node_id>:<key>=<val>',
show_default=True)
show_default=False)
def node_playbook(node_id, playbook, tags, extra, node_vars):
""" Execute an Ansible playbook in a set of nodes.
Expand All @@ -530,6 +528,10 @@ def node_playbook(node_id, playbook, tags, extra, node_vars):
print("No nodes informed")
return 0

playbook = path_extend(playbook)
if not os.path.isfile(playbook):
raise ValueError(f"Invalid playbook file `{playbook}`")

extra_args = dict()
for e in extra:
if '=' not in e:
Expand Down Expand Up @@ -562,7 +564,7 @@ def node_playbook(node_id, playbook, tags, extra, node_vars):
logger.error(f"Playbook {playbook} did not executed successfully...")
return 1

print(f"{'-' * 20} Execution summary {'-' * 20}")
print(str_at_middle("Execution Summary", 80))
for node_id in sorted(list(result.hosts.keys())):
r = result.hosts[node_id]
print(f"{node_id}: {'ok' if r else 'not ok'}")
Expand Down
65 changes: 61 additions & 4 deletions app/cli/modules/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from dataclasses import asdict
from clap.node_manager import NodeRepositoryController
from clap.repository import RepositoryFactory
from clap.role_manager import RoleManager
from clap.role_manager import RoleManager, NodeRoleError
from clap.utils import path_extend, get_logger, Singleton, defaultdict_to_dict
from app.cli.cliapp import clap_command, Defaults
from app.cli.cliapp import clap_command, Defaults, ArgumentError

logger = get_logger(__name__)

Expand Down Expand Up @@ -133,7 +133,7 @@ def role_add(role, node, node_vars, host_vars, extra):
role, hosts_node_map=nodes, host_vars=host_vars,
node_vars=node_vars, extra_args=extra_args)

print(f"{len(added_nodes)} nodes were added to role {role}: {added_nodes}")
print(f"{len(added_nodes)} nodes were added to role {role}: {', '.join(sorted(added_nodes))}")
return 0


Expand Down Expand Up @@ -161,9 +161,26 @@ def role_action(role, action, node, node_vars, host_vars, extra):
role_manager = get_role_manager()
nodes, node_vars, host_vars, extra_args = _split_vars(
node, node_vars, host_vars, extra)
nodes = role_manager.get_role_nodes(role, from_node_ids=nodes)

if not nodes:
nodes = role_manager.get_all_role_nodes_hosts(role)
else:
if type(nodes) is list:
d = defaultdict(list)
for n in nodes:
hosts = role_manager.get_role_node_hosts(role, n)
if not hosts:
raise NodeRoleError(n, role)
for hname in hosts:
d[hname].append(n)
nodes = defaultdict_to_dict(d)
else:
nodes = nodes

all_values = [n for v in nodes.values() for n in v]
if not all_values:
raise ValueError(f"No nodes to perform the action '{action} of role {role}")

result = role_manager.perform_action(
role, action, hosts_node_map=nodes, host_vars=host_vars,
node_vars=node_vars, extra_args=extra_args)
Expand All @@ -173,6 +190,46 @@ def role_action(role, action, node, node_vars, host_vars, extra):
f"executed successfully...")
return 1

print(f"Action {action} from role {role} was successfully performed!")
return 0


@role.command('remove')
@click.argument('role', nargs=1, type=str, required=True)
@click.option('-n', '--node', nargs=1, type=str, multiple=True, required=True,
help='Nodes to perform the action. Can use multiple "-n" commands and it '
'can be a list of colon-separated node as "<node>,<node>,..." '
'or "<role_host_name>:<node>,<node>". The formats are '
'mutually exclusive. If not is passed, the action will be '
'performed in all nodes that belongs to the role.')
def role_remove(role, node):
""" Perform an group action at a set of nodes.
The ROLE argument specify the role which the action will be performed.
"""
role_manager = get_role_manager()
nodes, node_vars, host_vars, extra_args = _split_vars(node, [], [], [])

if not nodes:
raise ArgumentError('No nodes informed')

if type(nodes) is list:
d = defaultdict(list)
for n in nodes:
hosts = role_manager.get_role_node_hosts(role, n)
if not hosts:
raise NodeRoleError(n, role)
for hname in hosts:
d[hname].append(n)
nodes = defaultdict_to_dict(d)
else:
nodes = nodes

if not nodes:
raise ValueError(f"No nodes to remove from role {role}")

result = role_manager.remove_role(role, nodes)
print(f"{len(result)} nodes were removed from {role}: {', '.join(sorted(result))}")
return 0


Expand Down
Loading

0 comments on commit f43abba

Please sign in to comment.