diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 333c846..3a08557 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -3,7 +3,7 @@ name: Coriolis Client Unit Tests on: workflow_dispatch: pull_request: - branches: [ "master" ] + branches: [ "main" ] permissions: contents: read diff --git a/coriolisclient/cli/endpoints.py b/coriolisclient/cli/endpoints.py index 7f4749b..938ad9e 100644 --- a/coriolisclient/cli/endpoints.py +++ b/coriolisclient/cli/endpoints.py @@ -151,20 +151,20 @@ def take_action(self, args): "--connection-secret, but not both") conn_info = get_connection_info_from_args(args) - endpoint = self.app.client_manager.coriolis.endpoints.create( + endpoints = self.app.client_manager.coriolis.endpoints + if not args.skip_validation: + valid, message = endpoints.validate_connection_info( + args.provider, conn_info, regions=args.regions) + if not valid: + raise exceptions.EndpointConnectionValidationFailed(message) + + endpoint = endpoints.create( args.name, args.provider, conn_info, args.description, regions=args.regions) - if not args.skip_validation: - valid, message = ( - self.app.client_manager.coriolis.endpoints.validate_connection( - endpoint.id)) - if not valid: - raise exceptions.EndpointConnectionValidationFailed(message) - return EndpointDetailFormatter().get_formatted_entity(endpoint) @@ -295,3 +295,30 @@ def take_action(self, args): valid, message = endpoints.validate_connection(endpoint_id) if not valid: raise exceptions.EndpointConnectionValidationFailed(message) + + +class EndpointValidateConnectionInfo(command.Command): + """Validates the given endpoint connection info""" + + def get_parser(self, prog_name): + parser = super( + EndpointValidateConnectionInfo, self).get_parser(prog_name) + parser.add_argument('--provider', required=True, + help='The provider, e.g.: ' + 'vmware_vsphere, openstack') + parser.add_argument('--coriolis-region', action='append', + dest='regions', default=[], + help="ID of a region the connection info should " + "be validated in. Can be supplied multiple " + "times.") + add_connection_info_args_to_parser(parser) + + return parser + + def take_action(self, args): + conn_info = get_connection_info_from_args(args) + endpoints = self.app.client_manager.coriolis.endpoints + valid, message = endpoints.validate_connection_info( + args.provider, conn_info, regions=args.regions) + if not valid: + raise exceptions.EndpointConnectionValidationFailed(message) diff --git a/coriolisclient/tests/cli/test_endpoints.py b/coriolisclient/tests/cli/test_endpoints.py index 09b8acd..b1347e2 100644 --- a/coriolisclient/tests/cli/test_endpoints.py +++ b/coriolisclient/tests/cli/test_endpoints.py @@ -200,11 +200,11 @@ def test_take_action( args.regions = mock.sentinel.regions args.skip_validation = False mock_create = mock.Mock() - mock_validate_connection = mock.Mock() - mock_validate_connection.return_value = (True, "mock_message") + mock_validate_connection_info = mock.Mock() + mock_validate_connection_info.return_value = (True, "mock_message") self.mock_app.client_manager.coriolis.endpoints.create = mock_create - self.mock_app.client_manager.coriolis.endpoints.validate_connection = \ - mock_validate_connection + (self.mock_app.client_manager.coriolis.endpoints + .validate_connection_info) = mock_validate_connection_info result = self.endpoint.take_action(args) @@ -213,6 +213,10 @@ def test_take_action( result ) mock_get_connection_info_from_args.assert_called_once_with(args) + mock_validate_connection_info.assert_called_once_with( + mock.sentinel.provider, + mock_get_connection_info_from_args.return_value, + regions=mock.sentinel.regions) mock_create.assert_called_once_with( mock.sentinel.name, mock.sentinel.provider, @@ -220,8 +224,6 @@ def test_take_action( mock.sentinel.description, regions=mock.sentinel.regions, ) - mock_validate_connection.assert_called_once_with( - mock_create.return_value.id) mock_get_formatted_entity.assert_called_once_with( mock_create.return_value) @@ -253,11 +255,11 @@ def test_take_action_validation_failed( args.regions = mock.sentinel.regions args.skip_validation = False mock_create = mock.Mock() - mock_validate_connection = mock.Mock() - mock_validate_connection.return_value = (False, "mock_message") + mock_validate_connection_info = mock.Mock() + mock_validate_connection_info.return_value = (False, "mock_message") self.mock_app.client_manager.coriolis.endpoints.create = mock_create - self.mock_app.client_manager.coriolis.endpoints.validate_connection = \ - mock_validate_connection + (self.mock_app.client_manager.coriolis.endpoints + .validate_connection_info) = mock_validate_connection_info self.assertRaises( exceptions.EndpointConnectionValidationFailed, @@ -266,15 +268,11 @@ def test_take_action_validation_failed( ) mock_get_connection_info_from_args.assert_called_once_with(args) - mock_create.assert_called_once_with( - mock.sentinel.name, + mock_validate_connection_info.assert_called_once_with( mock.sentinel.provider, mock_get_connection_info_from_args.return_value, - mock.sentinel.description, - regions=mock.sentinel.regions, - ) - mock_validate_connection.assert_called_once_with( - mock_create.return_value.id) + regions=mock.sentinel.regions) + mock_create.assert_not_called() mock_get_formatted_entity.assert_not_called() @@ -533,3 +531,72 @@ def test_take_action_not_valid(self, mock_list_objects): mock.sentinel.id) mock_validate_connection.assert_called_once_with( mock_endpoint.get_endpoint_id_for_name.return_value) + + +class EndpointValidateConnectionInfoTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Client Endpoint Validate Connection + Info.""" + + def setUp(self): + self.mock_app = mock.Mock() + super(EndpointValidateConnectionInfoTestCase, self).setUp() + self.endpoint = endpoints.EndpointValidateConnectionInfo( + self.mock_app, mock.sentinel.app_args) + + @mock.patch.object(endpoints, 'add_connection_info_args_to_parser') + @mock.patch.object(command.Command, 'get_parser') + def test_get_parser( + self, + mock_get_parser, + mock_add_connection_info_args_to_parser + ): + result = self.endpoint.get_parser(mock.sentinel.prog_name) + + self.assertEqual( + mock_get_parser.return_value, + result + ) + mock_get_parser.assert_called_once_with(mock.sentinel.prog_name) + mock_add_connection_info_args_to_parser.assert_called_once_with( + mock_get_parser.return_value) + + @mock.patch.object(endpoints, 'get_connection_info_from_args') + def test_take_action(self, mock_get_connection_info_from_args): + args = mock.Mock() + args.provider = mock.sentinel.provider + args.regions = mock.sentinel.regions + mock_validate_connection_info = mock.Mock() + mock_validate_connection_info.return_value = (True, None) + (self.mock_app.client_manager.coriolis.endpoints + .validate_connection_info) = mock_validate_connection_info + + self.endpoint.take_action(args) + + mock_get_connection_info_from_args.assert_called_once_with(args) + mock_validate_connection_info.assert_called_once_with( + mock.sentinel.provider, + mock_get_connection_info_from_args.return_value, + regions=mock.sentinel.regions) + + @mock.patch.object(endpoints, 'get_connection_info_from_args') + def test_take_action_not_valid(self, mock_get_connection_info_from_args): + args = mock.Mock() + args.provider = mock.sentinel.provider + args.regions = mock.sentinel.regions + mock_validate_connection_info = mock.Mock() + mock_validate_connection_info.return_value = (False, "mock_message") + (self.mock_app.client_manager.coriolis.endpoints + .validate_connection_info) = mock_validate_connection_info + + self.assertRaisesRegex( + exceptions.EndpointConnectionValidationFailed, + "mock_message", + self.endpoint.take_action, + args + ) + + mock_get_connection_info_from_args.assert_called_once_with(args) + mock_validate_connection_info.assert_called_once_with( + mock.sentinel.provider, + mock_get_connection_info_from_args.return_value, + regions=mock.sentinel.regions) diff --git a/coriolisclient/tests/v1/test_endpoints.py b/coriolisclient/tests/v1/test_endpoints.py index 1e3e058..7a80920 100644 --- a/coriolisclient/tests/v1/test_endpoints.py +++ b/coriolisclient/tests/v1/test_endpoints.py @@ -160,6 +160,47 @@ def test_validate_connection(self): '/endpoints/53773ab8-1474-4cf7-bf0c-a496a6595ecb/actions', json={'validate-connection': None}) + def test_validate_connection_info(self): + self.mock_client.post.return_value.json.return_value = { + "validate-connection-info": { + "valid": mock.sentinel.valid, + "message": mock.sentinel.message + } + } + + result = self.endpoint.validate_connection_info( + mock.sentinel.platform, mock.sentinel.connection_info, + regions=mock.sentinel.regions) + + self.assertEqual( + (mock.sentinel.valid, mock.sentinel.message), + result + ) + self.mock_client.post.assert_called_once_with( + '/endpoints/actions', + json={'validate-connection-info': { + "platform": mock.sentinel.platform, + "connection_info": mock.sentinel.connection_info, + "mapped_regions": mock.sentinel.regions}}) + + def test_validate_connection_info_no_regions(self): + self.mock_client.post.return_value.json.return_value = { + "validate-connection-info": { + "valid": mock.sentinel.valid, + "message": mock.sentinel.message + } + } + + self.endpoint.validate_connection_info( + mock.sentinel.platform, mock.sentinel.connection_info) + + self.mock_client.post.assert_called_once_with( + '/endpoints/actions', + json={'validate-connection-info': { + "platform": mock.sentinel.platform, + "connection_info": mock.sentinel.connection_info, + "mapped_regions": []}}) + @mock.patch.object(endpoints.EndpointManager, '_get_endpoint_id_for_name') def test_get_endpoint_id_for_name_uuid( self, diff --git a/coriolisclient/v1/endpoints.py b/coriolisclient/v1/endpoints.py index 25afd3d..58e0403 100644 --- a/coriolisclient/v1/endpoints.py +++ b/coriolisclient/v1/endpoints.py @@ -80,7 +80,21 @@ def validate_connection(self, endpoint): data = self.client.post( '/endpoints/%s/actions' % base.getid(endpoint), json={'validate-connection': None}).json() - validate_data = data["validate-connection"] + return self._get_validation_result(data, 'validate-connection') + + def validate_connection_info(self, platform, connection_info, + regions=None): + data = self.client.post( + '/endpoints/actions', + json={'validate-connection-info': { + "platform": platform, + "connection_info": connection_info, + "mapped_regions": regions or []}}).json() + return self._get_validation_result(data, 'validate-connection-info') + + @staticmethod + def _get_validation_result(data, action): + validate_data = data[action] return validate_data.get("valid"), validate_data.get("message") def get_endpoint_id_for_name(self, endpoint): diff --git a/setup.cfg b/setup.cfg index d223995..a2789f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ coriolis.v1 = endpoint_list = coriolisclient.cli.endpoints:ListEndpoint endpoint_show = coriolisclient.cli.endpoints:ShowEndpoint endpoint_validate_connection = coriolisclient.cli.endpoints:EndpointValidateConnection + endpoint_validate_connection_info = coriolisclient.cli.endpoints:EndpointValidateConnectionInfo endpoint_export_inventory = coriolisclient.cli.endpoints:ExportEndpointInventory endpoint_instance_list = coriolisclient.cli.endpoint_instances:ListEndpointInstance