diff --git a/CHANGELOG.md b/CHANGELOG.md index 45251a8..81989fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.9.2 (2025-10-16) +## 1.9.4 (2025-10-16) - added `--output-file` argument - added stack level `exports` - pre defined `stack_name`, `stack_env`, `elapsed_time` and user defined diff --git a/setup.py b/setup.py index e5cea3c..c07fbeb 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='stackql-deploy', - version='1.9.2', + version='1.9.4', 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 8e2b7ed..b0bb384 100644 --- a/stackql_deploy/__init__.py +++ b/stackql_deploy/__init__.py @@ -1 +1 @@ -__version__ = '1.9.2' +__version__ = '1.9.4' diff --git a/stackql_deploy/cmd/build.py b/stackql_deploy/cmd/build.py index 6805059..9772828 100644 --- a/stackql_deploy/cmd/build.py +++ b/stackql_deploy/cmd/build.py @@ -159,44 +159,102 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): ignore_errors = True # - # OPTIMIZED exists and state check - try exports first for happy path + # State checking logic # exports_result_from_proxy = None # Track exports result if used as proxy if createorupdate_query: + # Skip all existence and state checks for createorupdate pass else: - # OPTIMIZATION: Try exports first if available for one-query solution - if exports_query: + # Determine the validation strategy based on available queries + if statecheck_query: + # + # Flow 1: Traditional flow when statecheck exists + # exists → create/update → statecheck → exports + # + if exists_query: + resource_exists = self.check_if_resource_exists( + resource_exists, + resource, + full_context, + exists_query, + exists_retries, + exists_retry_delay, + dry_run, + show_queries + ) + else: + # Use statecheck as exists check + is_correct_state = self.check_if_resource_is_correct_state( + is_correct_state, + resource, + full_context, + statecheck_query, + statecheck_retries, + statecheck_retry_delay, + dry_run, + show_queries + ) + resource_exists = is_correct_state + + # Pre-deployment state check for existing resources + if resource_exists and not is_correct_state: + if resource.get('skip_validation', False): + self.logger.info( + f"skipping validation for [{resource['name']}] as skip_validation is set to true." + ) + is_correct_state = True + else: + is_correct_state = self.check_if_resource_is_correct_state( + is_correct_state, + resource, + full_context, + statecheck_query, + statecheck_retries, + statecheck_retry_delay, + dry_run, + show_queries + ) + + elif exports_query: + # + # Flow 2: Optimized flow when only exports exists (no statecheck) + # Try exports first with FAST FAIL (no retries) + # If fails: exists → create/update → exports (with retries as statecheck) + # self.logger.info( - f"🔄 trying exports query first for optimal single-query validation " + f"🔄 trying exports query first (fast-fail) for optimal validation " f"for [{resource['name']}]" ) is_correct_state, exports_result_from_proxy = self.check_state_using_exports_proxy( resource, full_context, exports_query, - exports_retries, - exports_retry_delay, + 1, # Fast fail: only 1 attempt + 0, # No delay dry_run, show_queries ) resource_exists = is_correct_state - # If exports succeeded, we're done with validation for happy path + # If exports succeeded, we're done with validation (happy path) if is_correct_state: self.logger.info( - f"✅ [{resource['name']}] validated successfully with single exports query" + f"✅ [{resource['name']}] validated successfully with fast exports query" ) else: - # If exports failed, fall back to traditional exists check + # Exports failed, fall back to exists check self.logger.info( - f"📋 exports validation failed, falling back to exists check " + f"📋 fast exports validation failed, falling back to exists check " f"for [{resource['name']}]" ) + # Clear the failed exports result + exports_result_from_proxy = None + if exists_query: resource_exists = self.check_if_resource_exists( - False, # Reset this since exports failed + False, resource, full_context, exists_query, @@ -205,23 +263,14 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): dry_run, show_queries ) - elif statecheck_query: - # statecheck can be used as an exists check fallback - is_correct_state = self.check_if_resource_is_correct_state( - False, # Reset this - resource, - full_context, - statecheck_query, - statecheck_retries, - statecheck_retry_delay, - dry_run, - show_queries - ) - resource_exists = is_correct_state - # Reset is_correct_state since we need to re-validate after create/update - is_correct_state = False + else: + # No exists query, assume resource doesn't exist + resource_exists = False + elif exists_query: - # Traditional path: exports not available, use exists + # + # Flow 3: Basic flow with only exists query + # resource_exists = self.check_if_resource_exists( resource_exists, resource, @@ -232,59 +281,12 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): dry_run, show_queries ) - elif statecheck_query: - # statecheck can be used as an exists check - is_correct_state = self.check_if_resource_is_correct_state( - is_correct_state, - resource, - full_context, - statecheck_query, - statecheck_retries, - statecheck_retry_delay, - dry_run, - show_queries - ) - resource_exists = is_correct_state else: catch_error_and_exit( "iql file must include either 'exists', 'statecheck', or 'exports' anchor.", self.logger ) - # - # state check with optimizations (only if we haven't already validated via exports) - # - if resource_exists and not is_correct_state and exports_result_from_proxy is None: - # bypass state check if skip_validation is set to true - if resource.get('skip_validation', False): - self.logger.info( - f"skipping validation for [{resource['name']}] as skip_validation is set to true." - ) - is_correct_state = True - elif statecheck_query: - is_correct_state = self.check_if_resource_is_correct_state( - is_correct_state, - resource, - full_context, - statecheck_query, - statecheck_retries, - statecheck_retry_delay, - dry_run, - show_queries - ) - elif exports_query: - # This shouldn't happen since we tried exports first, but keeping for safety - self.logger.info(f"🔄 using exports query as proxy for statecheck for [{resource['name']}]") - is_correct_state, _ = self.check_state_using_exports_proxy( - resource, - full_context, - exports_query, - exports_retries, - exports_retry_delay, - dry_run, - show_queries - ) - # # resource does not exist # @@ -319,10 +321,11 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): ) # - # check state again after create or update with optimizations + # check state again after create or update # if is_created_or_updated: if statecheck_query: + # Use statecheck for post-deploy validation is_correct_state = self.check_if_resource_is_correct_state( is_correct_state, resource, @@ -334,17 +337,23 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): show_queries, ) elif exports_query: - # OPTIMIZATION: Use exports as statecheck proxy for post-deploy validation + # Use exports as statecheck proxy with proper retries + # This handles the case where statecheck doesn't exist self.logger.info( - f"🔄 using exports query as proxy for post-deploy statecheck " + f"🔄 using exports query as post-deploy statecheck " f"for [{resource['name']}]" ) - is_correct_state, _ = self.check_state_using_exports_proxy( + # Need to determine retries: if we have statecheck config, use it + # Otherwise fall back to exports config + post_deploy_retries = statecheck_retries if statecheck_retries > 1 else exports_retries + post_deploy_delay = statecheck_retry_delay if statecheck_retries > 1 else exports_retry_delay + + is_correct_state, exports_result_from_proxy = self.check_state_using_exports_proxy( resource, full_context, exports_query, - exports_retries, - exports_retry_delay, + post_deploy_retries, + post_deploy_delay, dry_run, show_queries ) diff --git a/stackql_deploy/cmd/test.py b/stackql_deploy/cmd/test.py index 2cb2100..526e6e7 100644 --- a/stackql_deploy/cmd/test.py +++ b/stackql_deploy/cmd/test.py @@ -108,8 +108,8 @@ def run(self, dry_run, show_queries, on_failure, output_file=None): resource, full_context, exports_query, - exports_retries, - exports_retry_delay, + statecheck_retries, # Use statecheck retries when using as statecheck proxy + statecheck_retry_delay, # Use statecheck delay when using as statecheck proxy dry_run, show_queries )