From 17f641e1c3ac58acdc39d064369395198d2654d2 Mon Sep 17 00:00:00 2001
From: He Jie Xu <hejie.xu@intel.com>
Date: Tue, 19 Jun 2018 12:19:49 +0000
Subject: [PATCH] Compute: Add user id support for keypair

This patch adds functionality of specific the user id when create,
delete, show and list keypairs.

Change-Id: Ib826f1f4f5a73d1875ba0f02e124b3222c4d05ed
Co-Authored-By: tianhui <tianhui@awcloud.com>
---
 openstackclient/compute/v2/keypair.py         |  86 ++++++++-
 .../tests/unit/compute/v2/test_keypair.py     | 171 +++++++++++++++++-
 .../keypair-user-id-db694210695a0ee0.yaml     |   6 +
 3 files changed, 250 insertions(+), 13 deletions(-)
 create mode 100644 releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml

diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py
index ae653e76ee..157dd8cd90 100644
--- a/openstackclient/compute/v2/keypair.py
+++ b/openstackclient/compute/v2/keypair.py
@@ -64,10 +64,20 @@ class CreateKeypair(command.ShowOne):
                 "(Supported by API versions '2.2' - '2.latest')"
             ),
         )
+        parser.add_argument(
+            '--user',
+            metavar='<user>',
+            help=_(
+                'The owner of the keypair. (admin only) (name or ID). '
+                'Requires ``--os-compute-api-version`` 2.10 or greater.'
+            ),
+        )
+        identity_common.add_user_domain_option_to_parser(parser)
         return parser
 
     def take_action(self, parsed_args):
         compute_client = self.app.client_manager.compute
+        identity_client = self.app.client_manager.identity
 
         public_key = parsed_args.public_key
         if public_key:
@@ -89,12 +99,26 @@ class CreateKeypair(command.ShowOne):
             if compute_client.api_version < api_versions.APIVersion('2.2'):
                 msg = _(
                     '--os-compute-api-version 2.2 or greater is required to '
-                    'support the --type option.'
+                    'support the --type option'
                 )
                 raise exceptions.CommandError(msg)
 
             kwargs['key_type'] = parsed_args.type
 
+        if parsed_args.user:
+            if compute_client.api_version < api_versions.APIVersion('2.10'):
+                msg = _(
+                    '--os-compute-api-version 2.10 or greater is required to '
+                    'support the --user option'
+                )
+                raise exceptions.CommandError(msg)
+
+            kwargs['user_id'] = identity_common.find_user(
+                identity_client,
+                parsed_args.user,
+                parsed_args.user_domain,
+            ).id
+
         keypair = compute_client.keypairs.create(**kwargs)
 
         private_key = parsed_args.private_key
@@ -139,16 +163,43 @@ class DeleteKeypair(command.Command):
             nargs='+',
             help=_("Name of key(s) to delete (name only)")
         )
+        parser.add_argument(
+            '--user',
+            metavar='<user>',
+            help=_(
+                'The owner of the keypair. (admin only) (name or ID). '
+                'Requires ``--os-compute-api-version`` 2.10 or greater.'
+            ),
+        )
+        identity_common.add_user_domain_option_to_parser(parser)
         return parser
 
     def take_action(self, parsed_args):
         compute_client = self.app.client_manager.compute
+        identity_client = self.app.client_manager.identity
+
+        kwargs = {}
         result = 0
+
+        if parsed_args.user:
+            if compute_client.api_version < api_versions.APIVersion('2.10'):
+                msg = _(
+                    '--os-compute-api-version 2.10 or greater is required to '
+                    'support the --user option'
+                )
+                raise exceptions.CommandError(msg)
+
+            kwargs['user_id'] = identity_common.find_user(
+                identity_client,
+                parsed_args.user,
+                parsed_args.user_domain,
+            ).id
+
         for n in parsed_args.name:
             try:
                 data = utils.find_resource(
                     compute_client.keypairs, n)
-                compute_client.keypairs.delete(data.name)
+                compute_client.keypairs.delete(data.name, **kwargs)
             except Exception as e:
                 result += 1
                 LOG.error(_("Failed to delete key with name "
@@ -229,12 +280,39 @@ class ShowKeypair(command.ShowOne):
             default=False,
             help=_("Show only bare public key paired with the generated key")
         )
+        parser.add_argument(
+            '--user',
+            metavar='<user>',
+            help=_(
+                'The owner of the keypair. (admin only) (name or ID). '
+                'Requires ``--os-compute-api-version`` 2.10 or greater.'
+            ),
+        )
+        identity_common.add_user_domain_option_to_parser(parser)
         return parser
 
     def take_action(self, parsed_args):
         compute_client = self.app.client_manager.compute
-        keypair = utils.find_resource(compute_client.keypairs,
-                                      parsed_args.name)
+        identity_client = self.app.client_manager.identity
+
+        kwargs = {}
+
+        if parsed_args.user:
+            if compute_client.api_version < api_versions.APIVersion('2.10'):
+                msg = _(
+                    '--os-compute-api-version 2.10 or greater is required to '
+                    'support the --user option'
+                )
+                raise exceptions.CommandError(msg)
+
+            kwargs['user_id'] = identity_common.find_user(
+                identity_client,
+                parsed_args.user,
+                parsed_args.user_domain,
+            ).id
+
+        keypair = utils.find_resource(
+            compute_client.keypairs, parsed_args.name, **kwargs)
 
         info = {}
         info.update(keypair._info)
diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py
index 5e6a47414f..56cbe3f100 100644
--- a/openstackclient/tests/unit/compute/v2/test_keypair.py
+++ b/openstackclient/tests/unit/compute/v2/test_keypair.py
@@ -38,6 +38,15 @@ class TestKeypair(compute_fakes.TestComputev2):
         self.keypairs_mock = self.app.client_manager.compute.keypairs
         self.keypairs_mock.reset_mock()
 
+        # Initialize the user mock
+        self.users_mock = self.app.client_manager.identity.users
+        self.users_mock.reset_mock()
+        self.users_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.USER),
+            loaded=True,
+        )
+
 
 class TestKeypairCreate(TestKeypair):
 
