From dee22d8faa0c8a0da1d6ff62c0997c2cc770b759 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 27 Feb 2017 14:35:05 +0800 Subject: [PATCH] Add "--private-key" option for "keypair create" Aim to specify the private key file to save when keypair is created. That is a convenient way to save private key in OSC interactive mode, avoid to copy CLI output, then paste it into file. Change-Id: I119d2f2a3323d17ecbe3de4e27f35e1ceef6e0a5 Closes-Bug: #1549410 --- doc/source/command-objects/keypair.rst | 7 ++++- openstackclient/compute/v2/keypair.py | 31 +++++++++++++++++-- .../functional/compute/v2/test_keypair.py | 21 +++++++++++++ .../tests/unit/compute/v2/test_keypair.py | 31 +++++++++++++++++++ .../notes/bug-1549410-8df3a4b12fe13ffa.yaml | 8 +++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index 47acf7f2c1..a539f0a208 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -18,13 +18,18 @@ Create new public or private key for server ssh access .. code:: bash openstack keypair create - [--public-key ] + [--public-key | --private-key ] .. option:: --public-key Filename for public key to add. If not used, creates a private key. +.. option:: --private-key + + Filename for private key to save. If not used, print private key in + console. + .. describe:: New public or private key name diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 2a8524d62b..851cced0e9 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -41,12 +41,19 @@ class CreateKeypair(command.ShowOne): metavar='', help=_("New public or private key name") ) - parser.add_argument( + key_group = parser.add_mutually_exclusive_group() + key_group.add_argument( '--public-key', metavar='', help=_("Filename for public key to add. If not used, " "creates a private key.") ) + key_group.add_argument( + '--private-key', + metavar='', + help=_("Filename for private key to save. If not used, " + "print private key in console.") + ) return parser def take_action(self, parsed_args): @@ -69,13 +76,31 @@ class CreateKeypair(command.ShowOne): public_key=public_key, ) + private_key = parsed_args.private_key + # Save private key into specified file + if private_key: + try: + with io.open( + os.path.expanduser(parsed_args.private_key), 'w+' + ) as p: + p.write(keypair.private_key) + except IOError as e: + msg = _("Key file %(private_key)s can not be saved: " + "%(exception)s") + raise exceptions.CommandError( + msg % {"private_key": parsed_args.private_key, + "exception": e} + ) # NOTE(dtroyer): how do we want to handle the display of the private # key when it needs to be communicated back to the user # For now, duplicate nova keypair-add command output info = {} - if public_key: + if public_key or private_key: info.update(keypair._info) - del info['public_key'] + if 'public_key' in info: + del info['public_key'] + if 'private_key' in info: + del info['private_key'] return zip(*sorted(six.iteritems(info))) else: sys.stdout.write(keypair.private_key) diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py index 01078c6136..1e1a03d67d 100644 --- a/openstackclient/tests/functional/compute/v2/test_keypair.py +++ b/openstackclient/tests/functional/compute/v2/test_keypair.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import tempfile from openstackclient.tests.functional import base @@ -100,6 +101,26 @@ class KeypairTests(KeypairBase): ) self.assertIn('tmpkey', raw_output) + def test_keypair_create_private_key(self): + """Test for create keypair with --private-key option. + + Test steps: + 1) Create keypair with private key file + 2) Delete keypair + """ + with tempfile.NamedTemporaryFile() as f: + cmd_output = json.loads(self.openstack( + 'keypair create -f json --private-key %s tmpkey' % f.name, + )) + self.addCleanup(self.openstack, 'keypair delete tmpkey') + self.assertEqual('tmpkey', cmd_output.get('name')) + self.assertIsNotNone(cmd_output.get('user_id')) + self.assertIsNotNone(cmd_output.get('fingerprint')) + pk_content = f.read() + self.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', pk_content) + self.assertRegex(pk_content, "[0-9A-Za-z+/]+[=]{0,3}\n") + self.assertInOutput('-----END RSA PRIVATE KEY-----', pk_content) + def test_keypair_create(self): """Test keypair create command. diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index efc5463cc6..d6f5ecf457 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -15,6 +15,7 @@ import mock from mock import call +import uuid from osc_lib import exceptions from osc_lib import utils @@ -115,6 +116,36 @@ class TestKeypairCreate(TestKeypair): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_keypair_create_private_key(self): + tmp_pk_file = '/tmp/kp-file-' + uuid.uuid4().hex + arglist = [ + '--private-key', tmp_pk_file, + self.keypair.name, + ] + verifylist = [ + ('private_key', tmp_pk_file), + ('name', self.keypair.name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + self.keypair.name, + public_key=None + ) + + mock_open.assert_called_once_with(tmp_pk_file, 'w+') + m_file.write.assert_called_once_with(self.keypair.private_key) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestKeypairDelete(TestKeypair): diff --git a/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml b/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml new file mode 100644 index 0000000000..ac37f81636 --- /dev/null +++ b/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--private-key`` option for ``keypair create`` command to specify the + private key file to save when a keypair is created, removing the need to + copy the output and paste it into a new file. This is a convenient way + to save private key in OSC interactive mode. + [Bug `1549410 `_]