Example #1
0
def _PrintUrl(service_name, events_port, stop):
    """Read the local url of a service from the event stream and print it.

  Read the event stream api and find the portForward events. Print the local
  url as determined from the portFoward events. This function will continuously
  listen to the event stream and print out all local urls until eitherthe event
  stream connection closes or the stop event is set.

  Args:
    service_name: Name of the service.
    events_port: Port number of the skaffold events stream api.
    stop: threading.Event event.
  """
    try:
        with contextlib.closing(_OpenEventStreamRetry(events_port,
                                                      stop)) as response:
            for port in GetServiceLocalPort(response, service_name):
                # If the thread has been signaled to stop, don't print out the url
                if stop.is_set():
                    return
                con = console_attr.GetConsoleAttr()
                msg = 'Service URL: {bold}{url}{normal}'.format(
                    bold=con.GetFontCode(bold=True),
                    url='http://localhost:%s/' % port,
                    normal=con.GetFontCode())
                # Sleep for a second to make sure the URL is printed below the start
                # up logs printed by skaffold.'
                stop.wait(1)
                log.status.Print(con.Colorize(msg, color='blue'))
    except StopThreadError:
        return
Example #2
0
 def __init__(self, message, autotick, detail_message_callback, tick_delay,
              interruptable, aborted_message, spinner_override_message):
   self._stream = sys.stderr
   if message is None:
     self._spinner_only = True
     self._message = ''
     self._prefix = ''
   else:
     self._spinner_only = False
     self._message = message
     self._prefix = message + '...'
   self._detail_message_callback = detail_message_callback
   self.spinner_override_message = spinner_override_message
   self._ticks = 0
   self._done = False
   self._lock = threading.Lock()
   self._tick_delay = tick_delay
   self._ticker = None
   console_width = console_attr.ConsoleAttr().GetTermSize()[0]
   if console_width < 0:
     # This can happen if we're on a pseudo-TTY. Set it to 0 and also
     # turn off output to prevent hanging.
     console_width = 0
   self._output_enabled = log.IsUserOutputEnabled() and console_width != 0
   # Don't bother autoticking if we aren't going to print anything.
   self.__autotick = autotick and self._output_enabled
   self._interruptable = interruptable
   self._aborted_message = aborted_message
   self._old_signal_handler = None
   self._symbols = console_attr.GetConsoleAttr().GetProgressTrackerSymbols()
Example #3
0
    def _Write(self, msg, styled_msg):
        """Just a helper so we don't have to double encode from Print and write.

    Args:
      msg: A text string that only has characters that are safe to encode with
        utf-8.
      styled_msg: A text string with the same properties as msg but also
        contains ANSI control sequences.
    """
        # The log file always uses utf-8 encoding, just give it the string.
        self.__logger.info(msg)

        if self.__filter.enabled:
            # Make sure the string is safe to print in the console. The console might
            # have a more restrictive encoding than utf-8.
            stream_encoding = console_attr.GetConsoleAttr().GetEncoding()
            stream_msg = console_attr.SafeText(styled_msg,
                                               encoding=stream_encoding,
                                               escape=False)
            if six.PY2:
                # Encode to byte strings for output only on Python 2.
                stream_msg = styled_msg.encode(stream_encoding or 'utf8',
                                               'replace')
            self.__stream_wrapper.stream.write(stream_msg)
            if self.__always_flush:
                self.flush()