@@ -226,6 +235,54 @@ class TestKeypairCreate(TestKeypair):
                 '--os-compute-api-version 2.2 or greater is required',
                 str(ex))
 
+    def test_key_pair_create_with_user(self):
+
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.10')
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypair.name,
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', self.keypair.name),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.keypairs_mock.create.assert_called_with(
+            name=self.keypair.name,
+            public_key=None,
+            user_id=identity_fakes.user_id,
+        )
+
+        self.assertEqual({}, columns)
+        self.assertEqual({}, data)
+
+    def test_key_pair_create_with_user_pre_v210(self):
+
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.9')
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypair.name,
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', self.keypair.name),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        ex = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-compute-api-version 2.10 or greater is required', str(ex))
+
 
 class TestKeypairDelete(TestKeypair):
 
@@ -301,6 +358,51 @@ class TestKeypairDelete(TestKeypair):
                 self.keypairs[0].name
             )
 
+    def test_keypair_delete_with_user(self):
+
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.10')
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypairs[0].name
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', [self.keypairs[0].name]),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        ret = self.cmd.take_action(parsed_args)
+
+        self.assertIsNone(ret)
+        self.keypairs_mock.delete.assert_called_with(
+            self.keypairs[0].name,
+            user_id=identity_fakes.user_id,
+        )
+
+    def test_keypair_delete_with_user_pre_v210(self):
+
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.9')
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypairs[0].name
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', [self.keypairs[0].name]),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        ex = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-compute-api-version 2.10 or greater is required', str(ex))
+
 
 class TestKeypairList(TestKeypair):
 
@@ -310,14 +412,6 @@ class TestKeypairList(TestKeypair):
     def setUp(self):
         super(TestKeypairList, self).setUp()
 
-        self.users_mock = self.app.client_manager.identity.users
-        self.users_mock.reset_mock()
-        self.users_mock.get.return_value = fakes.FakeResource(
-            None,
-            copy.deepcopy(identity_fakes.USER),
-            loaded=True,
-        )
-
         self.keypairs_mock.list.return_value = self.keypairs
 
         # Get the command object to test
@@ -477,11 +571,14 @@ class TestKeypairShow(TestKeypair):
         verifylist = [
             ('name', self.keypair.name)
         ]
-
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
         columns, data = self.cmd.take_action(parsed_args)
 
+        self.keypairs_mock.get.assert_called_with(
+            self.keypair.name,
+        )
+
         self.assertEqual(self.columns, columns)
         self.assertEqual(self.data, data)
 
@@ -502,3 +599,59 @@ class TestKeypairShow(TestKeypair):
 
         self.assertEqual({}, columns)
         self.assertEqual({}, data)
+
+    def test_keypair_show_with_user(self):
+
+        # overwrite the setup one because we want to omit private_key
+        self.keypair = compute_fakes.FakeKeypair.create_one_keypair(
+            no_pri=True)
+        self.keypairs_mock.get.return_value = self.keypair
+
+        self.data = (
+            self.keypair.fingerprint,
+            self.keypair.name,
+            self.keypair.type,
+            self.keypair.user_id
+        )
+
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.10')
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypair.name,
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', self.keypair.name)
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.users_mock.get.assert_called_with(identity_fakes.user_name)
+        self.keypairs_mock.get.assert_called_with(
+            self.keypair.name,
+        )
+
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, data)
+
+    def test_keypair_show_with_user_pre_v210(self):
+
+        arglist = [
+            '--user', identity_fakes.user_name,
+            self.keypair.name,
+        ]
+        verifylist = [
+            ('user', identity_fakes.user_name),
+            ('name', self.keypair.name)
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        ex = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-compute-api-version 2.10 or greater is required', str(ex))
diff --git a/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml b/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml
new file mode 100644
index 0000000000..073973e2cf
--- /dev/null
+++ b/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``--user`` option to the ``keypair create``, ``keypair delete``, and
+    ``keypair show`` commands. Only available starting with
+    ``--os-compute-api-version 2.10``.