Skip to content

Commit

Permalink
Allow disabling text colors
Browse files Browse the repository at this point in the history
  • Loading branch information
Shrews committed Feb 20, 2025
1 parent 16a5dce commit 018f6e5
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 54 deletions.
10 changes: 10 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ will stack. For example, the following are equivalent to setting the verbosity l
$ ansible-builder build -v -v -v
``--no-colors``
***************

Disables ANSI text colors.

If this option is not given, the default will be to enable text colors for the output.
The ``NO_COLOR`` and ``FORCE_COLOR`` environment variables will be honored if this CLI option
is not supplied.


``--prune-images``
******************

Expand Down
55 changes: 48 additions & 7 deletions src/ansible_builder/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from . import constants

from .colors import MessageColors
from .exceptions import DefinitionError
from .main import AnsibleBuilder
from .policies import PolicyChoices
Expand Down Expand Up @@ -40,19 +39,55 @@ def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.count)


def _should_disable_colors() -> bool:
"""
Check the environment to decide if text colorization should be disabled.
According to no-color.org, if NO_COLOR is present, and not an empty string (regardless of
its value), text should not be colorized.
According to force-color.org, if FORCE_COLOR is present, and not an empty string (regardless
of its value), text should be colorized, and should trump NO_COLOR.
:returns: True if colors are disabled, False if enabled.
"""
disabled = False

if os.environ.get('TERM', '') == 'dumb':
return True

no_color = os.environ.get('NO_COLOR', None)
force_color = os.environ.get('FORCE_COLOR', None)

if no_color:
disabled = True
if force_color:
disabled = False

return disabled


def run():
args = parse_args()
configure_logger(args.verbosity)

# If user explicitly requests to disable colors, that value takes precedence. Otherwise,
# we'll check the environment.
disable_colors = args.no_colors
if '--no-colors' not in sys.argv:
disable_colors = _should_disable_colors()

configure_logger(args.verbosity, disable_colors)

if args.action in ['create', 'build']:
ab = AnsibleBuilder(**vars(args))
kwargs = vars(args)
kwargs.pop('no_colors') # not a value we should pass along

ab = AnsibleBuilder(**kwargs)
action = getattr(ab, ab.action)
try:
if action():
print(
f"{MessageColors.OKGREEN}Complete! The build context can be found at: "
f"{os.path.abspath(ab.build_context)}{MessageColors.ENDC}"
)
logger.log(constants.SUCCESS_LOGLEVEL,
"Complete! The build context can be found at: %s", os.path.abspath(ab.build_context))
sys.exit(0)
except DefinitionError as e:
logger.error(e.args[0])
Expand Down Expand Up @@ -200,6 +235,12 @@ def add_container_options(parser):
'Integer values are also accepted (for example, "-v3" or "--verbosity 3"). '
'Default is %(default)s.')

n.add_argument('--no-colors',
dest='no_colors',
action='store_true',
help='Disable ANSI text colors (enabled by default). NO_COLOR and FORCE_COLOR environment '
'variables will be honored if this option is not used.')


def parse_args(args=None):

Expand Down
7 changes: 0 additions & 7 deletions src/ansible_builder/colors.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/ansible_builder/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@

DEFAULT_EE_BASENAME = "execution-environment"
YAML_FILENAME_EXTENSIONS = ('yml', 'yaml')

SUCCESS_LOGLEVEL = 100
72 changes: 33 additions & 39 deletions src/ansible_builder/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import filecmp
import logging
import logging.config
Expand All @@ -9,58 +11,50 @@
from collections import deque
from pathlib import Path

from .colors import MessageColors
from . import constants


logger = logging.getLogger(__name__)
logging_levels = {
'0': 'ERROR',
'1': 'WARNING',
'2': 'INFO',
'3': 'DEBUG',
}


class ColorFilter(logging.Filter):
class MessageColors:
ERROR = '\033[91m' # bright red
WARNING = '\033[93m' # bright yellow
INFO = '\033[94m' # bright blue
DEBUG = '\033[95m' # bright magenta
OKGREEN = '\033[92m' # bright green
DEFAULT = '\033[0m' # terminal default

color_map = {
'ERROR': MessageColors.FAIL,
'WARNING': MessageColors.WARNING,
'INFO': MessageColors.HEADER,
'DEBUG': MessageColors.OK
logging.CRITICAL: MessageColors.ERROR,
logging.ERROR: MessageColors.ERROR,
logging.WARNING: MessageColors.WARNING,
logging.INFO: MessageColors.INFO,
logging.DEBUG: MessageColors.DEBUG,
constants.SUCCESS_LOGLEVEL: MessageColors.OKGREEN,
}

def filter(self, record):
if sys.stdout.isatty():
record.msg = self.color_map[record.levelname] + record.msg + MessageColors.ENDC
record.msg = self.color_map[record.levelno] + record.msg + ColorFilter.MessageColors.DEFAULT
return record


LOGGING = {
'version': 1,
'filters': {
'colorize': {
'()': ColorFilter
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'filters': ['colorize'],
'stream': 'ext://sys.stdout'
}
},
'loggers': {
'ansible_builder': {
'handlers': ['console'],
}
def configure_logger(verbosity: int, disable_colors: bool = False):
logging_levels = {
0: 'ERROR',
1: 'WARNING',
2: 'INFO',
3: 'DEBUG',
}
}


def configure_logger(verbosity):
LOGGING['loggers']['ansible_builder']['level'] = logging_levels[str(verbosity)]
logging.config.dictConfig(LOGGING)
root_logger = logging.getLogger()
root_logger.setLevel(logging_levels[verbosity])
handler = logging.StreamHandler(sys.stdout)
if not disable_colors:
handler.addFilter(ColorFilter())
root_logger.addHandler(handler)


def run_command(command, capture_output=False, allow_error=False):
Expand Down Expand Up @@ -118,7 +112,7 @@ def run_command(command, capture_output=False, allow_error=False):
logger.error("An error occurred (rc=%s), see output line(s) above for details.", rc)
sys.exit(1)

return (rc, output)
return rc, output


def write_file(filename: str, lines: list) -> bool:
Expand Down Expand Up @@ -170,9 +164,9 @@ def copy_file(source: str, dest: str, ignore_mtime: bool = False) -> bool:
to copy the file if it doesn't exist, or if it has changed between builds.
See the `copy_directory()` function for the directory copy equivalent.
:param source str: Path to a source file.
:param dest str: Path to a destination file within the context subdir.
:param ignore_mtime bool: Whether or not mtime should be considered.
:param str source: Path to a source file.
:param str dest: Path to a destination file within the context subdir.
:param bool ignore_mtime: Whether mtime should be considered.
:returns: True if the file was copied, False if not.
Expand Down
4 changes: 3 additions & 1 deletion test/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

def prepare(args):
args = parse_args(args)
return AnsibleBuilder(**vars(args))
kwargs = vars(args)
kwargs.pop('no_colors')
return AnsibleBuilder(**kwargs)


def test_custom_image(exec_env_definition_file, tmp_path):
Expand Down

0 comments on commit 018f6e5

Please sign in to comment.