From 47e2ba178361c097d87654a055c6b9eda961abe3 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Mon, 22 Sep 2025 13:58:02 +1000 Subject: [PATCH 1/3] log formatting --- stackql_deploy/cli.py | 22 +++++----------------- stackql_deploy/cmd/build.py | 6 +++++- stackql_deploy/cmd/teardown.py | 7 ++++++- stackql_deploy/cmd/test.py | 6 +++++- stackql_deploy/lib/utils.py | 21 +++++++++++++++++++++ 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/stackql_deploy/cli.py b/stackql_deploy/cli.py index 3a7fc2b..b1ba854 100644 --- a/stackql_deploy/cli.py +++ b/stackql_deploy/cli.py @@ -3,8 +3,10 @@ import os import sys import subprocess + from . import __version__ as deploy_version from .lib.bootstrap import logger +from .lib.utils import print_unicode_box, BorderColor from .cmd.build import StackQLProvisioner from .cmd.test import StackQLTestRunner from .cmd.teardown import StackQLDeProvisioner @@ -16,20 +18,6 @@ # utility functions # -def print_unicode_box(message): - border_color = '\033[93m' # Yellow color - reset_color = '\033[0m' - - lines = message.split('\n') - max_length = max(len(line) for line in lines) - top_border = border_color + '┌' + '─' * (max_length + 2) + '┐' + reset_color - bottom_border = border_color + '└' + '─' * (max_length + 2) + '┘' + reset_color - - click.echo(top_border) - for line in lines: - click.echo(border_color + '│ ' + line.ljust(max_length) + ' │' + reset_color) - click.echo(bottom_border) - def get_stackql_instance(custom_registry=None, download_dir=None): """Initializes StackQL with the given options.""" stackql_kwargs = {} @@ -190,7 +178,7 @@ def build(ctx, stack_dir, stack_env, log_level, env_file, ) message = (f"Deploying stack: [{stack_name_display}] " f"to environment: [{stack_env}]") - print_unicode_box(message) + print_unicode_box(message, BorderColor.YELLOW) provisioner.run(dry_run, show_queries, on_failure) click.echo("🎯 dry-run build complete" if dry_run @@ -222,7 +210,7 @@ def teardown(ctx, stack_dir, stack_env, log_level, env_file, ) message = (f"Tearing down stack: [{stack_name_display}] " f"in environment: [{stack_env}]") - print_unicode_box(message) + print_unicode_box(message, BorderColor.YELLOW) deprovisioner.run(dry_run, show_queries, on_failure) click.echo(f"🚧 teardown complete (dry run: {dry_run})") @@ -253,7 +241,7 @@ def test(ctx, stack_dir, stack_env, log_level, env_file, ) message = (f"Testing stack: [{stack_name_display}] " f"in environment: [{stack_env}]") - print_unicode_box(message) + print_unicode_box(message, BorderColor.YELLOW) test_runner.run(dry_run, show_queries, on_failure) click.echo(f"🔍 tests complete (dry run: {dry_run})") diff --git a/stackql_deploy/cmd/build.py b/stackql_deploy/cmd/build.py index f237bc1..ea60adf 100644 --- a/stackql_deploy/cmd/build.py +++ b/stackql_deploy/cmd/build.py @@ -4,7 +4,9 @@ catch_error_and_exit, export_vars, run_ext_script, - get_type + get_type, + print_unicode_box, + BorderColor ) from ..lib.config import get_full_context, render_value from ..lib.templating import get_queries, render_inline_template @@ -43,6 +45,8 @@ def run(self, dry_run, show_queries, on_failure): for resource in self.manifest.get('resources', []): + print_unicode_box(f"Processing resource: [{resource['name']}]", BorderColor.BLUE) + type = get_type(resource, self.logger) self.logger.info(f"processing resource [{resource['name']}], type: {type}") diff --git a/stackql_deploy/cmd/teardown.py b/stackql_deploy/cmd/teardown.py index c2045d0..6964f43 100644 --- a/stackql_deploy/cmd/teardown.py +++ b/stackql_deploy/cmd/teardown.py @@ -2,7 +2,9 @@ import datetime from ..lib.utils import ( catch_error_and_exit, - get_type + get_type, + print_unicode_box, + BorderColor ) from ..lib.config import get_full_context, render_value from ..lib.templating import get_queries, render_inline_template @@ -70,6 +72,9 @@ def run(self, dry_run, show_queries, on_failure): self.collect_exports(show_queries, dry_run) for resource in reversed(self.manifest['resources']): + + print_unicode_box(f"Processing resource: [{resource['name']}]", BorderColor.RED) + # process resources in reverse order type = get_type(resource, self.logger) diff --git a/stackql_deploy/cmd/test.py b/stackql_deploy/cmd/test.py index 03226ae..f4ede7a 100644 --- a/stackql_deploy/cmd/test.py +++ b/stackql_deploy/cmd/test.py @@ -2,7 +2,9 @@ import datetime from ..lib.utils import ( catch_error_and_exit, - get_type + get_type, + print_unicode_box, + BorderColor ) from ..lib.config import get_full_context from ..lib.templating import get_queries, render_inline_template @@ -19,6 +21,8 @@ def run(self, dry_run, show_queries, on_failure): for resource in self.manifest.get('resources', []): + print_unicode_box(f"Processing resource: [{resource['name']}]", BorderColor.BLUE) + type = get_type(resource, self.logger) if type == 'query': diff --git a/stackql_deploy/lib/utils.py b/stackql_deploy/lib/utils.py index a1a1b06..4c4dbc7 100644 --- a/stackql_deploy/lib/utils.py +++ b/stackql_deploy/lib/utils.py @@ -1,10 +1,31 @@ # lib/utils.py +import click +from enum import Enum import time import json import sys import subprocess import re +class BorderColor(Enum): + YELLOW = '\033[93m' # Bright yellow + BLUE = '\033[94m' # Bright blue + RED = '\033[91m' # Bright red + +def print_unicode_box(message: str, color: BorderColor = BorderColor.YELLOW): + border_color = color.value + reset_color = '\033[0m' + + lines = message.split('\n') + max_length = max(len(line) for line in lines) + top_border = border_color + '┌' + '─' * (max_length + 2) + '┐' + reset_color + bottom_border = border_color + '└' + '─' * (max_length + 2) + '┘' + reset_color + + click.echo(top_border) + for line in lines: + click.echo(border_color + '│ ' + line.ljust(max_length) + ' │' + reset_color) + click.echo(bottom_border) + def catch_error_and_exit(errmsg, logger): logger.error(errmsg) sys.exit("stackql-deploy operation failed 🚫") From 8ea30e2664488ad55b86820baa101a2679d508d5 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Sat, 11 Oct 2025 11:02:18 +1100 Subject: [PATCH 2/3] logging output update --- setup.py | 2 +- stackql_deploy/__init__.py | 2 +- stackql_deploy/cmd/base.py | 19 +++++++++++++++++++ stackql_deploy/lib/utils.py | 33 ++++++++++++++++++++------------- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index c6388fa..05cf1e8 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='stackql-deploy', - version='1.8.6', + version='1.8.7', description='Model driven resource provisioning and deployment framework using StackQL.', long_description=readme, long_description_content_type='text/x-rst', diff --git a/stackql_deploy/__init__.py b/stackql_deploy/__init__.py index 7bb2219..161276c 100644 --- a/stackql_deploy/__init__.py +++ b/stackql_deploy/__init__.py @@ -1 +1 @@ -__version__ = '1.8.6' +__version__ = '1.8.7' diff --git a/stackql_deploy/cmd/base.py b/stackql_deploy/cmd/base.py index 876a16f..f8b8809 100644 --- a/stackql_deploy/cmd/base.py +++ b/stackql_deploy/cmd/base.py @@ -119,6 +119,25 @@ def process_exports( show_query(True, exports_query, self.logger) catch_error_and_exit(f"exports query failed for {resource['name']}", self.logger) + # Check if we received an error from the query execution + if (len(exports) >= 1 and isinstance(exports[0], dict)): + # Check for our custom error wrapper + if '_stackql_deploy_error' in exports[0]: + error_msg = exports[0]['_stackql_deploy_error'] + show_query(True, exports_query, self.logger) + catch_error_and_exit( + f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", + self.logger + ) + # Check for direct error in result + elif 'error' in exports[0]: + error_msg = exports[0]['error'] + show_query(True, exports_query, self.logger) + catch_error_and_exit( + f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", + self.logger + ) + if len(exports) > 1: catch_error_and_exit( f"exports should include one row only, received {str(len(exports))} rows", diff --git a/stackql_deploy/lib/utils.py b/stackql_deploy/lib/utils.py index 4c4dbc7..e2f4b36 100644 --- a/stackql_deploy/lib/utils.py +++ b/stackql_deploy/lib/utils.py @@ -39,6 +39,7 @@ def get_type(resource, logger): def run_stackql_query(query, stackql, suppress_errors, logger, custom_auth=None, env_vars=None, retries=0, delay=5): attempt = 0 + last_error = None while attempt <= retries: try: logger.debug(f"(utils.run_stackql_query) executing stackql query on attempt {attempt + 1}:\n\n{query}\n") @@ -50,20 +51,22 @@ def run_stackql_query(query, stackql, suppress_errors, logger, custom_auth=None, if len(result) == 0: logger.debug("(utils.run_stackql_query) stackql query executed successfully, retrieved 0 items.") pass - elif not suppress_errors and result and 'error' in result[0]: + elif result and 'error' in result[0]: error_message = result[0]['error'] - if attempt == retries: - # If retries are exhausted, log the error and exit - catch_error_and_exit( - ( - f"(utils.run_stackql_query) error occurred during stackql query execution:\n\n" - f"{error_message}\n" - ), - logger - ) - else: - # Log the error and prepare for another attempt - logger.error(f"attempt {attempt + 1} failed:\n\n{error_message}\n") + last_error = error_message # Store the error for potential return + if not suppress_errors: + if attempt == retries: + # If retries are exhausted, log the error and exit + catch_error_and_exit( + ( + f"(utils.run_stackql_query) error occurred during stackql query execution:\n\n" + f"{error_message}\n" + ), + logger + ) + else: + # Log the error and prepare for another attempt + logger.error(f"attempt {attempt + 1} failed:\n\n{error_message}\n") elif 'count' in result[0]: # If the result is a count query, return the count logger.debug( @@ -95,6 +98,7 @@ def run_stackql_query(query, stackql, suppress_errors, logger, custom_auth=None, except Exception as e: # Log the exception and check if retry attempts are exhausted + last_error = str(e) # Store the exception for potential return if attempt == retries: catch_error_and_exit( f"(utils.run_stackql_query) an exception occurred during stackql query execution:\n\n{str(e)}\n", @@ -108,6 +112,9 @@ def run_stackql_query(query, stackql, suppress_errors, logger, custom_auth=None, attempt += 1 logger.debug(f"(utils.run_stackql_query) all attempts ({retries + 1}) to execute the query completed.") + # If suppress_errors is True and we have an error, return an empty list with error info as a special dict + if suppress_errors and last_error: + return [{'_stackql_deploy_error': last_error}] # return None return [] From 6b106f2bc2a9a6f5afc6131d01aa411d1edf9995 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Sat, 11 Oct 2025 11:06:10 +1100 Subject: [PATCH 3/3] fix linting errors --- stackql_deploy/cmd/base.py | 4 ++-- stackql_deploy/cmd/teardown.py | 6 +++--- stackql_deploy/cmd/test.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stackql_deploy/cmd/base.py b/stackql_deploy/cmd/base.py index f8b8809..108d7cd 100644 --- a/stackql_deploy/cmd/base.py +++ b/stackql_deploy/cmd/base.py @@ -126,7 +126,7 @@ def process_exports( error_msg = exports[0]['_stackql_deploy_error'] show_query(True, exports_query, self.logger) catch_error_and_exit( - f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", + f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", self.logger ) # Check for direct error in result @@ -134,7 +134,7 @@ def process_exports( error_msg = exports[0]['error'] show_query(True, exports_query, self.logger) catch_error_and_exit( - f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", + f"exports query failed for {resource['name']}\n\nError details:\n{error_msg}", self.logger ) diff --git a/stackql_deploy/cmd/teardown.py b/stackql_deploy/cmd/teardown.py index 6964f43..657c3f6 100644 --- a/stackql_deploy/cmd/teardown.py +++ b/stackql_deploy/cmd/teardown.py @@ -4,7 +4,7 @@ catch_error_and_exit, get_type, print_unicode_box, - BorderColor + BorderColor ) from ..lib.config import get_full_context, render_value from ..lib.templating import get_queries, render_inline_template @@ -72,9 +72,9 @@ def run(self, dry_run, show_queries, on_failure): self.collect_exports(show_queries, dry_run) for resource in reversed(self.manifest['resources']): - + print_unicode_box(f"Processing resource: [{resource['name']}]", BorderColor.RED) - + # process resources in reverse order type = get_type(resource, self.logger) diff --git a/stackql_deploy/cmd/test.py b/stackql_deploy/cmd/test.py index f4ede7a..35b46a2 100644 --- a/stackql_deploy/cmd/test.py +++ b/stackql_deploy/cmd/test.py @@ -4,7 +4,7 @@ catch_error_and_exit, get_type, print_unicode_box, - BorderColor + BorderColor ) from ..lib.config import get_full_context from ..lib.templating import get_queries, render_inline_template