diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index f7b860df8d..7be1bb9fb3 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1356,14 +1356,26 @@ class CreateServer(command.ShowOne): 'This option requires cloud support.' ), ) - parser.add_argument( + secgroups = parser.add_mutually_exclusive_group() + secgroups.add_argument( + '--no-security-group', + dest='security_groups', + action='store_const', + const=[], + help=_( + 'Do not associate a security group with ports attached to ' + 'this server. This does not affect the security groups ' + 'associated with pre-existing ports.' + ), + ) + secgroups.add_argument( '--security-group', metavar='', action='append', - default=[], dest='security_groups', help=_( - 'Security group to assign to this server (name or ID) ' + 'Security group to associate with ports attached to this ' + 'server (name or ID) ' '(repeat option to set multiple groups)' ), ) @@ -1980,22 +1992,24 @@ class CreateServer(command.ShowOne): networks = 'auto' # Check security group(s) exist and convert ID to name - security_groups = [] - if self.app.client_manager.is_network_endpoint_enabled(): - network_client = self.app.client_manager.network - for security_group in parsed_args.security_groups: - sg = network_client.find_security_group( - security_group, ignore_missing=False - ) - # Use security group ID to avoid multiple security group have - # same name in neutron networking backend - security_groups.append({'name': sg.id}) - else: # nova-network - for security_group in parsed_args.security_groups: - sg = compute_v2.find_security_group( - compute_client, security_group - ) - security_groups.append({'name': sg['name']}) + security_groups = None + if parsed_args.security_groups is not None: + security_groups = [] + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + for security_group in parsed_args.security_groups: + sg = network_client.find_security_group( + security_group, ignore_missing=False + ) + # Use security group ID to avoid multiple security group + # have same name in neutron networking backend + security_groups.append({'name': sg.id}) + else: # nova-network + for security_group in parsed_args.security_groups: + sg = compute_v2.find_security_group( + compute_client, security_group + ) + security_groups.append({'name': sg['name']}) hints = {} for key, values in parsed_args.hints.items(): @@ -2058,7 +2072,7 @@ class CreateServer(command.ShowOne): if files: kwargs['personality'] = files - if security_groups: + if security_groups is not None: kwargs['security_groups'] = security_groups if block_device_mapping_v2: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index a07e33333f..20848842ef 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1544,6 +1544,60 @@ class TestServerCreate(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_no_security_group(self): + arglist = [ + '--image', + self.image.id, + '--flavor', + self.flavor.id, + '--no-security-group', + self.server.name, + ] + verifylist = [ + ('image', self.image.id), + ('flavor', self.flavor.id), + ('key_name', None), + ('properties', None), + ('security_groups', []), + ('hints', {}), + ('server_group', None), + ('config_drive', False), + ('password', None), + ('server_name', self.server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.compute_sdk_client.find_flavor.assert_has_calls( + [mock.call(self.flavor.id, ignore_missing=False)] * 2 + ) + self.network_client.find_security_group.assert_not_called() + self.image_client.find_image.assert_called_once_with( + self.image.id, ignore_missing=False + ) + self.compute_sdk_client.create_server.assert_called_once_with( + name=self.server.name, + image_id=self.image.id, + flavor_id=self.flavor.id, + min_count=1, + max_count=1, + security_groups=[], + networks=[], + block_device_mapping=[ + { + 'uuid': self.image.id, + 'boot_index': 0, + 'source_type': 'image', + 'destination_type': 'local', + 'delete_on_termination': True, + }, + ], + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_network(self): network_net1 = network_fakes.create_one_network() network_net2 = network_fakes.create_one_network() diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 2a39ca4831..607047f14e 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -93,8 +93,14 @@ class TestCommand(TestCase): f"Argument parse failed: {stderr.getvalue()}" ) for av in verify_args: - attr, value = av + attr, expected_value = av if attr: + actual_value = getattr(parsed_args, attr) self.assertIn(attr, parsed_args) - self.assertEqual(value, getattr(parsed_args, attr)) + self.assertEqual( + expected_value, + actual_value, + f'args.{attr}: expected: {expected_value}, got: ' + f'{actual_value}', + ) return parsed_args diff --git a/releasenotes/notes/server-create-no-security-group-option-627697bddae429b1.yaml b/releasenotes/notes/server-create-no-security-group-option-627697bddae429b1.yaml new file mode 100644 index 0000000000..b2169d36e3 --- /dev/null +++ b/releasenotes/notes/server-create-no-security-group-option-627697bddae429b1.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``server create`` command now supports a ``--no-security-group`` + option. When provided, no security groups will be associated with ports + created and attached to the server during server creation. This does not + affect pre-created ports.