Example #4
0
  def __init__(self, *args, **kwargs):
    """Creates a new TablePrinter."""
    self._rows = []
    super(TablePrinter, self).__init__(*args, by_columns=True,
                                       non_empty_projection_required=True,
                                       **kwargs)
    encoding = None
    for name in ['ascii', 'utf8', 'win']:
      if name in self.attributes:
        encoding = name
        break
    if not self._console_attr:
      self._console_attr = console_attr.GetConsoleAttr(encoding=encoding)
    self._csi = self._console_attr.GetControlSequenceIndicator()
    self._page_count = 0

    # Check for subformat columns.
    self._optional = False
    self._subformats = []
    self._has_subprinters = False
    has_subformats = False
    self._aggregate = True
    if self.column_attributes:
      for col in self.column_attributes.Columns():
        if col.attribute.subformat or col.attribute.hidden:
          has_subformats = True
        else:
          self._aggregate = False
        if col.attribute.optional:
          self._optional = True
        if col.attribute.wrap:
          self._wrap = True
      defaults = resource_projection_spec.ProjectionSpec(
          symbols=self.column_attributes.symbols)
      index = 0
      for col in self.column_attributes.Columns():
        if col.attribute.subformat:
          # This initializes a nested Printer to a string stream.
          out = self._out if self._aggregate else io.StringIO()
          wrap = None
          printer = self.Printer(col.attribute.subformat, out=out,
                                 console_attr=self._console_attr,
                                 defaults=defaults)
          self._has_subprinters = True
        else:
          out = None
          printer = None
          wrap = col.attribute.wrap
        self._subformats.append(
            SubFormat(index, col.attribute.hidden, printer, out, wrap))
        index += 1
    self._visible = None
    if not has_subformats:
      self._subformats = None
      self._aggregate = False
    elif self._subformats and not self._aggregate:
      self._visible = []
      for subformat in self._subformats:
        if not subformat.hidden and not subformat.printer:
          self._visible.append(subformat.index)
Example #5
0
  def __init__(self, message, stages, success_message, failure_message,
               autotick, tick_delay, interruptable, aborted_message,
               tracker_id, done_message_callback):
    super(_BaseStagedProgressTracker, self).__init__(stages)
    self._stream = sys.stderr
    # TODO(b/111637901): Support detailed message callback when true multiline
    # support is available.
    self._message = message
    self._success_message = success_message
    self._failure_message = failure_message
    self._aborted_message = aborted_message
    self._done_message_callback = done_message_callback
    self._tracker_id = tracker_id
    console_width = console_attr.ConsoleAttr().GetTermSize()[0]
    if console_width < 0:
      # This can happen if we're on a pseudo-TTY. Set it to 0 and also
      # turn off output to prevent hanging.
      console_width = 0
    self._output_enabled = log.IsUserOutputEnabled() and console_width != 0
    # Don't bother autoticking if we aren't going to print anything.
    self.__autotick = autotick and self._output_enabled
    self._interruptable = interruptable
    self._tick_delay = tick_delay

    self._symbols = console_attr.GetConsoleAttr().GetProgressTrackerSymbols()
    self._done = False
    self._exception_is_uncaught = True
    self._ticks = 0
    self._ticker = None
    self._running_stages = set()
    self._completed_stages = []
    self._lock = threading.Lock()
Example #6
0
 def __init__(self,
              height=0,
              encoding='utf8',
              compact=True,
              *args,
              **kwargs):
     super(TokenRenderer, self).__init__(*args, **kwargs)
     self._attr = console_attr.GetConsoleAttr(encoding=encoding)
     self._csi = self.CSI
     self._attr._csi = self._csi  # pylint: disable=protected-access
     self._blank = True
     self._bullet = self._attr.GetBullets()
     self._compact = compact
     self._fill = 0
     self._current_token_type = Token.Markdown.Normal
     self._height = height
     self._ignore_paragraph = False
     self._ignore_width = False
     self._indent = [self.Indent(compact)]
     self._level = 0
     self._lines = []
     self._table = False
     self._tokens = []
     self._truncated = False
     self._rows = []
Example #7
0
  def __init__(self, contents, out=None, prompt=None):
    """Constructor.

    Args:
      contents: The entire contents of the text lines to page.
      out: The output stream, log.out (effectively) if None.
      prompt: The page break prompt, a defalt prompt is used if None..
    """
    self._contents = contents
    self._out = out or sys.stdout
    self._search_pattern = None
    self._search_direction = None

    # prev_pos, prev_next values to force reprint
    self.prev_pos, self.prev_nxt = self.PREV_POS_NXT_REPRINT
    # Initialize the console attributes.
    self._attr = console_attr.GetConsoleAttr()
    self._width, self._height = self._attr.GetTermSize()

    # Initialize the prompt and the prompt clear string.
    if not prompt:
      prompt = '{bold}--({{percent}}%)--{normal}'.format(
          bold=self._attr.GetFontCode(bold=True),
          normal=self._attr.GetFontCode())
    self._clear = '\r{0}\r'.format(' ' * (self._attr.DisplayWidth(prompt) - 6))
    self._prompt = prompt

    # Initialize a list of lines with long lines split into separate display
    # lines.
    self._lines = []
    for line in contents.splitlines():
      self._lines += self._attr.SplitLine(line, self._width)
