diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 02fc5816b8..15a5084d0d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -31,6 +31,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from oslo_utils import strutils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -193,6 +194,24 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): return info +def boolenv(*vars, default=False): + """Search for the first defined of possibly many bool-like env vars. + + Returns the first environment variable defined in vars, or returns the + default. + + :param vars: Arbitrary strings to search for. Case sensitive. + :param default: The default to return if no value found. + :returns: A boolean corresponding to the value found, else the default if + no value found. + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return strutils.bool_from_string(value) + return default + + class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -1322,6 +1341,15 @@ class DeleteServer(command.Command): action='store_true', help=_('Force delete server(s)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Delete server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -1339,7 +1367,8 @@ class DeleteServer(command.Command): compute_client = self.app.client_manager.compute for server in parsed_args.server: server_obj = utils.find_resource( - compute_client.servers, server) + compute_client.servers, server, + all_tenants=parsed_args.all_projects) if parsed_args.force: compute_client.servers.force_delete(server_obj.id) @@ -1347,11 +1376,13 @@ class DeleteServer(command.Command): compute_client.servers.delete(server_obj.id) if parsed_args.wait: - if not utils.wait_for_delete(compute_client.servers, - server_obj.id, - callback=_show_progress): - LOG.error(_('Error deleting server: %s'), - server_obj.id) + if not utils.wait_for_delete( + compute_client.servers, + server_obj.id, + callback=_show_progress, + ): + msg = _('Error deleting server: %s') + LOG.error(msg, server_obj.id) self.app.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1446,8 +1477,11 @@ class ListServer(command.Lister): parser.add_argument( '--all-projects', action='store_true', - default=bool(int(os.environ.get("ALL_PROJECTS", 0))), - help=_('Include all projects (admin only)'), + default=boolenv('ALL_PROJECTS'), + help=_( + 'Include all projects (admin only) ' + '(can be specified using the ALL_PROJECTS envvar)' + ), ) parser.add_argument( '--project', @@ -3939,6 +3973,15 @@ class StartServer(command.Command): nargs="+", help=_('Server(s) to start (name or ID)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Start server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) return parser def take_action(self, parsed_args): @@ -3947,6 +3990,7 @@ class StartServer(command.Command): utils.find_resource( compute_client.servers, server, + all_tenants=parsed_args.all_projects, ).start() @@ -3961,6 +4005,15 @@ class StopServer(command.Command): nargs="+", help=_('Server(s) to stop (name or ID)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Stop server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) return parser def take_action(self, parsed_args): @@ -3969,6 +4022,7 @@ class StopServer(command.Command): utils.find_resource( compute_client.servers, server, + all_tenants=parsed_args.all_projects, ).stop() diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 16885eb86a..9a01758c8c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2913,6 +2913,28 @@ class TestServerDelete(TestServer): self.servers_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + @mock.patch.object(common_utils, 'find_resource') + def test_server_delete_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): servers = self.setup_servers_mock(count=1) @@ -6781,6 +6803,28 @@ class TestServerStart(TestServer): def test_server_start_multi_servers(self): self.run_method_with_servers('start', 3) + @mock.patch.object(common_utils, 'find_resource') + def test_server_start_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + class TestServerStop(TestServer): @@ -6801,6 +6845,28 @@ class TestServerStop(TestServer): def test_server_stop_multi_servers(self): self.run_method_with_servers('stop', 3) + @mock.patch.object(common_utils, 'find_resource') + def test_server_start_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + class TestServerSuspend(TestServer): diff --git a/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml b/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml new file mode 100644 index 0000000000..b14eb50414 --- /dev/null +++ b/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``server delete``, ``server start`` and ``server stop`` commands now + support the ``--all-projects`` option. This allows you to perform the + specified action on a server in another project using the server name. + This is an admin-only action by default. diff --git a/requirements.txt b/requirements.txt index 5077ad779b..d3a17e9a48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ iso8601>=0.1.11 # MIT openstacksdk>=0.53.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=17.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0