xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/commandsequence.py (revision cff95066cb05b52120fe4f3cd315b963de174813)
12d57dc69SVladimir Kotal#
22d57dc69SVladimir Kotal# CDDL HEADER START
32d57dc69SVladimir Kotal#
42d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the
52d57dc69SVladimir Kotal# Common Development and Distribution License (the "License").
62d57dc69SVladimir Kotal# You may not use this file except in compliance with the License.
72d57dc69SVladimir Kotal#
82d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific
92d57dc69SVladimir Kotal# language governing permissions and limitations under the License.
102d57dc69SVladimir Kotal#
112d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each
122d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt.
132d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the
142d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying
152d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner]
162d57dc69SVladimir Kotal#
172d57dc69SVladimir Kotal# CDDL HEADER END
182d57dc69SVladimir Kotal#
192d57dc69SVladimir Kotal
202d57dc69SVladimir Kotal#
2180df37bbSVladimir Kotal# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
222d57dc69SVladimir Kotal#
232d57dc69SVladimir Kotal
242d57dc69SVladimir Kotalimport logging
252d57dc69SVladimir Kotal
2696aeefc4SVladimir Kotalfrom requests.exceptions import RequestException
272d57dc69SVladimir Kotal
282d57dc69SVladimir Kotalfrom .command import Command
292d57dc69SVladimir Kotalfrom .exitvals import (
302d57dc69SVladimir Kotal    CONTINUE_EXITVAL,
312d57dc69SVladimir Kotal    SUCCESS_EXITVAL,
322d57dc69SVladimir Kotal    FAILURE_EXITVAL
332d57dc69SVladimir Kotal)
342d57dc69SVladimir Kotalfrom .restful import call_rest_api
35c41895f8SVladimir Kotalfrom .patterns import PROJECT_SUBST, COMMAND_PROPERTY, CALL_PROPERTY, URL_SUBST
362d57dc69SVladimir Kotalimport re
372d57dc69SVladimir Kotal
382d57dc69SVladimir Kotal
39*cff95066SVladimir KotalAPI_TIMEOUT_PROPERTY = "api_timeout"
40*cff95066SVladimir KotalASYNC_API_TIMEOUT_PROPERTY = "async_api_timeout"
41*cff95066SVladimir KotalHEADERS_PROPERTY = "headers"
42*cff95066SVladimir KotalMETHOD_PROPERTY = "method"
43*cff95066SVladimir KotalURI_PROPERTY = "uri"
44*cff95066SVladimir Kotal
45*cff95066SVladimir Kotal
4680df37bbSVladimir Kotalclass CommandConfigurationException(Exception):
4780df37bbSVladimir Kotal    pass
4880df37bbSVladimir Kotal
4980df37bbSVladimir Kotal
50*cff95066SVladimir Kotaldef check_call_config(call):
51*cff95066SVladimir Kotal    """
52*cff95066SVladimir Kotal    :param call: dictionary with API call configuration
53*cff95066SVladimir Kotal    """
54*cff95066SVladimir Kotal    if not isinstance(call, dict):
55*cff95066SVladimir Kotal        raise CommandConfigurationException("call value not a dictionary: {}".
56*cff95066SVladimir Kotal                                            format(call))
57*cff95066SVladimir Kotal
58*cff95066SVladimir Kotal    uri = call.get(URI_PROPERTY)
59*cff95066SVladimir Kotal    if not uri:
60*cff95066SVladimir Kotal        raise CommandConfigurationException(f"no '{URI_PROPERTY}' key present in {call}")
61*cff95066SVladimir Kotal
62*cff95066SVladimir Kotal    method = call.get(METHOD_PROPERTY)
63*cff95066SVladimir Kotal    if method and method.upper() not in ['GET', 'POST', 'PUT', 'DELETE']:
64*cff95066SVladimir Kotal        raise CommandConfigurationException(f"invalid HTTP method: {method}")
65*cff95066SVladimir Kotal
66*cff95066SVladimir Kotal    headers = call.get(HEADERS_PROPERTY)
67*cff95066SVladimir Kotal    if headers and not isinstance(headers, dict):
68*cff95066SVladimir Kotal        raise CommandConfigurationException("headers must be a dictionary")
69*cff95066SVladimir Kotal
70*cff95066SVladimir Kotal    call_timeout = call.get(API_TIMEOUT_PROPERTY)
71*cff95066SVladimir Kotal    if call_timeout:
72*cff95066SVladimir Kotal        try:
73*cff95066SVladimir Kotal            int(call_timeout)
74*cff95066SVladimir Kotal        except ValueError as exc:
75*cff95066SVladimir Kotal            raise CommandConfigurationException(f"{API_TIMEOUT_PROPERTY} not an integer", exc)
76*cff95066SVladimir Kotal
77*cff95066SVladimir Kotal    call_api_timeout = call.get(ASYNC_API_TIMEOUT_PROPERTY)
78*cff95066SVladimir Kotal    if call_api_timeout:
79*cff95066SVladimir Kotal        try:
80*cff95066SVladimir Kotal            int(call_api_timeout)
81*cff95066SVladimir Kotal        except ValueError as exc:
82*cff95066SVladimir Kotal            raise CommandConfigurationException(f"{ASYNC_API_TIMEOUT_PROPERTY} not an integer", exc)
83*cff95066SVladimir Kotal
84*cff95066SVladimir Kotal
8580df37bbSVladimir Kotaldef check_command_property(command):
8680df37bbSVladimir Kotal    """
8780df37bbSVladimir Kotal    Check if the 'commands' parameter of CommandSequenceBase() has the right structure
8880df37bbSVladimir Kotal    w.r.t. individual commands.
8980df37bbSVladimir Kotal    :param command: command element
9080df37bbSVladimir Kotal    """
91*cff95066SVladimir Kotal
9280df37bbSVladimir Kotal    if not isinstance(command, dict):
9377cacb4dSVladimir Kotal        raise CommandConfigurationException("command '{}' is not a dictionary".format(command))
9480df37bbSVladimir Kotal
9580df37bbSVladimir Kotal    command_value = command.get(COMMAND_PROPERTY)
96c41895f8SVladimir Kotal    call_value = command.get(CALL_PROPERTY)
97c41895f8SVladimir Kotal    if command_value is None and call_value is None:
98c41895f8SVladimir Kotal        raise CommandConfigurationException(f"command dictionary has unknown key: {command}")
9980df37bbSVladimir Kotal
100c41895f8SVladimir Kotal    if command_value and not isinstance(command_value, list):
10180df37bbSVladimir Kotal        raise CommandConfigurationException("command value not a list: {}".
10280df37bbSVladimir Kotal                                            format(command_value))
103*cff95066SVladimir Kotal    if call_value:
104*cff95066SVladimir Kotal        check_call_config(call_value)
105*cff95066SVladimir Kotal
106*cff95066SVladimir Kotal
107*cff95066SVladimir Kotalclass ApiCall:
108*cff95066SVladimir Kotal    """
109*cff95066SVladimir Kotal    Container class to store properties of API call.
110*cff95066SVladimir Kotal    """
111*cff95066SVladimir Kotal    def __init__(self, call_dict):
112*cff95066SVladimir Kotal        """
113*cff95066SVladimir Kotal        Initialize the object from a dictionary.
114*cff95066SVladimir Kotal        :param call_dict: dictionary
115*cff95066SVladimir Kotal        """
116*cff95066SVladimir Kotal        if not isinstance(call_dict, dict):
117*cff95066SVladimir Kotal            raise CommandConfigurationException(f"not a dictionary: {call_dict}")
118*cff95066SVladimir Kotal
119*cff95066SVladimir Kotal        self.uri = call_dict.get(URI_PROPERTY)
120*cff95066SVladimir Kotal        self.method = call_dict.get(METHOD_PROPERTY)
121*cff95066SVladimir Kotal        if not self.method:
122*cff95066SVladimir Kotal            self.method = "GET"
123*cff95066SVladimir Kotal
124*cff95066SVladimir Kotal        self.data = call_dict.get("data")
125*cff95066SVladimir Kotal
126*cff95066SVladimir Kotal        self.headers = call_dict.get(HEADERS_PROPERTY)
127*cff95066SVladimir Kotal        if not self.headers:
128*cff95066SVladimir Kotal            self.headers = {}
129*cff95066SVladimir Kotal
130*cff95066SVladimir Kotal        self.api_timeout = None
131*cff95066SVladimir Kotal        call_timeout = call_dict.get(API_TIMEOUT_PROPERTY)
132*cff95066SVladimir Kotal        if call_timeout:
133*cff95066SVladimir Kotal            self.api_timeout = call_timeout
134*cff95066SVladimir Kotal
135*cff95066SVladimir Kotal        self.async_api_timeout = None
136*cff95066SVladimir Kotal        call_api_timeout = call_dict.get(ASYNC_API_TIMEOUT_PROPERTY)
137*cff95066SVladimir Kotal        if call_api_timeout:
138*cff95066SVladimir Kotal            self.async_api_timeout = call_api_timeout
13980df37bbSVladimir Kotal
14080df37bbSVladimir Kotal
1412d57dc69SVladimir Kotalclass CommandSequenceBase:
1422d57dc69SVladimir Kotal    """
1432d57dc69SVladimir Kotal    Wrap the run of a set of Command instances.
1442d57dc69SVladimir Kotal
1452d57dc69SVladimir Kotal    This class intentionally does not contain any logging
1462d57dc69SVladimir Kotal    so that it can be passed through Pool.map().
1472d57dc69SVladimir Kotal    """
1482d57dc69SVladimir Kotal
1492d57dc69SVladimir Kotal    def __init__(self, name, commands, loglevel=logging.INFO, cleanup=None,
150732be1c2SVladimir Kotal                 driveon=False, url=None, env=None, http_headers=None,
1514f5a8888SVladimir Kotal                 api_timeout=None, async_api_timeout=None):
1522d57dc69SVladimir Kotal        self.name = name
15380df37bbSVladimir Kotal
15480df37bbSVladimir Kotal        if commands is None:
15580df37bbSVladimir Kotal            raise CommandConfigurationException("commands is None")
15680df37bbSVladimir Kotal        if not isinstance(commands, list):
15780df37bbSVladimir Kotal            raise CommandConfigurationException("commands is not a list")
1582d57dc69SVladimir Kotal        self.commands = commands
15980df37bbSVladimir Kotal        for command in self.commands:
16080df37bbSVladimir Kotal            check_command_property(command)
16180df37bbSVladimir Kotal
1622d57dc69SVladimir Kotal        self.failed = False
1632d57dc69SVladimir Kotal        self.retcodes = {}
1642d57dc69SVladimir Kotal        self.outputs = {}
1652d57dc69SVladimir Kotal
16680df37bbSVladimir Kotal        if cleanup and not isinstance(cleanup, list):
16780df37bbSVladimir Kotal            raise CommandConfigurationException("cleanup is not a list of commands")
1682d57dc69SVladimir Kotal        self.cleanup = cleanup
16980df37bbSVladimir Kotal        if self.cleanup:
17080df37bbSVladimir Kotal            for command in self.cleanup:
17180df37bbSVladimir Kotal                check_command_property(command)
17280df37bbSVladimir Kotal
1732d57dc69SVladimir Kotal        self.loglevel = loglevel
1742d57dc69SVladimir Kotal        self.driveon = driveon
175b2d29daeSVladimir Kotal        self.env = env
17689229afdSVladimir Kotal        self.http_headers = http_headers
177732be1c2SVladimir Kotal        self.api_timeout = api_timeout
1784f5a8888SVladimir Kotal        self.async_api_timeout = async_api_timeout
1792d57dc69SVladimir Kotal
1802d97c0a2SVladimir Kotal        self.url = url
1812d97c0a2SVladimir Kotal
1822d57dc69SVladimir Kotal    def __str__(self):
1832d57dc69SVladimir Kotal        return str(self.name)
1842d57dc69SVladimir Kotal
1852d57dc69SVladimir Kotal    def get_cmd_output(self, cmd, indent=""):
186b2d29daeSVladimir Kotal        """
187b2d29daeSVladimir Kotal        :param cmd: command
188b2d29daeSVladimir Kotal        :param indent: prefix for each line
189b2d29daeSVladimir Kotal        :return: command output as string
190b2d29daeSVladimir Kotal        """
191b2d29daeSVladimir Kotal
19280df37bbSVladimir Kotal        str_out = ""
1932d57dc69SVladimir Kotal        for line in self.outputs.get(cmd, []):
19480df37bbSVladimir Kotal            str_out += '{}{}'.format(indent, line)
1952d57dc69SVladimir Kotal
19680df37bbSVladimir Kotal        return str_out
1972d57dc69SVladimir Kotal
1982d57dc69SVladimir Kotal    def fill(self, retcodes, outputs, failed):
1992d57dc69SVladimir Kotal        self.retcodes = retcodes
2002d57dc69SVladimir Kotal        self.outputs = outputs
2012d57dc69SVladimir Kotal        self.failed = failed
2022d57dc69SVladimir Kotal
2032d57dc69SVladimir Kotal
2042d57dc69SVladimir Kotalclass CommandSequence(CommandSequenceBase):
2052d57dc69SVladimir Kotal    re_program = re.compile('ERROR[:]*\\s+')
2062d57dc69SVladimir Kotal
2072d57dc69SVladimir Kotal    def __init__(self, base):
2082d57dc69SVladimir Kotal        super().__init__(base.name, base.commands, loglevel=base.loglevel,
2092d97c0a2SVladimir Kotal                         cleanup=base.cleanup, driveon=base.driveon,
210b369c884SVladimir Kotal                         url=base.url, env=base.env,
211732be1c2SVladimir Kotal                         http_headers=base.http_headers,
2124f5a8888SVladimir Kotal                         api_timeout=base.api_timeout,
2134f5a8888SVladimir Kotal                         async_api_timeout=base.async_api_timeout)
2142d57dc69SVladimir Kotal
2152d57dc69SVladimir Kotal        self.logger = logging.getLogger(__name__)
2162d57dc69SVladimir Kotal        self.logger.setLevel(base.loglevel)
2172d57dc69SVladimir Kotal
2182d57dc69SVladimir Kotal    def run_command(self, cmd):
2192d57dc69SVladimir Kotal        """
2202d57dc69SVladimir Kotal        Execute a command and return its return code.
2212d57dc69SVladimir Kotal        """
2222d57dc69SVladimir Kotal        cmd.execute()
2232d57dc69SVladimir Kotal        self.retcodes[str(cmd)] = cmd.getretcode()
2242d57dc69SVladimir Kotal        self.outputs[str(cmd)] = cmd.getoutput()
2252d57dc69SVladimir Kotal
2262d57dc69SVladimir Kotal        return cmd.getretcode()
2272d57dc69SVladimir Kotal
2282d57dc69SVladimir Kotal    def run(self):
2292d57dc69SVladimir Kotal        """
2302d57dc69SVladimir Kotal        Run the sequence of commands and capture their output and return code.
2312d57dc69SVladimir Kotal        First command that returns code other than 0 terminates the sequence.
2322d57dc69SVladimir Kotal        If the command has return code 2, the sequence will be terminated
2332d57dc69SVladimir Kotal        however it will not be treated as error (unless the 'driveon' parameter
2342d57dc69SVladimir Kotal        is True).
2352d57dc69SVladimir Kotal
2362d57dc69SVladimir Kotal        If a command contains PROJECT_SUBST pattern, it will be replaced
2372d57dc69SVladimir Kotal        by project name, otherwise project name will be appended to the
2382d57dc69SVladimir Kotal        argument list of the command.
2392d57dc69SVladimir Kotal
240519c2abbSVladimir Kotal        Any command entry that is a URI, will be used to submit REST API
2412d57dc69SVladimir Kotal        request.
2422d57dc69SVladimir Kotal        """
2432d57dc69SVladimir Kotal
2442d57dc69SVladimir Kotal        for command in self.commands:
245c41895f8SVladimir Kotal            if command.get(CALL_PROPERTY):
2462d57dc69SVladimir Kotal                try:
247*cff95066SVladimir Kotal                    call_rest_api(ApiCall(command.get(CALL_PROPERTY)),
248c41895f8SVladimir Kotal                                  {PROJECT_SUBST: self.name,
24989229afdSVladimir Kotal                                   URL_SUBST: self.url},
2504f5a8888SVladimir Kotal                                  self.http_headers,
2514f5a8888SVladimir Kotal                                  self.api_timeout,
2524f5a8888SVladimir Kotal                                  self.async_api_timeout)
25396aeefc4SVladimir Kotal                except RequestException as e:
254c41895f8SVladimir Kotal                    self.logger.error("REST API call {} failed: {}".
2552d57dc69SVladimir Kotal                                      format(command, e))
2562d57dc69SVladimir Kotal                    self.failed = True
2572d57dc69SVladimir Kotal                    self.retcodes[str(command)] = FAILURE_EXITVAL
2582d57dc69SVladimir Kotal
2592d57dc69SVladimir Kotal                    break
260c41895f8SVladimir Kotal            elif command.get(COMMAND_PROPERTY):
2612d57dc69SVladimir Kotal                command_args = command.get(COMMAND_PROPERTY)
2622d57dc69SVladimir Kotal                command = Command(command_args,
2632d57dc69SVladimir Kotal                                  env_vars=command.get("env"),
264b2d29daeSVladimir Kotal                                  logger=self.logger,
2652d57dc69SVladimir Kotal                                  resource_limits=command.get("limits"),
2662d97c0a2SVladimir Kotal                                  args_subst={PROJECT_SUBST: self.name,
2672d97c0a2SVladimir Kotal                                              URL_SUBST: self.url},
2682d57dc69SVladimir Kotal                                  args_append=[self.name], excl_subst=True)
269c41895f8SVladimir Kotal                ret_code = self.run_command(command)
2702d57dc69SVladimir Kotal
2712d57dc69SVladimir Kotal                # If a command exits with non-zero return code,
2722d57dc69SVladimir Kotal                # terminate the sequence of commands.
273c41895f8SVladimir Kotal                if ret_code != SUCCESS_EXITVAL:
274c41895f8SVladimir Kotal                    if ret_code == CONTINUE_EXITVAL:
2752d57dc69SVladimir Kotal                        if not self.driveon:
2762d57dc69SVladimir Kotal                            self.logger.debug("command '{}' for project {} "
2772d57dc69SVladimir Kotal                                              "requested break".
2782d57dc69SVladimir Kotal                                              format(command, self.name))
2792d57dc69SVladimir Kotal                            self.run_cleanup()
2802d57dc69SVladimir Kotal                        else:
2812d57dc69SVladimir Kotal                            self.logger.debug("command '{}' for project {} "
2822d57dc69SVladimir Kotal                                              "requested break however "
2832d57dc69SVladimir Kotal                                              "the 'driveon' option is set "
2842d57dc69SVladimir Kotal                                              "so driving on.".
2852d57dc69SVladimir Kotal                                              format(command, self.name))
2862d57dc69SVladimir Kotal                            continue
2872d57dc69SVladimir Kotal                    else:
288923e5a3aSVladimir Kotal                        self.logger.error("command '{}' for project {} failed "
289923e5a3aSVladimir Kotal                                          "with code {}, breaking".
290c41895f8SVladimir Kotal                                          format(command, self.name, ret_code))
2912d57dc69SVladimir Kotal                        self.failed = True
2922d57dc69SVladimir Kotal                        self.run_cleanup()
2932d57dc69SVladimir Kotal
2942d57dc69SVladimir Kotal                    break
295c41895f8SVladimir Kotal            else:
296c41895f8SVladimir Kotal                raise Exception(f"unknown command: {command}")
2972d57dc69SVladimir Kotal
2982d57dc69SVladimir Kotal    def run_cleanup(self):
2992d57dc69SVladimir Kotal        """
3002d57dc69SVladimir Kotal        Call cleanup sequence in case the command sequence failed
3012d57dc69SVladimir Kotal        or termination was requested.
3022d57dc69SVladimir Kotal        """
3032d57dc69SVladimir Kotal        if self.cleanup is None:
3042d57dc69SVladimir Kotal            return
3052d57dc69SVladimir Kotal
3062d57dc69SVladimir Kotal        for cleanup_cmd in self.cleanup:
307c41895f8SVladimir Kotal            if cleanup_cmd.get(CALL_PROPERTY):
3082d57dc69SVladimir Kotal                try:
309*cff95066SVladimir Kotal                    call_rest_api(ApiCall(cleanup_cmd.get(CALL_PROPERTY)),
310c41895f8SVladimir Kotal                                  {PROJECT_SUBST: self.name,
311b369c884SVladimir Kotal                                   URL_SUBST: self.url},
3124f5a8888SVladimir Kotal                                  self.http_headers,
3134f5a8888SVladimir Kotal                                  self.api_timeout,
3144f5a8888SVladimir Kotal                                  self.async_api_timeout)
31596aeefc4SVladimir Kotal                except RequestException as e:
316c41895f8SVladimir Kotal                    self.logger.error("API call {} failed: {}".
3172d57dc69SVladimir Kotal                                      format(cleanup_cmd, e))
318c41895f8SVladimir Kotal            elif cleanup_cmd.get(COMMAND_PROPERTY):
3192d57dc69SVladimir Kotal                command_args = cleanup_cmd.get(COMMAND_PROPERTY)
3202d57dc69SVladimir Kotal                self.logger.debug("Running cleanup command '{}'".
3212d57dc69SVladimir Kotal                                  format(command_args))
3222d57dc69SVladimir Kotal                cmd = Command(command_args,
323b2d29daeSVladimir Kotal                              logger=self.logger,
3242d97c0a2SVladimir Kotal                              args_subst={PROJECT_SUBST: self.name,
3252d97c0a2SVladimir Kotal                                          URL_SUBST: self.url},
3262d57dc69SVladimir Kotal                              args_append=[self.name], excl_subst=True)
3272d57dc69SVladimir Kotal                cmd.execute()
3282d57dc69SVladimir Kotal                if cmd.getretcode() != SUCCESS_EXITVAL:
3292d57dc69SVladimir Kotal                    self.logger.error("cleanup command '{}' failed with "
3302d57dc69SVladimir Kotal                                      "code {}".
3312d57dc69SVladimir Kotal                                      format(cmd.cmd, cmd.getretcode()))
3322d57dc69SVladimir Kotal                    self.logger.info('output: {}'.format(cmd.getoutputstr()))
333c41895f8SVladimir Kotal            else:
334c41895f8SVladimir Kotal                raise Exception(f"unknown type of action: {cleanup_cmd}")
3352d57dc69SVladimir Kotal
336b2d29daeSVladimir Kotal    def print_outputs(self, logger, loglevel=logging.INFO, lines=False):
337b2d29daeSVladimir Kotal        """
338b2d29daeSVladimir Kotal        Print command outputs.
339b2d29daeSVladimir Kotal        """
340b2d29daeSVladimir Kotal
341b2d29daeSVladimir Kotal        logger.debug("Output for project '{}':".format(self.name))
342b2d29daeSVladimir Kotal        for cmd in self.outputs.keys():
343b2d29daeSVladimir Kotal            if self.outputs[cmd] and len(self.outputs[cmd]) > 0:
344b2d29daeSVladimir Kotal                if lines:
345b2d29daeSVladimir Kotal                    logger.log(loglevel, "Output from '{}':".format(cmd))
346b2d29daeSVladimir Kotal                    logger.log(loglevel, '{}'.format(self.get_cmd_output(cmd)))
347b2d29daeSVladimir Kotal                else:
348b2d29daeSVladimir Kotal                    logger.log(loglevel, "'{}': {}".
349b2d29daeSVladimir Kotal                               format(cmd, self.outputs[cmd]))
350b2d29daeSVladimir Kotal
3512d57dc69SVladimir Kotal    def check(self, ignore_errors):
3522d57dc69SVladimir Kotal        """
3532d57dc69SVladimir Kotal        Check the output of the commands and perform logging.
3542d57dc69SVladimir Kotal
3555a30ebc3SVladimir Kotal        Return SUCCESS_EXITVAL on success, 1 if error was detected.
3562d57dc69SVladimir Kotal        """
3572d57dc69SVladimir Kotal
3582d57dc69SVladimir Kotal        ret = SUCCESS_EXITVAL
359b2d29daeSVladimir Kotal        self.print_outputs(self.logger, loglevel=logging.DEBUG)
3602d57dc69SVladimir Kotal
3615a30ebc3SVladimir Kotal        if ignore_errors and self.name in ignore_errors:
3622d57dc69SVladimir Kotal            self.logger.debug("errors of project '{}' ignored".
3632d57dc69SVladimir Kotal                              format(self.name))
3642d57dc69SVladimir Kotal            return
3652d57dc69SVladimir Kotal
3662d57dc69SVladimir Kotal        self.logger.debug("retcodes = {}".format(self.retcodes))
3672d57dc69SVladimir Kotal        if any(rv != SUCCESS_EXITVAL and rv != CONTINUE_EXITVAL
3682d57dc69SVladimir Kotal               for rv in self.retcodes.values()):
3692d57dc69SVladimir Kotal            ret = 1
3702d57dc69SVladimir Kotal            self.logger.error("processing of project '{}' failed".
3712d57dc69SVladimir Kotal                              format(self))
3722d57dc69SVladimir Kotal            indent = "  "
3732d57dc69SVladimir Kotal            self.logger.error("{}failed commands:".format(indent))
3742d57dc69SVladimir Kotal            failed_cmds = {k: v for k, v in
3752d57dc69SVladimir Kotal                           self.retcodes.items() if v != SUCCESS_EXITVAL}
3762d57dc69SVladimir Kotal            indent = "    "
3772d57dc69SVladimir Kotal            for cmd in failed_cmds.keys():
3782d57dc69SVladimir Kotal                self.logger.error("{}'{}': {}".
3792d57dc69SVladimir Kotal                                  format(indent, cmd, failed_cmds[cmd]))
3802d57dc69SVladimir Kotal                out = self.get_cmd_output(cmd,
3812d57dc69SVladimir Kotal                                          indent=indent + "  ")
3822d57dc69SVladimir Kotal                if out:
3832d57dc69SVladimir Kotal                    self.logger.error(out)
3842d57dc69SVladimir Kotal            self.logger.error("")
3852d57dc69SVladimir Kotal
3862d57dc69SVladimir Kotal        errored_cmds = {k: v for k, v in self.outputs.items()
3872d57dc69SVladimir Kotal                        if self.re_program.match(str(v))}
3882d57dc69SVladimir Kotal        if len(errored_cmds) > 0:
3892d57dc69SVladimir Kotal            ret = 1
3902d57dc69SVladimir Kotal            self.logger.error("Command output in project '{}'"
3912d57dc69SVladimir Kotal                              " contains errors:".format(self.name))
3922d57dc69SVladimir Kotal            indent = "  "
3932d57dc69SVladimir Kotal            for cmd in errored_cmds.keys():
3942d57dc69SVladimir Kotal                self.logger.error("{}{}".format(indent, cmd))
3952d57dc69SVladimir Kotal                out = self.get_cmd_output(cmd,
3962d57dc69SVladimir Kotal                                          indent=indent + "  ")
3972d57dc69SVladimir Kotal                if out:
3982d57dc69SVladimir Kotal                    self.logger.error(out)
3992d57dc69SVladimir Kotal                self.logger.error("")
4002d57dc69SVladimir Kotal
4012d57dc69SVladimir Kotal        return ret
402