Example #8
0
def MakeUserAgentString(cmd_path=None):
    """Return a user-agent string for this request.

  Contains 'gcloud' in addition to several other product IDs used for tracing in
  metrics reporting.

  Args:
    cmd_path: str representing the current command for tracing.

  Returns:
    str, User Agent string.
  """
    return (
        'gcloud/{version}'
        ' command/{cmd}'
        ' invocation-id/{inv_id}'
        ' environment/{environment}'
        ' environment-version/{env_version}'
        ' interactive/{is_interactive}'
        ' from-script/{from_script}'
        ' python/{py_version}'
        ' term/{term}'
        ' {ua_fragment}').format(
            version=config.CLOUD_SDK_VERSION.replace(' ', '_'),
            cmd=(cmd_path or properties.VALUES.metrics.command_name.Get()),
            inv_id=uuid.uuid4().hex,
            environment=properties.GetMetricsEnvironment(),
            env_version=properties.VALUES.metrics.environment_version.Get(),
            is_interactive=console_io.IsInteractive(error=True,
                                                    heuristic=True),
            py_version=platform.python_version(),
            ua_fragment=platforms.Platform.Current().UserAgentFragment(),
            from_script=console_io.IsRunFromShellScript(),
            term=console_attr.GetConsoleAttr().GetTermIdentifier())
 def __init__(self, message, autotick, detail_message_callback, tick_delay,
              interruptable, aborted_message):
     if message is None:
         self._spinner_only = True
         self._message = ''
         self._prefix = ''
         self._suffix = ''
     else:
         self._spinner_only = False
         self._message = message
         self._prefix = message + '...'
         self._suffix = 'done.'
     self._stream = sys.stderr
     self._ticks = 0
     self._done = False
     self._lock = threading.Lock()
     self._detail_message_callback = detail_message_callback
     self._multi_line = False
     self._last_display_message = ''
     self._tick_delay = tick_delay
     self._is_tty = console_io.IsInteractive(error=True)
     self._ticker = None
     self._output_enabled = log.IsUserOutputEnabled()
     # Don't bother autoticking if we aren't going to print anything.
     self.__autotick = autotick and self._output_enabled
     self._interruptable = interruptable
     self._aborted_message = aborted_message
     self._old_signal_handler = None
     self._symbols = console_attr.GetConsoleAttr(
     ).GetProgressTrackerSymbols()
  def CallToAction(self, record):
    """Call to action to configure IP for the domain.

    Args:
      record: dict, the integration.

    Returns:
      A formatted string of the call to action message,
      or None if no call to action is required.
    """
    resource_config = record.get('config')
    resource_status = record.get('status')
    if not resource_status or not resource_status:
      return None

    domain = resource_config.get('router', {}).get('domain')
    resource_components = resource_status.get('resourceComponentStatuses', {})
    ssl_status = self._GetSSLStatus(resource_components)
    ip = resource_status.get('routerDetails', {}).get('ipAddress')
    if domain and ip and ssl_status != states.ACTIVE:
      domain_padding = len(domain) - len('NAME')
      padding_string = ' ' * domain_padding
      con = console_attr.GetConsoleAttr()
      return ('{0} To complete the process, please ensure the following '
              'DNS records are configured for the domain "{1}":\n'
              '    NAME{2}  TTL   TYPE  DATA\n'
              '    {1}  3600  A     {3}\n'
              'It can take up to an hour for the certificate to be provisioned.'
              .format(con.Colorize('!', 'yellow'), domain, padding_string, ip))
    return None
