mirror of https://github.com/borgbackup/borg.git
yes(): handle JSON output
This commit is contained in:
parent
85a2d2fc08
commit
e98b5b20df
|
@ -96,6 +96,8 @@ log_message
|
|||
:ref:`msgid <msgid>`
|
||||
Message ID, may be *none* or absent
|
||||
|
||||
See Prompts_ for the types used by prompts.
|
||||
|
||||
.. rubric:: Examples (reformatted, each object would be on exactly one line)
|
||||
|
||||
:ref:`borg_extract` progress::
|
||||
|
@ -129,10 +131,78 @@ A debug log message::
|
|||
{"message": "35 self tests completed in 0.08 seconds",
|
||||
"type": "log_message", "created": 1488278449.5575905, "levelname": "DEBUG", "name": "borg.archiver"}
|
||||
|
||||
Prompts
|
||||
-------
|
||||
|
||||
Prompts assume a JSON form as well when the ``--log-json`` option is specified. Responses
|
||||
are still read verbatim from *stdin*, while prompts are JSON messages printed to *stderr*,
|
||||
just like log messages.
|
||||
|
||||
Prompts use the *question_prompt*, *question_prompt_retry*, *question_invalid_answer*,
|
||||
*question_accepted_default*, *question_accepted_true*, *question_accepted_false* and
|
||||
*question_env_answer* types.
|
||||
|
||||
The *message* property contains the same string displayed regularly in the same situation,
|
||||
while the *msgid* property may contain a msgid_, typically the name of the
|
||||
environment variable that can be used to override the prompt. It is the same for all JSON
|
||||
messages pertaining to the same prompt.
|
||||
|
||||
The *is_prompt* boolean property distinguishes informational messages from prompts, it
|
||||
is true for *question_prompt* and *question_prompt_retry* types, otherwise it is false.
|
||||
|
||||
.. rubric:: Examples (reformatted, each object would be on exactly one line)
|
||||
|
||||
Providing an invalid answer::
|
||||
|
||||
{"type": "question_prompt", "msgid": "BORG_CHECK_I_KNOW_WHAT_I_AM_DOING", "is_prompt": true,
|
||||
"message": "... Type 'YES' if you understand this and want to continue: "}
|
||||
incorrect answer # input on stdin
|
||||
{"type": "question_invalid_answer", "msgid": "BORG_CHECK_I_KNOW_WHAT_I_AM_DOING", "is_prompt": false,
|
||||
"message": "Invalid answer, aborting."}
|
||||
|
||||
Providing a false (negative) answer::
|
||||
|
||||
{"type": "question_prompt", "msgid": "BORG_CHECK_I_KNOW_WHAT_I_AM_DOING", "is_prompt": true,
|
||||
"message": "... Type 'YES' if you understand this and want to continue: "}
|
||||
NO # input on stdin
|
||||
{"type": "question_accepted_false", "msgid": "BORG_CHECK_I_KNOW_WHAT_I_AM_DOING",
|
||||
"message": "Aborting.", "is_prompt": false}
|
||||
|
||||
Providing a true (affirmative) answer::
|
||||
|
||||
{"type": "question_prompt", "msgid": "BORG_CHECK_I_KNOW_WHAT_I_AM_DOING", "is_prompt": true,
|
||||
"message": "... Type 'YES' if you understand this and want to continue: "}
|
||||
YES # input on stdin
|
||||
# no further output, just like the prompt without --log-json
|
||||
|
||||
Passphrase prompts
|
||||
------------------
|
||||
|
||||
Passphrase prompts should be handled differently. Use the environment variables *BORG_PASSPHRASE*
|
||||
and *BORG_NEW_PASSPHRASE* (see :ref:`env_vars` for reference) to pass passphrases to Borg, don't
|
||||
use the interactive passphrase prompts.
|
||||
|
||||
When setting a new passphrase (:ref:`borg_init`, :ref:`borg_key_change-passphrase`) normally
|
||||
Borg prompts whether it should display the passphrase. This can be suppressed by setting
|
||||
the environment variable *BORG_DISPLAY_PASSPHRASE* to *no*.
|
||||
|
||||
When "confronted" with an unknown repository, where the application does not know whether
|
||||
the repository is encrypted, the following algorithm can be followed to detect encryption:
|
||||
|
||||
1. Set *BORG_PASSPHRASE* to gibberish (for example a freshly generated UUID4, which cannot
|
||||
possibly be the passphrase)
|
||||
2. Invoke ``borg list repository ...``
|
||||
3. If this fails, due the repository being encrypted and the passphrase obviously being
|
||||
wrong, you'll get an error with the *PassphraseWrong* msgid.
|
||||
|
||||
The repository is encrypted, for further access the application will need the passphrase.
|
||||
|
||||
4. If this does not fail, then the repository is not encrypted.
|
||||
|
||||
Standard output
|
||||
---------------
|
||||
|
||||
*stdout* is different and more command-dependent. Commands like :ref:`borg_info`, :ref:`borg_create`
|
||||
*stdout* is different and more command-dependent than logging. Commands like :ref:`borg_info`, :ref:`borg_create`
|
||||
and :ref:`borg_list` implement a ``--json`` option which turns their regular output into a single JSON object.
|
||||
|
||||
Dates are formatted according to ISO-8601 with the strftime format string '%a, %Y-%m-%d %H:%M:%S',
|
||||
|
@ -461,3 +531,15 @@ Operations
|
|||
*info* is one string element, the name of the path currently extracted.
|
||||
- extract.permissions
|
||||
- archive.delete
|
||||
|
||||
Prompts
|
||||
BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK
|
||||
For "Warning: Attempting to access a previously unknown unencrypted repository"
|
||||
BORG_RELOCATED_REPO_ACCESS_IS_OK
|
||||
For "Warning: The repository at location ... was previously located at ..."
|
||||
BORG_CHECK_I_KNOW_WHAT_I_AM_DOING
|
||||
For "Warning: 'check --repair' is an experimental feature that might result in data loss."
|
||||
BORG_DELETE_I_KNOW_WHAT_I_AM_DOING
|
||||
For "You requested to completely DELETE the repository *including* all archives it contains:"
|
||||
BORG_RECREATE_I_KNOW_WHAT_I_AM_DOING
|
||||
For "recreate is an experimental feature."
|
||||
|
|
|
@ -117,6 +117,7 @@ Borg can exit with the following return codes (rc):
|
|||
If you use ``--show-rc``, the return code is also logged at the indicated
|
||||
level as the last log entry.
|
||||
|
||||
.. _env_vars:
|
||||
|
||||
Environment Variables
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -1307,7 +1307,8 @@ DEFAULTISH = ('Default', 'DEFAULT', 'default', 'D', 'd', '', )
|
|||
def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
||||
retry_msg=None, invalid_msg=None, env_msg='{} (from {})',
|
||||
falsish=FALSISH, truish=TRUISH, defaultish=DEFAULTISH,
|
||||
default=False, retry=True, env_var_override=None, ofile=None, input=input, prompt=True):
|
||||
default=False, retry=True, env_var_override=None, ofile=None, input=input, prompt=True,
|
||||
msgid=None):
|
||||
"""Output <msg> (usually a question) and let user input an answer.
|
||||
Qualifies the answer according to falsish, truish and defaultish as True, False or <default>.
|
||||
If it didn't qualify and retry is False (no retries wanted), return the default [which
|
||||
|
@ -1337,6 +1338,23 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
|||
:param input: input function [input from builtins]
|
||||
:return: boolean answer value, True or False
|
||||
"""
|
||||
def output(msg, msg_type, is_prompt=False, **kwargs):
|
||||
json_output = getattr(logging.getLogger('borg'), 'json', False)
|
||||
if json_output:
|
||||
kwargs.update(dict(
|
||||
type='question_%s' % msg_type,
|
||||
msgid=msgid,
|
||||
message=msg,
|
||||
is_prompt=is_prompt,
|
||||
))
|
||||
print(json.dumps(kwargs), file=sys.stderr)
|
||||
else:
|
||||
if is_prompt:
|
||||
print(msg, file=ofile, end='', flush=True)
|
||||
else:
|
||||
print(msg, file=ofile)
|
||||
|
||||
msgid = msgid or env_var_override
|
||||
# note: we do not assign sys.stderr as default above, so it is
|
||||
# really evaluated NOW, not at function definition time.
|
||||
if ofile is None:
|
||||
|
@ -1344,13 +1362,13 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
|||
if default not in (True, False):
|
||||
raise ValueError("invalid default value, must be True or False")
|
||||
if msg:
|
||||
print(msg, file=ofile, end='', flush=True)
|
||||
output(msg, 'prompt', is_prompt=True)
|
||||
while True:
|
||||
answer = None
|
||||
if env_var_override:
|
||||
answer = os.environ.get(env_var_override)
|
||||
if answer is not None and env_msg:
|
||||
print(env_msg.format(answer, env_var_override), file=ofile)
|
||||
output(env_msg.format(answer, env_var_override), 'env_answer', env_var=env_var_override)
|
||||
if answer is None:
|
||||
if not prompt:
|
||||
return default
|
||||
|
@ -1361,23 +1379,23 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
|
|||
answer = truish[0] if default else falsish[0]
|
||||
if answer in defaultish:
|
||||
if default_msg:
|
||||
print(default_msg, file=ofile)
|
||||
output(default_msg, 'accepted_default')
|
||||
return default
|
||||
if answer in truish:
|
||||
if true_msg:
|
||||
print(true_msg, file=ofile)
|
||||
output(true_msg, 'accepted_true')
|
||||
return True
|
||||
if answer in falsish:
|
||||
if false_msg:
|
||||
print(false_msg, file=ofile)
|
||||
output(false_msg, 'accepted_false')
|
||||
return False
|
||||
# if we get here, the answer was invalid
|
||||
if invalid_msg:
|
||||
print(invalid_msg, file=ofile)
|
||||
output(invalid_msg, 'invalid_answer')
|
||||
if not retry:
|
||||
return default
|
||||
if retry_msg:
|
||||
print(retry_msg, file=ofile, end='', flush=True)
|
||||
output(retry_msg, 'prompt_retry', is_prompt=True)
|
||||
# in case we used an environment variable and it gave an invalid answer, do not use it again:
|
||||
env_var_override = None
|
||||
|
||||
|
|
Loading…
Reference in New Issue