Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
34 changes: 16 additions & 18 deletions leapp/cli/upgrade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from datetime import datetime

from leapp.config import get_config
from leapp.exceptions import CommandError, LeappError
from leapp.exceptions import CommandError, LeappError, UsageError
from leapp.logger import configure_logger
from leapp.messaging.answerstore import AnswerStore
from leapp.repository.scan import find_and_scan_repositories
Expand Down Expand Up @@ -180,14 +180,15 @@ def process_whitelist_experimental(repositories, workflow, configuration, logger
@command_opt('resume', is_flag=True, help='Continue the last execution after it was stopped (e.g. after reboot)')
@command_opt('reboot', is_flag=True, help='Automatically performs reboot when requested.')
@command_opt('whitelist-experimental', action='append', metavar='ActorName', help='Enables experimental actors')
@command_opt('load-answerfile', help='Path to load custom answerfile')
@command_opt('debug', is_flag=True, help='Enable debug mode', inherit=False)
@command_opt('verbose', is_flag=True, help='Enable verbose logging', inherit=False)
def upgrade(args):
skip_phases_until = None
context = str(uuid.uuid4())
cfg = get_config()
configuration = prepare_configuration(args)
answerfile_path = cfg.get('report', 'answerfile')
userchoices_path = cfg.get('report', 'userchoices')

if os.getuid():
raise CommandError('This command has to be run under the root user.')
Expand Down Expand Up @@ -224,16 +225,12 @@ def upgrade(args):
process_whitelist_experimental(repositories, workflow, configuration, logger)
warn_if_unsupported(configuration)
with beautify_actor_exception():
answerfile_path = cfg.get('report', 'answerfile')
logger.info("Using answerfile at %s", answerfile_path)
workflow.load_answerfile(answerfile_path)
workflow.load_answers(answerfile_path, userchoices_path)
workflow.run(context=context, skip_phases_until=skip_phases_until, skip_dialogs=True)

report_errors(workflow.errors)
generate_report_files(context)

cfg = get_config()

report_files = get_cfg_files('report', cfg)
log_files = get_cfg_files('logs', cfg)
report_info(report_files, log_files, fail=workflow.failure)
Expand All @@ -244,14 +241,14 @@ def upgrade(args):

@command('preupgrade', help='Generate preupgrade report')
@command_opt('whitelist-experimental', action='append', metavar='ActorName', help='Enables experimental actors')
@command_opt('save-answerfile', help='Path to save custom answerfile')
@command_opt('debug', is_flag=True, help='Enable debug mode', inherit=False)
@command_opt('verbose', is_flag=True, help='Enable verbose logging', inherit=False)
def preupgrade(args):
context = str(uuid.uuid4())
cfg = get_config()
configuration = prepare_configuration(args)
answerfile_path = cfg.get('report', 'answerfile')
userchoices_path = cfg.get('report', 'userchoices')

if os.getuid():
raise CommandError('This command has to be run under the root user.')
Expand All @@ -270,41 +267,42 @@ def preupgrade(args):
warn_if_unsupported(configuration)
process_whitelist_experimental(repositories, workflow, configuration, logger)
with beautify_actor_exception():
workflow.load_answerfile(answerfile_path)
workflow.load_answers(answerfile_path, userchoices_path)
until_phase = 'ReportsPhase'
logger.info('Executing workflow until phase: %s', until_phase)
workflow.run(context=context, until_phase=until_phase, skip_dialogs=True)

logger.info("Answerfile will be created at %s", answerfile_path)
workflow.save_answerfile(answerfile_path)
workflow.save_answers(answerfile_path, userchoices_path)
generate_report_files(context)
report_errors(workflow.errors)
report_files = get_cfg_files('report', cfg)
log_files = get_cfg_files('logs', cfg)
report_info(report_files, log_files, fail=workflow.failure)
report_info(report_files, log_files, answerfile_path, fail=workflow.failure)
if workflow.failure:
sys.exit(1)


@command('answer', help='Manage answerfile')
@command_opt('answerfile', help='Path to an answerfile to update')
@command('answer', help='Manage answerfile generation: register persistent user choices for specific dialog sections')
@command_opt('section', action='append', metavar='dialog_sections',
help='Record answer for specific section in answerfile')
help='Register answer for a specific section in the answerfile')
@command_opt('add', is_flag=True,
help='If set sections will be created even if missing in original answerfile')
def answer(args):
"""A command to manage answerfile. Updates answerfile with userchoices"""
"""A command to record user choices to the questions in the answerfile.
Saves user answer between leapp preupgrade runs.
"""
cfg = get_config()
if args.section:
args.section = list(itertools.chain(*[i.split(',') for i in args.section]))
else:
raise CommandError('At least one dialog section must be specified, ex. --section dialog.option=mychoice')
raise UsageError('At least one dialog section must be specified, ex. --section dialog.option=mychoice')
try:
sections = [tuple((dialog_option.split('.', 2) + [value]))
for dialog_option, value in [s.split('=', 2) for s in args.section]]
except ValueError:
raise CommandError("A bad formatted section has been passed. Expected format is dialog.option=mychoice")
answerfile_path = args.answerfile or cfg.get('report', 'answerfile')
raise UsageError("A bad formatted section has been passed. Expected format is dialog.option=mychoice")
answerfile_path = cfg.get('report', 'answerfile')
answerstore = AnswerStore()
answerstore.load(answerfile_path)
for dialog, option, value in sections:
Expand Down
1 change: 1 addition & 0 deletions leapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
'dir': '/var/log/leapp/',
'files': ','.join(_REPORTS),
'answerfile': '/var/log/leapp/answerfile',
'userchoices': '/var/log/leapp/answerfile.userchoices'
},
'repositories': {
'repo_path': '.',
Expand Down
1 change: 1 addition & 0 deletions leapp/dialogs/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class BooleanComponent(Component):
"""
BooleanComponent is used for boolean inputs such as Yes/No questions.
"""
choices = ('True', 'False')
values = ('Yes', 'No')
value_type = bool

Expand Down
2 changes: 1 addition & 1 deletion leapp/dialogs/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def serialize(self):

@property
def answerfile_sections(self):
return ["{}.{}".format(self.scope, c.key) for c in self.components]
return {"{}.{}".format(self.scope, c.key): c.choices for c in self.components}

@property
def min_label_width(self):
Expand Down
3 changes: 1 addition & 2 deletions leapp/messaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ def register_dialog(self, dialog, actor):
userchoices = dialog.get_answers(self._answers)
if not userchoices:
# produce DialogModel messages for all the dialogs that don't have answers in answerfile
self.produce(DialogModel(actor=actor.name, answerfile_sections=','.join(dialog.answerfile_sections)),
actor)
self.produce(DialogModel(actor=actor.name, answerfile_sections=dialog.answerfile_sections), actor)
else:
# update dialogs with answers from answerfile. That is necessary for proper answerfile generation
for component, value in userchoices.items():
Expand Down
6 changes: 2 additions & 4 deletions leapp/messaging/answerstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def update(self, answer_file, allow_missing=False):
if section not in conf.sections() and allow_missing:
conf.add_section(section)
try:
conf.set(section, opt, val)
conf.set(section, opt, str(val))
except configparser.NoSectionError:
not_updated.append("{sec}.{opt}={val}".format(sec=section, opt=opt, val=val))
with open(answer_file, 'w') as afile:
Expand Down Expand Up @@ -145,9 +145,7 @@ def generate(self, dialogs, answer_file_path):
choices = ''
answer_entry = ''
if hasattr(component, 'choices'):
choices += '# Available choices:\n'
for choice in component.choices:
choices += '# - {}\n'.format(choice)
choices += '# Available choices: {}\n'.format('/'.join(component.choices))
if component.value_type is tuple:
default = ';'.join(component.default)
answer = ';'.join(answer) if answer else answer
Expand Down
2 changes: 1 addition & 1 deletion leapp/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class ErrorModel(Model):
class DialogModel(Model):
topic = DialogTopic

answerfile_sections = fields.String()
answerfile_sections = fields.JSON()
actor = fields.String()
details = fields.Nullable(fields.String())

Expand Down
5 changes: 4 additions & 1 deletion leapp/utils/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def report_errors(errors):
sys.stdout.write(pretty_block("END OF ERRORS", color=Color.red))


def report_info(report_paths, log_paths, fail=False):
def report_info(report_paths, log_paths, answerfile=None, fail=False):
report_paths = [report_paths] if not isinstance(report_paths, list) else report_paths
log_paths = [log_paths] if not isinstance(report_paths, list) else log_paths

Expand All @@ -63,6 +63,9 @@ def report_info(report_paths, log_paths, fail=False):
sys.stdout.write(pretty_block("END OF REPORT", color=Color.bold if fail else Color.green))
sys.stdout.write("\n")

if answerfile:
sys.stdout.write("Answerfile has been generated at {}\n".format(answerfile))


def report_unsupported(devel_vars, experimental):
sys.stdout.write(pretty_block("UNSUPPORTED UPGRADE", color=Color.yellow))
Expand Down
21 changes: 15 additions & 6 deletions leapp/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,33 @@ def answer_store(self):
"""
return self._answer_store

def save_answerfile(self, filepath):
def save_answers(self, answerfile_path, userchoices_path):
"""
Generates an answer file for the dialogs of the workflow and saves it to `filepath`.
Generates an answer file for the dialogs of the workflow and saves it to `answerfile_path`.
Updates a .userchoices file at `userchoices_path` with new answers encountered in answerfile.

:param filepath: The path of where to store the answer file.
:param answerfile_path: The path where to store the answer file.
:param userchoices_path: The path where to store the .userchoices file.
:return: None
"""
self._answer_store.generate(self._dialogs, filepath)
# answerfile is generated only for the dialogs actually encountered in the worflow
self._answer_store.generate(self._dialogs, answerfile_path)
# userchoices is updated with any new data retrieved from answerfile
self._answer_store.update(userchoices_path, allow_missing=True)

def load_answerfile(self, filepath):
def _load_from_file(self, filepath):
if os.path.isfile(filepath):
# XXX FIXME load_and_translate doesn't help here as somehow dialog.component.value
# in Dialog.request_answers is not respectfully updated (set to None so storage
# values are not taken into consideration).
# Patching in 2 places - load here and direct call to translate in request_answers
self._answer_store.load(filepath)
else:
self.log.warning("Previous answerfile %s not found", filepath)
self.log.warning("Previous file %s not found", filepath)

def load_answers(self, answerfile_path, userchoices_path):
self._load_from_file(userchoices_path)
self._load_from_file(answerfile_path)

def __init__(self, logger=None, auto_reboot=False):
"""
Expand Down