feat: exit code 1 if any migration fails (#7036)

* feat: exit code 1 if any migration fails

* update tests

* remove unused variables
This commit is contained in:
Peter Braun
2025-07-29 16:16:44 +02:00
committed by thedoubl3j
parent ad461a3aab
commit a3f2401740
2 changed files with 54 additions and 14 deletions

View File

@@ -172,6 +172,8 @@ class Command(BaseCommand):
if not migrators:
self.stdout.write(self.style.WARNING('NO MIGRATIONS WILL EXECUTE.'))
# Exit with success code since this is not an error condition
sys.exit(0)
else:
for migrator in migrators:
self.stdout.write(self.style.SUCCESS(f'\n=== Migrating {migrator.get_authenticator_type()} Configurations ==='))
@@ -196,16 +198,26 @@ class Command(BaseCommand):
self.stdout.write(f'Total settings unchanged: {total_results["settings_unchanged"]}')
self.stdout.write(f'Total settings failed: {total_results["settings_failed"]}')
# Check for any failures and return appropriate status code
has_failures = total_results["failed"] > 0 or total_results["mappers_failed"] > 0 or total_results["settings_failed"] > 0
if has_failures:
self.stdout.write(self.style.ERROR('\nMigration completed with failures.'))
sys.exit(1)
else:
self.stdout.write(self.style.SUCCESS('\nMigration completed successfully.'))
sys.exit(0)
except GatewayAPIError as e:
self.stdout.write(self.style.ERROR(f'Gateway API Error: {e.message}'))
if e.status_code:
self.stdout.write(self.style.ERROR(f'Status Code: {e.status_code}'))
if e.response_data:
self.stdout.write(self.style.ERROR(f'Response: {e.response_data}'))
return
sys.exit(1)
except Exception as e:
self.stdout.write(self.style.ERROR(f'Unexpected error during migration: {str(e)}'))
return
sys.exit(1)
def _print_export_summary(self, config_type, result):
"""Print a summary of the export results."""

View File

@@ -1,4 +1,5 @@
import os
import pytest
from unittest.mock import patch, Mock, call, DEFAULT
from io import StringIO
from unittest import TestCase
@@ -119,10 +120,11 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
def test_handle_missing_env_vars_basic_auth(self, mock_stdout):
"""Test that missing environment variables cause clean exit when using basic auth."""
with patch('sys.exit') as mock_exit:
with patch.object(self.command, 'stdout', mock_stdout):
with patch.object(self.command, 'stdout', mock_stdout):
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**self.options_basic_auth_full_send())
mock_exit.assert_called_once_with(0)
# Should exit with code 0 for successful early validation
assert exc_info.value.code == 0
output = mock_stdout.getvalue()
self.assertIn('Missing required environment variables:', output)
@@ -161,7 +163,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
self.create_mock_migrator(mock_settings_migrator, settings_created=1, settings_updated=0, settings_unchanged=2, settings_failed=0)
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_basic_auth_full_send())
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**self.options_basic_auth_full_send())
# Should exit with code 0 for success
assert exc_info.value.code == 0
# Verify gateway client was created with correct parameters
mock_gateway_client.assert_called_once_with(
@@ -184,6 +189,7 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
self.assertIn('mappers', output)
self.assertIn('settings', output)
@patch.dict(os.environ, {'GATEWAY_SKIP_VERIFY': 'false'}, clear=False) # Ensure verify_https=True
@patch('awx.main.management.commands.import_auth_config_to_gateway.create_api_client')
@patch('awx.main.management.commands.import_auth_config_to_gateway.GatewayClientSVCToken')
@patch('awx.main.management.commands.import_auth_config_to_gateway.urlparse')
@@ -215,7 +221,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
mock_gateway_client_svc.return_value.__exit__.return_value = None
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_svc_token_skip_all())
with patch('sys.exit'):
self.command.handle(**self.options_svc_token_skip_all())
# Should call sys.exit(0) for success, but may not due to test setup
# Just verify the command completed without raising an exception
# Verify resource API client was created and configured
mock_create_api_client.assert_called_once()
@@ -255,7 +264,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
mock_gateway_client.return_value.__exit__.return_value = None
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_basic_auth_skip_all_individual())
with patch('sys.exit'):
self.command.handle(**self.options_basic_auth_skip_all_individual())
# Should call sys.exit(0) for success, but may not due to test setup
# Just verify the command completed without raising an exception
# Verify no migrators were created
for mock_migrator in mock_migrators.values():
@@ -293,7 +305,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
options['skip_all_authenticators'] = True
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**options)
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**options)
# Should exit with code 0 for success (no failures)
assert exc_info.value.code == 0
# Verify no migrators were created
for mock_migrator in mock_migrators.values():
@@ -314,7 +329,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
mock_gateway_client.side_effect = GatewayAPIError('Test error message', status_code=400, response_data={'error': 'Bad request'})
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_basic_auth_full_send())
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**self.options_basic_auth_full_send())
# Should exit with code 1 for errors
assert exc_info.value.code == 1
# Verify error message output
output = mock_stdout.getvalue()
@@ -331,7 +349,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
mock_gateway_client.side_effect = ValueError('Unexpected error')
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_basic_auth_full_send())
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**self.options_basic_auth_full_send())
# Should exit with code 1 for errors
assert exc_info.value.code == 1
# Verify error message output
output = mock_stdout.getvalue()
@@ -361,7 +382,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
options['skip_settings'] = False
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**options)
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**options)
# Should exit with code 0 for success
assert exc_info.value.code == 0
# Verify migrator was created with force=True
mock_github.assert_called_once_with(mock_client_instance, self.command, force=True)
@@ -455,7 +479,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
options['skip_github'] = False
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**options)
with pytest.raises(SystemExit) as exc_info:
self.command.handle(**options)
# Should exit with code 0 for success
assert exc_info.value.code == 0
# Verify total results are accumulated correctly
output = mock_stdout.getvalue()
@@ -501,7 +528,8 @@ class TestImportAuthConfigToGatewayCommand(TestCase):
mock_gateway_client.return_value.__exit__.return_value = None
with patch.object(self.command, 'stdout', mock_stdout):
self.command.handle(**self.options_basic_auth_skip_all_individual())
with patch('sys.exit'):
self.command.handle(**self.options_basic_auth_skip_all_individual())
# Verify gateway client was called with correct skip_verify value
mock_gateway_client.assert_called_once_with(