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 @@
+ +