Example #11
0
    def __init__(self, *args, **kwargs):
        """Creates a new TablePrinter."""
        self._rows = []
        super(TablePrinter, self).__init__(*args,
                                           by_columns=True,
                                           non_empty_projection_required=True,
                                           **kwargs)
        encoding = None
        for name in ['ascii', 'utf8', 'win']:
            if name in self.attributes:
                encoding = name
                break
        if not self._console_attr:
            self._console_attr = console_attr.GetConsoleAttr(encoding=encoding,
                                                             out=self._out)
        self._rows_per_page = self.attributes.get('page', 0)
        if self._rows_per_page:
            log.warn('The [page=N] printer attribute is deprecated. '
                     'Use the --page-size=N flag instead.')
        self._page_count = 0

        # Check for subformat columns.
        self._optional = False
        self._subformats = []
        self._has_subprinters = False
        has_subformats = False
        self._aggregate = True
        if self.column_attributes:
            for col in self.column_attributes.Columns():
                if col.attribute.subformat or col.attribute.hidden:
                    has_subformats = True
                else:
                    self._aggregate = False
                if col.attribute.optional:
                    self._optional = True
            index = 0
            for col in self.column_attributes.Columns():
                if col.attribute.subformat:
                    # This initializes a nested Printer to a string stream.
                    out = self._out if self._aggregate else StringIO.StringIO()
                    printer = self.Printer(col.attribute.subformat,
                                           out=out,
                                           console_attr=self._console_attr,
                                           defaults=self.column_attributes)
                    self._has_subprinters = True
                else:
                    out = None
                    printer = None
                self._subformats.append(
                    SubFormat(index, col.attribute.hidden, printer, out))
                index += 1
        self._visible = None
        if not has_subformats:
            self._subformats = None
            self._aggregate = False
        elif self._subformats and not self._aggregate:
            self._visible = []
            for subformat in self._subformats:
                if not subformat.hidden and not subformat.printer:
                    self._visible.append(subformat.index)
Example #12
0
 def _GetReadyMessage(self, record):
   if record.ready_condition and record.ready_condition['message']:
     symbol, color = record.ReadySymbolAndColor()
     return console_attr.GetConsoleAttr().Colorize(
         textwrap.fill('{} {}'.format(
             symbol, record.ready_condition['message']), 100), color)
   else:
     return ''
Example #13
0
 def __init__(self, out_stream):
     super(_ConsoleFormatter, self).__init__()
     use_color = not properties.VALUES.core.disable_color.GetBool(
         validate=False)
     use_color &= out_stream.isatty()
     use_color &= console_attr.GetConsoleAttr().SupportsAnsi()
     self._formats = (_ConsoleFormatter.COLOR_FORMATS
                      if use_color else _ConsoleFormatter.FORMATS)
Example #14
0
 def __init__(self, ofile, timeout, tick_delay, background_ttl, autotick):
   self._ofile = ofile or self._GetStream()
   self._timeout = timeout
   self._tick_delay = tick_delay
   self.__autotick = autotick
   self._background_ttl = background_ttl
   self._ticks = 0
   self._symbols = console_attr.GetConsoleAttr().GetProgressTrackerSymbols()
Example #15
0
    def testConsoleAttrStateResetTermSize(self):
        console_attr.ConsoleAttr._CONSOLE_ATTR_STATE = None

        console_attr.GetConsoleAttr()
        self.assertEqual(self._get_term_size_windows_mock.call_count, 1)
        console_attr.GetConsoleAttr()
        self.assertEqual(self._get_term_size_windows_mock.call_count, 1)

        console_attr.GetConsoleAttr(encoding='mock')
        self.assertEqual(self._get_term_size_windows_mock.call_count, 2)
        console_attr.GetConsoleAttr()
        self.assertEqual(self._get_term_size_windows_mock.call_count, 2)

        console_attr.ResetConsoleAttr()
        self.assertEqual(self._get_term_size_windows_mock.call_count, 3)
        console_attr.GetConsoleAttr()
        self.assertEqual(self._get_term_size_windows_mock.call_count, 3)
Example #16
0
 def __init__(self, set_spinner):
     self._done_loading = False
     self._spin_marks = console_attr.GetConsoleAttr()\
         .GetProgressTrackerSymbols().spin_marks
     self._ticker = None
     self._ticker_index = 0
     self._ticker_length = len(self._spin_marks)
     self._set_spinner = set_spinner
 def _GetRevisionHeader(self, record):
     header = ''
     if record.status is None:
         header = 'Unknown revision'
     else:
         header = 'Revision {}'.format(
             record.status.latestCreatedRevisionName)
     return console_attr.GetConsoleAttr().Emphasize(header)
Example #18
0
 def _GetServiceHeader(self, record):
     con = console_attr.GetConsoleAttr()
     status = con.Colorize(*record.ReadySymbolAndColor())
     try:
         place = 'region ' + record.region
     except KeyError:
         place = 'namespace ' + record.namespace
     return con.Emphasize('{} Service {} in {}'.format(
         status, record.name, place))
