444 lines
15 KiB
Python
444 lines
15 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Reusable parts for outputting Shipyard results in CLI format"""
|
|
|
|
from shipyard_client.cli import format_utils
|
|
|
|
|
|
def gen_action_steps(step_list, action_id):
|
|
"""Generate a table from the list of steps.
|
|
|
|
Assumes that the input list contains dictionaries with 'id', 'index', and
|
|
'state' fields.
|
|
Returns a string representation of the table.
|
|
"""
|
|
# Generate the steps table.
|
|
steps = format_utils.table_factory(
|
|
field_names=['Steps', 'Index', 'State', 'Footnotes']
|
|
)
|
|
# rendered notes , a list of lists of notes
|
|
r_notes = []
|
|
|
|
if step_list:
|
|
for step in step_list:
|
|
notes = step.get('notes')
|
|
if notes:
|
|
r_notes.append(format_notes(notes))
|
|
steps.add_row([
|
|
'step/{}/{}'.format(action_id, step.get('id')),
|
|
step.get('index'),
|
|
step.get('state'),
|
|
"({})".format(len(r_notes)) if notes else ""
|
|
])
|
|
else:
|
|
steps.add_row(['None', '', '', ''])
|
|
|
|
return "{}\n\n{}".format(
|
|
format_utils.table_get_string(steps),
|
|
notes_table("Step", r_notes))
|
|
|
|
|
|
def gen_action_commands(command_list):
|
|
"""Generate a table from the list of commands
|
|
|
|
Assumes command_list is a list of dictionaries with 'command', 'user', and
|
|
'datetime'.
|
|
"""
|
|
cmds = format_utils.table_factory(
|
|
field_names=['Commands', 'User', 'Datetime'])
|
|
if command_list:
|
|
for cmd in command_list:
|
|
cmds.add_row(
|
|
[cmd.get('command'),
|
|
cmd.get('user'),
|
|
cmd.get('datetime')])
|
|
else:
|
|
cmds.add_row(['None', '', ''])
|
|
|
|
return format_utils.table_get_string(cmds)
|
|
|
|
|
|
def gen_action_validations(validation_list):
|
|
"""Generates a CLI formatted listing of validations
|
|
|
|
Assumes validation_list is a list of dictionaries with 'validation_name',
|
|
'action_id', 'id', and 'details'.
|
|
"""
|
|
if validation_list:
|
|
validations = []
|
|
for val in validation_list:
|
|
validations.append('{} : validation/{}/{}\n'.format(
|
|
val.get('validation_name'), val.get('action_id'), val.get(
|
|
'id')))
|
|
validations.append(val.get('details'))
|
|
validations.append('\n\n')
|
|
return 'Validations: {}'.format('\n'.join(validations))
|
|
else:
|
|
return 'Validations: {}'.format('None')
|
|
|
|
|
|
def gen_action_details(action_dict):
|
|
"""Generates the detailed information for an action
|
|
|
|
Assumes action_dict is a dictionary with 'name', 'id', 'action_lifecycle',
|
|
'parameters', 'datetime', 'dag_status', 'context_marker', and 'user'
|
|
"""
|
|
details = format_utils.table_factory()
|
|
details.add_row(['Name:', action_dict.get('name')])
|
|
details.add_row(['Action:', 'action/{}'.format(action_dict.get('id'))])
|
|
details.add_row(['Lifecycle:', action_dict.get('action_lifecycle')])
|
|
details.add_row(['Parameters:', str(action_dict.get('parameters'))])
|
|
details.add_row(['Datetime:', action_dict.get('datetime')])
|
|
details.add_row(['Dag Status:', action_dict.get('dag_status')])
|
|
details.add_row(['Context Marker:', action_dict.get('context_marker')])
|
|
details.add_row(['User:', action_dict.get('user')])
|
|
return format_utils.table_get_string(details)
|
|
|
|
|
|
def gen_action_step_details(step_dict, action_id):
|
|
"""Generates the detailed information for an action step
|
|
|
|
Assumes action_dict is a dictionary with 'index', 'state', 'start_date',
|
|
'end_date', 'duration', 'try_number', and 'operator'
|
|
"""
|
|
details = format_utils.table_factory()
|
|
details.add_row(['Name:', step_dict.get('task_id')])
|
|
details.add_row(
|
|
['Task ID:', 'step/{}/{}'.format(action_id, step_dict.get('task_id'))])
|
|
details.add_row(['Index:', step_dict.get('index')])
|
|
details.add_row(['State:', step_dict.get('state')])
|
|
details.add_row(['Start Date:', step_dict.get('start_date')])
|
|
details.add_row(['End Date:', step_dict.get('end_date')])
|
|
details.add_row(['Duration:', step_dict.get('duration')])
|
|
details.add_row(['Try Number:', step_dict.get('try_number')])
|
|
details.add_row(['Operator:', step_dict.get('operator')])
|
|
return format_utils.table_get_string(details)
|
|
|
|
|
|
def gen_action_table(action_list):
|
|
"""Generates a list of actions
|
|
|
|
Assumes action_list is a list of dictionaries with 'name', 'id', and
|
|
'action_lifecycle'
|
|
"""
|
|
actions = format_utils.table_factory(
|
|
field_names=['Name', 'Action', 'Lifecycle', 'Execution Time',
|
|
'Step Succ/Fail/Oth', 'Footnotes'])
|
|
# list of lists of rendered notes
|
|
r_notes = []
|
|
if action_list:
|
|
# sort by id, which is ULID - chronological.
|
|
for action in sorted(action_list, key=lambda k: k['id']):
|
|
notes = action.get('notes')
|
|
if notes:
|
|
r_notes.append(format_notes(notes))
|
|
actions.add_row([
|
|
action.get('name'),
|
|
'action/{}'.format(action.get('id')),
|
|
action.get('action_lifecycle'),
|
|
action.get('dag_execution_date'),
|
|
_step_summary(action.get('steps', [])),
|
|
"({})".format(len(r_notes)) if notes else ""
|
|
])
|
|
else:
|
|
actions.add_row(['None', '', '', '', '', ''])
|
|
|
|
return "{}\n\n{}".format(
|
|
format_utils.table_get_string(actions),
|
|
notes_table("Action", r_notes))
|
|
|
|
|
|
def _step_summary(step_list):
|
|
"""Creates a single string representation of the step status
|
|
|
|
Success/Failed/Other counts in each position
|
|
"""
|
|
successes = 0
|
|
failures = 0
|
|
other = 0
|
|
for s in step_list:
|
|
state = s.get('state')
|
|
if state in ['success']:
|
|
successes += 1
|
|
elif state in ['failed']:
|
|
failures += 1
|
|
else:
|
|
other += 1
|
|
return "{}/{}/{}".format(successes, failures, other)
|
|
|
|
|
|
def gen_collection_table(collection_list):
|
|
"""Generates a list of collections and their status
|
|
|
|
Assumes collection_list is a list of dictionares with 'collection_name',
|
|
'base_status', 'new_status', 'base_version', 'base_revision', 'new_version'
|
|
and 'new_revision'.
|
|
"""
|
|
collections = format_utils.table_factory(
|
|
field_names=['Collection',
|
|
'Base',
|
|
'New'])
|
|
|
|
if collection_list:
|
|
for collection in collection_list:
|
|
collections.add_row([
|
|
collection.get('collection_name'),
|
|
collection.get('base_status'),
|
|
collection.get('new_status')
|
|
])
|
|
|
|
collections_title = (
|
|
'Comparing Base: {} (Deckhand revision {}) \n\t to New: {} '
|
|
'(Deckhand revision {})'.format(
|
|
collection.get('base_version'),
|
|
collection.get('base_revision'),
|
|
collection.get('new_version'),
|
|
collection.get('new_revision')))
|
|
|
|
else:
|
|
collections.add_row(['None', '', ''])
|
|
collections_title = ''
|
|
|
|
return format_utils.table_get_string(table=collections,
|
|
title=collections_title,
|
|
vertical_char=' ')
|
|
|
|
|
|
def gen_workflow_table(workflow_list):
|
|
"""Generates a list of workflows
|
|
|
|
Assumes workflow_list is a list of dictionaries with 'workflow_id' and
|
|
'state'
|
|
"""
|
|
workflows = format_utils.table_factory(field_names=['Workflows', 'State'])
|
|
if workflow_list:
|
|
for workflow in workflow_list:
|
|
workflows.add_row(
|
|
[workflow.get('workflow_id'),
|
|
workflow.get('state')])
|
|
else:
|
|
workflows.add_row(['None', ''])
|
|
|
|
return format_utils.table_get_string(workflows)
|
|
|
|
|
|
def gen_workflow_details(workflow_dict):
|
|
"""Generates a workflow detail
|
|
|
|
Assumes workflow_dict has 'execution_date', 'end_date', 'workflow_id',
|
|
'start_date', 'external_trigger', 'steps', 'dag_id', 'state', 'run_id',
|
|
and 'sub_dags'
|
|
"""
|
|
details = format_utils.table_factory()
|
|
details.add_row(['Workflow:', workflow_dict.get('workflow_id')])
|
|
|
|
details.add_row(['State:', workflow_dict.get('state')])
|
|
details.add_row(['Dag ID:', workflow_dict.get('dag_id')])
|
|
details.add_row(['Execution Date:', workflow_dict.get('execution_date')])
|
|
details.add_row(['Start Date:', workflow_dict.get('start_date')])
|
|
details.add_row(['End Date:', workflow_dict.get('end_date')])
|
|
details.add_row(
|
|
['External Trigger:',
|
|
workflow_dict.get('external_trigger')])
|
|
return format_utils.table_get_string(details)
|
|
|
|
|
|
def gen_workflow_steps(step_list):
|
|
"""Generates a table of steps for a workflow
|
|
|
|
Assumes step_list is a list of dictionaries with 'task_id' and 'state'
|
|
"""
|
|
steps = format_utils.table_factory(field_names=['Steps', 'State'])
|
|
if step_list:
|
|
for step in step_list:
|
|
steps.add_row([step.get('task_id'), step.get('state')])
|
|
else:
|
|
steps.add_row(['None', ''])
|
|
|
|
return format_utils.table_get_string(steps)
|
|
|
|
|
|
def gen_sub_workflows(wf_list):
|
|
"""Generates the list of Sub Workflows
|
|
|
|
Assumes wf_list is a list of dictionaries with the same contents as a
|
|
standard workflow
|
|
"""
|
|
wfs = []
|
|
for wf in wf_list:
|
|
wfs.append(gen_workflow_details(wf))
|
|
return '\n\n'.join(wfs)
|
|
|
|
|
|
def gen_site_statuses(status_dict):
|
|
"""Generates site statuses table.
|
|
|
|
Assumes status_types as list of filters and status_dict a dictionary
|
|
with statuses lists
|
|
"""
|
|
formatted_output = ''
|
|
|
|
status_types = status_dict.keys()
|
|
|
|
for st in status_types:
|
|
call_func = _site_statuses_switcher(st)
|
|
op = call_func(status_dict)
|
|
formatted_output = "{}\n{}\n".format(formatted_output, op)
|
|
|
|
return formatted_output
|
|
|
|
|
|
def _gen_machines_powerstate_table(status_dict):
|
|
# Generates machines power states status table
|
|
|
|
machine_powerstate_table = format_utils.table_factory(
|
|
field_names=['Hostname', 'Power State'])
|
|
|
|
pwrstate_list = status_dict.get('machines_powerstate')
|
|
|
|
if pwrstate_list:
|
|
for pwrstate in pwrstate_list:
|
|
machine_powerstate_table.add_row(
|
|
[pwrstate.get('hostname'),
|
|
pwrstate.get('power_state')])
|
|
else:
|
|
machine_powerstate_table.add_row(['', ''])
|
|
|
|
return format_utils.table_get_string(table=machine_powerstate_table,
|
|
title="Machines Power State:",
|
|
vertical_char=' ')
|
|
|
|
|
|
def _gen_nodes_provision_status_table(status_dict):
|
|
# Generates nodes provision status table
|
|
|
|
nodes_status_table = format_utils.table_factory(
|
|
field_names=['Hostname', 'Status'])
|
|
prov_status_list = status_dict.get('nodes_provision_status')
|
|
|
|
if prov_status_list:
|
|
for status in prov_status_list:
|
|
nodes_status_table.add_row(
|
|
[status.get('hostname'),
|
|
status.get('status')])
|
|
else:
|
|
nodes_status_table.add_row(['', ''])
|
|
|
|
return format_utils.table_get_string(table=nodes_status_table,
|
|
title="Nodes Provision Status:",
|
|
vertical_char=' ')
|
|
|
|
|
|
def _site_statuses_switcher(status_type):
|
|
"""Maps status types with a callabe function to the format
|
|
output.
|
|
|
|
The dictionary will be updated with new functions
|
|
to map future supported status-types for "site-statuses"
|
|
"""
|
|
status_func_switcher = {
|
|
'nodes_provision_status': _gen_nodes_provision_status_table,
|
|
'machines_powerstate': _gen_machines_powerstate_table,
|
|
}
|
|
|
|
call_func = status_func_switcher.get(status_type, lambda: None)
|
|
|
|
return call_func
|
|
|
|
|
|
def gen_detail_notes(title, dict_with_notes):
|
|
"""Generates a standard formatted section of notes
|
|
|
|
:param title: the title for the notes section. E.g.: "Step"
|
|
:param dict_with_notes: a dictionary with a possible notes field.
|
|
:returns: string of notes or empty string if there were no notes
|
|
"""
|
|
n_strings = format_notes(dict_with_notes.get('notes', []))
|
|
if n_strings:
|
|
return "{} Notes:\n{}".format(title, "\n".join(n_strings))
|
|
return ""
|
|
|
|
|
|
def notes_table(title, notes_list):
|
|
"""Format a table of notes
|
|
|
|
:param title: the header for the table. e.g.: "Step"
|
|
:param list notes_list: a list of lists of formatted notes:
|
|
e.g.:[[note1,note2],[note3]]
|
|
The notes ideally have been pre-formatted by "format_notes"
|
|
|
|
:returns: string of a table e.g.:
|
|
|
|
Step Notes Note
|
|
(1) > note1
|
|
- Info avail...
|
|
> note2
|
|
(2) > note3
|
|
|
|
If notes_list is empty, returns an empty string.
|
|
"""
|
|
if not notes_list:
|
|
return ""
|
|
headers = ["{} Footnotes".format(title), "Note"]
|
|
rows = []
|
|
index = 1
|
|
for notes in notes_list:
|
|
rows.append(["({})".format(index), "\n".join(notes)])
|
|
index += 1
|
|
return format_utils.table_get_string(
|
|
format_utils.table_factory(headers, rows))
|
|
|
|
|
|
def format_notes(notes):
|
|
"""Formats a list of notes.
|
|
|
|
:param list notes: The list of note dictionaries to display
|
|
:returns: a list of note strings
|
|
|
|
Assumed note dictionary example:
|
|
{
|
|
'assoc_id': "action/12345678901234567890123456,
|
|
'subject': "12345678901234567890123456",
|
|
'sub_type': "action",
|
|
'note_val': "message",
|
|
'verbosity': 1,
|
|
'note_id': "09876543210987654321098765",
|
|
'note_timestamp': "2018-10-08 14:23:53.346534",
|
|
'resolved_url_value': \
|
|
"Details at notedetails/09876543210987654321098765"
|
|
}
|
|
|
|
Resulting in:
|
|
|
|
> action:12345678901234567890123456(2018-10-08 14:23:53.346534): message
|
|
- Info available with 'describe notedetails/09876543210987654321098765'
|
|
"""
|
|
nl = []
|
|
for n in notes:
|
|
try:
|
|
s = "> {}:{}({}): {}".format(
|
|
n['sub_type'],
|
|
n['subject'],
|
|
n['note_timestamp'],
|
|
n['note_val']
|
|
)
|
|
if n['resolved_url_value']:
|
|
s += ("\n - Info available with "
|
|
"'describe notedetails/{}'".format(n['note_id']))
|
|
except KeyError:
|
|
s = "!!! Unparseable Note: {}".format(n)
|
|
|
|
nl.append(s)
|
|
return nl
|