From e332cef01c8c70df60c57537882a118b9f560784 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Tue, 19 Feb 2019 15:29:49 +0200 Subject: [PATCH] Add key_type selection on Keypairs form Key type can be selected through both Django and Angular panels. This change will allow X509 Public Certificates to be imported or created through Horizon. Also, the key type is being showed in the keypairs list. Change-Id: I0a07b4805e6af96f06ec12d2e86708c94946e9c9 Closes-Bug: 1816041 --- openstack_dashboard/api/microversions.py | 2 + openstack_dashboard/api/nova.py | 15 ++++--- openstack_dashboard/api/rest/nova.py | 7 ++- .../dashboards/project/key_pairs/forms.py | 13 ++++-- .../dashboards/project/key_pairs/tables.py | 1 + .../dashboards/project/key_pairs/tests.py | 28 ++++++++---- .../keypair/create-keypair.controller.js | 11 ++++- .../keypair/create-keypair.html | 7 +++ .../keypair/import-keypair.controller.js | 9 +++- .../keypair/import-keypair.html | 7 +++ .../keypair/keypair.controller.js | 7 ++- .../actions/create.key-type.controller.js | 45 +++++++++++++++++++ .../keypairs/actions/create.key-type.html | 9 ++++ .../core/keypairs/actions/create.service.js | 10 ++++- .../actions/import.public-key.controller.js | 7 +++ .../import.public-key.controller.spec.js | 4 ++ .../keypairs/actions/import.public-key.html | 3 ++ .../core/keypairs/actions/import.service.js | 5 +++ .../app/core/keypairs/keypairs.module.js | 7 ++- .../test/unit/api/rest/test_nova.py | 28 ++++++++---- 20 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/create.key-type.controller.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/create.key-type.html diff --git a/openstack_dashboard/api/microversions.py b/openstack_dashboard/api/microversions.py index 071a1b2b12..8b7fa9f1a9 100644 --- a/openstack_dashboard/api/microversions.py +++ b/openstack_dashboard/api/microversions.py @@ -35,6 +35,8 @@ MICROVERSION_FEATURES = { "servergroup_user_info": ["2.13", "2.60"], "multiattach": ["2.60"], "auto_allocated_network": ["2.37", "2.42"], + "key_types": ["2.2", "2.9"], + "key_type_list": ["2.9"], }, "cinder": { "groups": ["3.27", "3.43", "3.48", "3.58"], diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 6eef3f1889..b07a4b09b7 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -380,13 +380,17 @@ def snapshot_create(request, instance_id, name): @profiler.trace -def keypair_create(request, name): - return _nova.novaclient(request).keypairs.create(name) +def keypair_create(request, name, key_type='ssh'): + microversion = get_microversion(request, 'key_types') + return _nova.novaclient(request, microversion).\ + keypairs.create(name, key_type=key_type) @profiler.trace -def keypair_import(request, name, public_key): - return _nova.novaclient(request).keypairs.create(name, public_key) +def keypair_import(request, name, public_key, key_type='ssh'): + microversion = get_microversion(request, 'key_types') + return _nova.novaclient(request, microversion).\ + keypairs.create(name, public_key, key_type) @profiler.trace @@ -396,7 +400,8 @@ def keypair_delete(request, name): @profiler.trace def keypair_list(request): - return _nova.novaclient(request).keypairs.list() + microversion = get_microversion(request, 'key_type_list') + return _nova.novaclient(request, microversion).keypairs.list() @profiler.trace diff --git a/openstack_dashboard/api/rest/nova.py b/openstack_dashboard/api/rest/nova.py index 4074d5893b..6608d71d2e 100644 --- a/openstack_dashboard/api/rest/nova.py +++ b/openstack_dashboard/api/rest/nova.py @@ -84,9 +84,12 @@ class Keypairs(generic.View): """ if 'public_key' in request.DATA: new = api.nova.keypair_import(request, request.DATA['name'], - request.DATA['public_key']) + request.DATA['public_key'], + request.DATA['key_type']) else: - new = api.nova.keypair_create(request, request.DATA['name']) + new = api.nova.keypair_create(request, + request.DATA['name'], + request.DATA['key_type']) return rest_utils.CreatedResponse( '/api/nova/keypairs/%s' % utils_http.urlquote(new.name), new.to_dict() diff --git a/openstack_dashboard/dashboards/project/key_pairs/forms.py b/openstack_dashboard/dashboards/project/key_pairs/forms.py index f63ba0ef58..0bbb8bbf9e 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/forms.py +++ b/openstack_dashboard/dashboards/project/key_pairs/forms.py @@ -41,16 +41,23 @@ class ImportKeypair(forms.SelfHandlingForm): label=_("Key Pair Name"), regex=KEYPAIR_NAME_REGEX, error_messages=KEYPAIR_ERROR_MESSAGES) + key_type = forms.ChoiceField(label=_("Key Type"), + widget=forms.SelectWidget(), + choices=[('ssh', _("SSH Key")), + ('x509', _("X509 Certificate"))], + initial='ssh') public_key = forms.CharField(label=_("Public Key"), widget=forms.Textarea()) def handle(self, request, data): try: - # Remove any new lines in the public key - data['public_key'] = NEW_LINES.sub("", data['public_key']) + # Remove any new lines in the ssh public key + if data['key_type'] == 'ssh': + data['public_key'] = NEW_LINES.sub("", data['public_key']) keypair = api.nova.keypair_import(request, data['name'], - data['public_key']) + data['public_key'], + data['key_type']) messages.success(request, _('Successfully imported public key: %s') % data['name']) diff --git a/openstack_dashboard/dashboards/project/key_pairs/tables.py b/openstack_dashboard/dashboards/project/key_pairs/tables.py index 2c374a7147..decfd8e1ed 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/tables.py +++ b/openstack_dashboard/dashboards/project/key_pairs/tables.py @@ -118,6 +118,7 @@ class KeyPairsTable(tables.DataTable): detail_link = "horizon:project:key_pairs:detail" name = tables.Column("name", verbose_name=_("Key Pair Name"), link=detail_link) + key_type = tables.Column("type", verbose_name=_("Key Pair Type")) fingerprint = tables.Column("fingerprint", verbose_name=_("Fingerprint")) def get_object_id(self, keypair): diff --git a/openstack_dashboard/dashboards/project/key_pairs/tests.py b/openstack_dashboard/dashboards/project/key_pairs/tests.py index 50461b41a3..8f47d7f2bf 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/tests.py +++ b/openstack_dashboard/dashboards/project/key_pairs/tests.py @@ -104,29 +104,34 @@ class KeyPairTests(test.TestCase): public_key = "ssh-rsa ABCDEFGHIJKLMNOPQR\r\n" \ "STUVWXYZ1234567890\r" \ "XXYYZZ user@computer\n\n" + key_type = "ssh" self.mock_keypair_import.return_value = None formData = {'method': 'ImportKeypair', 'name': key1_name, - 'public_key': public_key} + 'public_key': public_key, + 'key_type': key_type} url = reverse('horizon:project:key_pairs:import') res = self.client.post(url, formData) self.assertMessageCount(res, success=1) self.mock_keypair_import.assert_called_once_with( test.IsHttpRequest(), key1_name, - public_key.replace("\r", "").replace("\n", "")) + public_key.replace("\r", "").replace("\n", ""), + key_type) @test.create_mocks({api.nova: ('keypair_import',)}) def test_import_keypair_invalid_key(self): key_name = "new_key_pair" public_key = "ABCDEF" + key_type = "ssh" self.mock_keypair_import.side_effect = self.exceptions.nova formData = {'method': 'ImportKeypair', 'name': key_name, - 'public_key': public_key} + 'public_key': public_key, + 'key_type': key_type} url = reverse('horizon:project:key_pairs:import') res = self.client.post(url, formData, follow=True) self.assertEqual(res.redirect_chain, []) @@ -134,15 +139,17 @@ class KeyPairTests(test.TestCase): self.assertFormErrors(res, count=1, message=msg) self.mock_keypair_import.assert_called_once_with( - test.IsHttpRequest(), key_name, public_key) + test.IsHttpRequest(), key_name, public_key, key_type) def test_import_keypair_invalid_key_name(self): key_name = "invalid#key?name=!" public_key = "ABCDEF" + key_type = "ssh" formData = {'method': 'ImportKeypair', 'name': key_name, - 'public_key': public_key} + 'public_key': public_key, + 'key_type': key_type} url = reverse('horizon:project:key_pairs:import') res = self.client.post(url, formData, follow=True) self.assertEqual(res.redirect_chain, []) @@ -152,10 +159,12 @@ class KeyPairTests(test.TestCase): def test_import_keypair_space_key_name(self): key_name = " " public_key = "ABCDEF" + key_type = "ssh" formData = {'method': 'ImportKeypair', 'name': key_name, - 'public_key': public_key} + 'public_key': public_key, + 'key_type': key_type} url = reverse('horizon:project:key_pairs:import') res = self.client.post(url, formData, follow=True) self.assertEqual(res.redirect_chain, []) @@ -168,15 +177,18 @@ class KeyPairTests(test.TestCase): public_key = "ssh-rsa ABCDEFGHIJKLMNOPQR\r\n" \ "STUVWXYZ1234567890\r" \ "XXYYZZ user@computer\n\n" + key_type = "ssh" self.mock_keypair_import.return_value = None formData = {'method': 'ImportKeypair', 'name': key1_name, - 'public_key': public_key} + 'public_key': public_key, + 'key_type': key_type} url = reverse('horizon:project:key_pairs:import') res = self.client.post(url, formData) self.assertMessageCount(res, success=1) self.mock_keypair_import.assert_called_once_with( test.IsHttpRequest(), key1_name, - public_key.replace("\r", "").replace("\n", "")) + public_key.replace("\r", "").replace("\n", ""), + key_type) diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js index b1a1bec8f5..7d706ceb44 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js @@ -46,9 +46,18 @@ ctrl.generate = generate; ctrl.keypair = ''; + ctrl.key_types = { + 'ssh': gettext("SSH Key"), + 'x509': gettext("X509 Certificate") + }; + ctrl.key_type = 'ssh'; ctrl.keypairExistsError = gettext('Keypair already exists or name contains bad characters.'); ctrl.copyPrivateKey = copyPrivateKey; + ctrl.onKeyTypeChange = function (keyType) { + ctrl.key_type = keyType; + }; + /* * @ngdoc function * @name doesKeypairExist @@ -60,7 +69,7 @@ } function generate() { - nova.createKeypair({name: ctrl.keypair}).then(onKeypairCreated); + nova.createKeypair({name: ctrl.keypair, key_type: ctrl.key_type}).then(onKeypairCreated); function onKeypairCreated(data) { ctrl.createdKeypair = data.data; diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html index 61bbab6c93..22028c6dc4 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html @@ -28,6 +28,13 @@ ng-show="(ctrl.doesKeypairExist() || wizardForm.$invalid) && wizardForm.$dirty"> {$ ctrl.keypairExistsError $} + +
+ + + diff --git a/openstack_dashboard/static/app/core/keypairs/actions/create.service.js b/openstack_dashboard/static/app/core/keypairs/actions/create.service.js index 526e2d6b3d..284d57e4c2 100644 --- a/openstack_dashboard/static/app/core/keypairs/actions/create.service.js +++ b/openstack_dashboard/static/app/core/keypairs/actions/create.service.js @@ -51,6 +51,10 @@ title: gettext("Key Pair Name"), type: "string", pattern: "^[A-Za-z0-9 -_]+$" + }, + "key_type": { + title: gettext("Key Type"), + type: "string" } } }; @@ -76,6 +80,10 @@ } }, required: true + }, + { + type: "template", + templateUrl: basePath + "actions/create.key-type.html" } ] } @@ -102,7 +110,7 @@ function perform() { getKeypairs(); - model = {}; + model = { key_type: 'ssh' }; var config = { "title": caption, "submitText": caption, diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js index 71ef415dde..12d2e896d9 100644 --- a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js @@ -35,7 +35,14 @@ function importPublicKeyController($scope) { var ctrl = this; ctrl.title = $scope.schema.properties.public_key.title; + ctrl.key_types = { + 'ssh': gettext("SSH Key"), + 'x509': gettext("X509 Certificate") + }; ctrl.public_key = ""; + ctrl.onKeyTypeChange = function (keyType) { + $scope.model.key_type = keyType; + }; ctrl.onPublicKeyChange = function (publicKey) { $scope.model.public_key = publicKey; }; diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js index 08e98ef4c2..c8c8df72fa 100644 --- a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js @@ -23,12 +23,16 @@ scope = _$rootScope_.$new(); scope.schema = { properties: { + key_type: { + title: "Key Type" + }, public_key: { title: 'Public Key' } } }; scope.model = { + key_type: 'ssh', public_key: '' }; diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html index 5134c40222..57c2d0cb44 100644 --- a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html @@ -1,4 +1,7 @@
+ +