Example #19
0
def CheckRedirectionPermission(project):
    con = console_attr.GetConsoleAttr()
    authorized = ar_requests.TestRedirectionIAMPermission(project)
    if not authorized:
        log.status.Print(
            con.Colorize("FAIL: ", "red") + "This operation requires "
            "the {} permission(s) on project {}.".format(
                ",".join(ar_requests.REDIRECT_PERMISSIONS), project))
    return authorized
 def __init__(self, *args, **kwargs):
     super(TextRenderer, self).__init__(*args, **kwargs)
     # We want the rendering to match the default console encoding. self._out
     # could be a file or pipe to a pager, either way we still want to see rich
     # encoding if the console supports it.
     encoding = console_attr.GetConsoleAttr().GetEncoding()
     self._attr = console_attr.GetConsoleAttr(out=self._out,
                                              encoding=encoding)
     self._blank = True
     self._bullet = self._attr.GetBullets()
     self._csi_char = self._attr.GetControlSequenceIndicator()
     if self._csi_char:
         self._csi_char = self._csi_char[0]
     self._fill = 0
     self._ignore_width = False
     self._indent = [self.Indent()]
     self._level = 0
     self._table = False
Example #21
0
def GetReadySymbol(ready):
  encoding = console_attr.GetConsoleAttr().GetEncoding()
  if ready == kubernetes_consts.VAL_UNKNOWN:
    return _PickSymbol('\N{HORIZONTAL ELLIPSIS}', '.', encoding)
  elif (ready == kubernetes_consts.VAL_TRUE or
        ready == kubernetes_consts.VAL_READY):
    return _PickSymbol('\N{HEAVY CHECK MARK}', '+', encoding)
  else:
    return 'X'
Example #22
0
def GetStyleMappings(console_attributes=None):
    console_attributes = console_attributes or console_attr.GetConsoleAttr()
    if console_attributes.SupportsAnsi():
        if console_attributes._term == 'xterm-256color':  # pylint: disable=protected-access
            return STYLE_MAPPINGS_ANSI_256
        else:
            return STYLE_MAPPINGS_ANSI
    else:
        return STYLE_MAPPINGS_BASIC
Example #23
0
 def testEncodingStdOutUtf8(self):
     sys.stdout = mock.MagicMock()
     sys.stdout.encoding = 'UTF-8'
     log.Reset()
     attr = console_attr.GetConsoleAttr(reset=True)
     locale_encoding = locale.getpreferredencoding()
     if locale_encoding and 'cp1252' in locale_encoding:
         self.assertEqual(attr.GetEncoding(), 'ascii')
     else:
         self.assertEqual(attr.GetEncoding(), 'utf8')
Example #24
0
    def _ConvertNonAsciiArgsToUnicode(self, args):
        """Converts non-ascii args to unicode.

    The args most likely came from sys.argv, and Python 2.7 passes them in as
    bytestrings instead of unicode.

    Args:
      args: [str], The list of args to convert.

    Raises:
      InvalidCharacterInArgException if a non-ascii arg cannot be converted to
      unicode.

    Returns:
      A new list of args with non-ascii args converted to unicode.
    """
        console_encoding = console_attr.GetConsoleAttr().GetEncoding()
        filesystem_encoding = sys.getfilesystemencoding()
        argv = []
        for arg in args:

            try:
                arg.encode('ascii')
                # Already ascii.
                argv.append(arg)
                continue
            except UnicodeError:
                pass

            try:
                # Convert bytestring to unicode using the console encoding.
                argv.append(unicode(arg, console_encoding))
                continue
            except TypeError:
                # Already unicode.
                argv.append(arg)
                continue
            except UnicodeError:
                pass

            try:
                # Convert bytestring to unicode using the filesystem encoding.
                # A pathname could have been passed in rather than typed, and
                # might not match the user terminal encoding, but still be a valid
                # path. For example: $ foo $(grep -l bar *)
                argv.append(unicode(arg, filesystem_encoding))
                continue
            except UnicodeError:
                pass

            # Can't convert to unicode -- bail out.
            raise exceptions.InvalidCharacterInArgException([self.name] + args,
                                                            arg)

        return argv
