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
This commit is contained in:
parent
69b7b9b059
commit
dee22d8faa
@ -18,13 +18,18 @@ Create new public or private key for server ssh access
|
||||
.. code:: bash
|
||||
|
||||
openstack keypair create
|
||||
[--public-key <file>]
|
||||
[--public-key <file> | --private-key <file>]
|
||||
<name>
|
||||
|
||||
.. option:: --public-key <file>
|
||||
|
||||
Filename for public key to add. If not used, creates a private key.
|
||||
|
||||
.. option:: --private-key <file>
|
||||
|
||||
Filename for private key to save. If not used, print private key in
|
||||
console.
|
||||
|
||||
.. describe:: <name>
|
||||
|
||||
New public or private key name
|
||||
|
@ -41,12 +41,19 @@ class CreateKeypair(command.ShowOne):
|
||||
metavar='<name>',
|
||||
help=_("New public or private key name")
|
||||
)
|
||||
parser.add_argument(
|
||||
key_group = parser.add_mutually_exclusive_group()
|
||||
key_group.add_argument(
|
||||
'--public-key',
|
||||
metavar='<file>',
|
||||
help=_("Filename for public key to add. If not used, "
|
||||
"creates a private key.")
|
||||
)
|
||||
key_group.add_argument(
|
||||
'--private-key',
|
||||
metavar='<file>',
|
||||
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)
|
||||
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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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):
|
||||
|
||||
|
8
releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml
Normal file
8
releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml
Normal file
@ -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 <https://bugs.launchpad.net/python-openstackclient/+bug/1549410>`_]
|
Loading…
Reference in New Issue
Block a user