Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion stackql_deploy/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.9.2'
__version__ = '1.9.4'
169 changes: 89 additions & 80 deletions stackql_deploy/cmd/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
#
Expand Down Expand Up @@ -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,
Expand All @@ -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
)
Expand Down
4 changes: 2 additions & 2 deletions stackql_deploy/cmd/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Loading