Example #25
0
def _Print(prefix, color, msg, **formatters):
    """Helper function to avoid import-time races."""
    con = console_attr.GetConsoleAttr()
    con.Colorize(prefix, color)
    formatters = formatters.copy()
    formatters.update({
        'bold': con.GetFontCode(bold=True),
        'italic': con.GetFontCode(italic=True),
        'reset': con.GetFontCode(),
    })
    log.status.Print(msg.format(**formatters))
Example #26
0
 def __init__(self, *args, **kwargs):
     """Creates a new TablePrinter."""
     self._rows = []
     super(TablePrinter, self).__init__(*args, by_columns=True, **kwargs)
     encoding = None
     for name in ['ascii', 'utf8', 'win']:
         if name in self._attributes:
             encoding = name
             break
     self._console_attr = console_attr.GetConsoleAttr(encoding=encoding,
                                                      out=self._out)
Example #27
0
    def _PrintCopyInstruction(self, auth_response):
        con = console_attr.GetConsoleAttr()

        log.status.write(self._COPY_AUTH_RESPONSE_INSTRUCTION + ' ')
        log.status.Print(
            self._COPY_AUTH_RESPONSE_WARNING.format(
                bold=con.GetFontCode(bold=True),
                command=self._target_command,
                normal=con.GetFontCode()))
        log.status.write('\n')
        log.status.Print(auth_response)
Example #28
0
 def __init__(self, *args, **kwargs):
     super(TextRenderer, self).__init__(*args, **kwargs)
     self._attr = console_attr.GetConsoleAttr()
     self._bullet = self._attr.GetBullets()
     self._csi_char = self._attr.GetControlSequenceIndicator()
     if self._csi_char:
         self._csi_char = self._csi_char[0]
     self._fill = 0
     self._ignore_width = False
     self._indent = [self.Indent()]
     self._level = 0
Example #29
0
 def ready_symbol(self):
     """Return a symbol summarizing the status of this object."""
     # NB: This can be overridden by subclasses to allow symbols for more
     # complex reasons the object isn't ready. Ex: Service overrides it to
     # provide '!' for "I'm serving, but not the revision you wanted."
     encoding = console_attr.GetConsoleAttr().GetEncoding()
     if self.ready is None:
         return self._PickSymbol('\N{HORIZONTAL ELLIPSIS}', '.', encoding)
     elif self.ready:
         return self._PickSymbol('\N{HEAVY CHECK MARK}', '+', encoding)
     else:
         return 'X'
Example #30
0
def GetRedirectionEnablementReport(project):
    """Prints a redirection enablement report and returns mis-configured repos.

  This checks all the GCR repositories in the supplied project and checks if
  they each have a repository in Artifact Registry create to be the redirection
  target. It prints a report as it validates.

  Args:
    project: The project to validate

  Returns:
    A list of the GCR repos that do not have a redirection repo configured in
    Artifact Registry.
  """
    locations = ar_requests.ListLocations(project, 100)

    # Sorting is performed so that Python2 & 3 agree on the order of API calls
    # in scenario tests.
    gcr_repos = GetExistingGCRBuckets(
        sorted(_GCR_BUCKETS.values(), key=lambda x: x["repository"]), project)

    failed_repos = []
    repo_report = []
    report_line = []
    con = console_attr.GetConsoleAttr()

    # For each gcr repo in a location that our environment supports,
    # is there an associated repo in AR?
    for gcr_repo in gcr_repos:
        if gcr_repo["location"] in locations:
            report_line = [gcr_repo["repository"], gcr_repo["location"]]
            ar_repo_name = "projects/{}/locations/{}/repositories/{}".format(
                project, gcr_repo["location"], gcr_repo["repository"])
            try:
                ar_repo = ar_requests.GetRepository(ar_repo_name)
                report_line.append(con.Colorize(ar_repo.name, "green"))
            except apitools_exceptions.HttpNotFoundError:
                report_line.append(con.Colorize("NOT FOUND", "red"))
                failed_repos.append(gcr_repo)
            repo_report.append(report_line)

    log.status.Print("Redirection enablement report:\n")
    printer = resource_printer.Printer("table", out=log.status)
    printer.AddHeading([
        con.Emphasize("Container Registry Host", bold=True),
        con.Emphasize("Location", bold=True),
        con.Emphasize("Artifact Registry Repository", bold=True)
    ])
    for line in repo_report:
        printer.AddRecord(line)
    printer.Finish()
    log.status.Print()
    return failed_repos