2021-03-21 08:40:25 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2022-07-21 05:27:26 +00:00
|
|
|
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
|
|
|
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2021-03-21 08:40:25 +00:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2021-06-26 11:45:28 +00:00
|
|
|
from ansible.module_utils.common.text.converters import to_text
|
2021-11-17 20:26:49 +00:00
|
|
|
from ansible.module_utils.six import binary_type, PY3
|
2022-10-31 20:33:27 +00:00
|
|
|
from ansible.module_utils.six.moves.http_client import responses as http_responses
|
|
|
|
|
|
|
|
|
|
|
|
def format_http_status(status_code):
|
|
|
|
expl = http_responses.get(status_code)
|
|
|
|
if not expl:
|
|
|
|
return str(status_code)
|
|
|
|
return '%d %s' % (status_code, expl)
|
2021-04-11 12:44:44 +00:00
|
|
|
|
2021-03-21 08:40:25 +00:00
|
|
|
|
|
|
|
def format_error_problem(problem, subproblem_prefix=''):
|
2023-08-21 18:49:55 +00:00
|
|
|
error_type = problem.get('type', 'about:blank') # https://www.rfc-editor.org/rfc/rfc7807#section-3.1
|
2021-03-21 08:40:25 +00:00
|
|
|
if 'title' in problem:
|
|
|
|
msg = 'Error "{title}" ({type})'.format(
|
2023-08-21 18:49:55 +00:00
|
|
|
type=error_type,
|
2021-03-21 08:40:25 +00:00
|
|
|
title=problem['title'],
|
|
|
|
)
|
|
|
|
else:
|
2023-08-21 18:49:55 +00:00
|
|
|
msg = 'Error {type}'.format(type=error_type)
|
2021-03-21 08:40:25 +00:00
|
|
|
if 'detail' in problem:
|
|
|
|
msg += ': "{detail}"'.format(detail=problem['detail'])
|
|
|
|
subproblems = problem.get('subproblems')
|
|
|
|
if subproblems is not None:
|
|
|
|
msg = '{msg} Subproblems:'.format(msg=msg)
|
|
|
|
for index, problem in enumerate(subproblems):
|
|
|
|
index_str = '{prefix}{index}'.format(prefix=subproblem_prefix, index=index)
|
2021-04-11 12:44:44 +00:00
|
|
|
msg = '{msg}\n({index}) {problem}'.format(
|
2021-03-21 08:40:25 +00:00
|
|
|
msg=msg,
|
|
|
|
index=index_str,
|
|
|
|
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index_str)),
|
|
|
|
)
|
|
|
|
return msg
|
|
|
|
|
|
|
|
|
|
|
|
class ModuleFailException(Exception):
|
|
|
|
'''
|
|
|
|
If raised, module.fail_json() will be called with the given parameters after cleanup.
|
|
|
|
'''
|
|
|
|
def __init__(self, msg, **args):
|
|
|
|
super(ModuleFailException, self).__init__(self, msg)
|
|
|
|
self.msg = msg
|
|
|
|
self.module_fail_args = args
|
|
|
|
|
|
|
|
def do_fail(self, module, **arguments):
|
|
|
|
module.fail_json(msg=self.msg, other=self.module_fail_args, **arguments)
|
|
|
|
|
|
|
|
|
|
|
|
class ACMEProtocolException(ModuleFailException):
|
2021-04-11 12:44:44 +00:00
|
|
|
def __init__(self, module, msg=None, info=None, response=None, content=None, content_json=None, extras=None):
|
2021-03-21 08:40:25 +00:00
|
|
|
# Try to get hold of content, if response is given and content is not provided
|
|
|
|
if content is None and content_json is None and response is not None:
|
|
|
|
try:
|
2021-11-17 20:26:49 +00:00
|
|
|
# In Python 2, reading from a closed response yields a TypeError.
|
|
|
|
# In Python 3, read() simply returns ''
|
|
|
|
if PY3 and response.closed:
|
|
|
|
raise TypeError
|
2021-03-21 08:40:25 +00:00
|
|
|
content = response.read()
|
2021-11-17 20:26:49 +00:00
|
|
|
except (AttributeError, TypeError):
|
2021-03-21 08:40:25 +00:00
|
|
|
content = info.pop('body', None)
|
|
|
|
|
2021-04-11 12:44:44 +00:00
|
|
|
# Make sure that content_json is None or a dictionary
|
|
|
|
if content_json is not None and not isinstance(content_json, dict):
|
|
|
|
if content is None and isinstance(content_json, binary_type):
|
|
|
|
content = content_json
|
|
|
|
content_json = None
|
|
|
|
|
2021-03-21 08:40:25 +00:00
|
|
|
# Try to get hold of JSON decoded content, when content is given and JSON not provided
|
2021-04-11 12:44:44 +00:00
|
|
|
if content_json is None and content is not None and module is not None:
|
2021-03-21 08:40:25 +00:00
|
|
|
try:
|
2021-04-11 12:44:44 +00:00
|
|
|
content_json = module.from_json(to_text(content))
|
|
|
|
except Exception as e:
|
2021-03-21 08:40:25 +00:00
|
|
|
pass
|
|
|
|
|
2021-04-11 12:44:44 +00:00
|
|
|
extras = extras or dict()
|
2025-01-18 09:51:10 +00:00
|
|
|
error_code = None
|
|
|
|
error_type = None
|
2021-03-21 08:40:25 +00:00
|
|
|
|
|
|
|
if msg is None:
|
|
|
|
msg = 'ACME request failed'
|
|
|
|
add_msg = ''
|
|
|
|
|
2021-04-11 12:44:44 +00:00
|
|
|
if info is not None:
|
|
|
|
url = info['url']
|
|
|
|
code = info['status']
|
|
|
|
extras['http_url'] = url
|
|
|
|
extras['http_status'] = code
|
2025-01-18 09:51:10 +00:00
|
|
|
error_code = code
|
2021-04-11 12:44:44 +00:00
|
|
|
if code is not None and code >= 400 and content_json is not None and 'type' in content_json:
|
2025-01-18 09:51:10 +00:00
|
|
|
error_type = content_json['type']
|
2021-04-11 12:44:44 +00:00
|
|
|
if 'status' in content_json and content_json['status'] != code:
|
2023-12-08 19:57:49 +00:00
|
|
|
code_msg = 'status {problem_code} (HTTP status: {http_code})'.format(
|
2022-10-31 20:33:27 +00:00
|
|
|
http_code=format_http_status(code), problem_code=content_json['status'])
|
2021-04-11 12:44:44 +00:00
|
|
|
else:
|
2023-12-08 19:57:49 +00:00
|
|
|
code_msg = 'status {problem_code}'.format(problem_code=format_http_status(code))
|
2023-12-04 20:34:51 +00:00
|
|
|
if code == -1 and info.get('msg'):
|
2023-12-08 19:57:49 +00:00
|
|
|
code_msg = 'error: {msg}'.format(msg=info['msg'])
|
2021-04-11 12:44:44 +00:00
|
|
|
subproblems = content_json.pop('subproblems', None)
|
|
|
|
add_msg = ' {problem}.'.format(problem=format_error_problem(content_json))
|
|
|
|
extras['problem'] = content_json
|
|
|
|
extras['subproblems'] = subproblems or []
|
|
|
|
if subproblems is not None:
|
|
|
|
add_msg = '{add_msg} Subproblems:'.format(add_msg=add_msg)
|
|
|
|
for index, problem in enumerate(subproblems):
|
|
|
|
add_msg = '{add_msg}\n({index}) {problem}.'.format(
|
|
|
|
add_msg=add_msg,
|
|
|
|
index=index,
|
|
|
|
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)),
|
|
|
|
)
|
2021-03-21 08:40:25 +00:00
|
|
|
else:
|
2023-12-08 19:57:49 +00:00
|
|
|
code_msg = 'HTTP status {code}'.format(code=format_http_status(code))
|
2023-12-04 20:34:51 +00:00
|
|
|
if code == -1 and info.get('msg'):
|
2023-12-08 19:57:49 +00:00
|
|
|
code_msg = 'error: {msg}'.format(msg=info['msg'])
|
2021-04-11 12:44:44 +00:00
|
|
|
if content_json is not None:
|
|
|
|
add_msg = ' The JSON error result: {content}'.format(content=content_json)
|
|
|
|
elif content is not None:
|
|
|
|
add_msg = ' The raw error result: {content}'.format(content=to_text(content))
|
2023-12-08 19:57:49 +00:00
|
|
|
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code_msg)
|
2021-04-11 12:44:44 +00:00
|
|
|
elif content_json is not None:
|
|
|
|
add_msg = ' The JSON result: {content}'.format(content=content_json)
|
|
|
|
elif content is not None:
|
|
|
|
add_msg = ' The raw result: {content}'.format(content=to_text(content))
|
2021-03-21 08:40:25 +00:00
|
|
|
|
|
|
|
super(ACMEProtocolException, self).__init__(
|
2021-04-11 12:44:44 +00:00
|
|
|
'{msg}.{add_msg}'.format(msg=msg, add_msg=add_msg),
|
2021-03-21 08:40:25 +00:00
|
|
|
**extras
|
|
|
|
)
|
|
|
|
self.problem = {}
|
|
|
|
self.subproblems = []
|
2025-01-18 09:51:10 +00:00
|
|
|
self.error_code = error_code
|
|
|
|
self.error_type = error_type
|
2021-03-21 08:40:25 +00:00
|
|
|
for k, v in extras.items():
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
|
|
|
|
|
|
|
class BackendException(ModuleFailException):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NetworkException(ModuleFailException):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class KeyParsingError(ModuleFailException):
|
|
|
|
pass
|