From 3a2e82ab75c605cfd6fd8d26d7dddd3cce1f812d Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Tue, 23 Aug 2022 17:59:37 +0300 Subject: [PATCH] Extend settings with OPENSTACK_SERVER_DEFAULT_USER_DATA The new setting should allow an administrator to specify default user_data for new VMs. The default user_data can be a string template that accepts request object which has info about the user, so the default user_data can be personalized. Change-Id: I86ac21bf82c1667135abd4f20fb4514da0899450 --- doc/source/configuration/settings.rst | 13 ++++++++++++- .../widgets/load-edit/load-edit.directive.js | 8 ++++++++ .../widgets/load-edit/load-edit.directive.spec.js | 14 ++++++++++++-- openstack_dashboard/api/rest/config.py | 14 ++++++++++++++ .../configuration/configuration.html | 1 + .../launch-instance-model.service.js | 5 +++++ .../launch-instance-model.service.spec.js | 9 +++++++-- openstack_dashboard/defaults.py | 3 +++ .../add_default_user_data-76d9c17e474fc34e.yaml | 7 +++++++ 9 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add_default_user_data-76d9c17e474fc34e.yaml diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index 674cc0de90..5128582569 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -2224,7 +2224,7 @@ hide_create_volume Default: ``False`` -This setting allow your to hide the "Create New Volume" option and rely on the +This setting allows you to hide the "Create New Volume" option and rely on the default value you select with ``create_volume`` to be the most suitable for your users. @@ -2314,6 +2314,17 @@ specified in this setting is not found in the availability zone list, the setting will be ignored and the behavior will be same as when ``Any`` is specified. +OPENSTACK_SERVER_DEFAULT_USER_DATA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 22.3.0(Zed) + +Default: ``""`` + +An administrator can specify a default user data (e.g. comments or instructions +for cloudinit) via this settings. It can be a string or template string that +accepts a request object. + OPENSTACK_ENABLE_PASSWORD_RETRIEVE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/horizon/static/framework/widgets/load-edit/load-edit.directive.js b/horizon/static/framework/widgets/load-edit/load-edit.directive.js index 7b5e94babc..4f33ebfff6 100644 --- a/horizon/static/framework/widgets/load-edit/load-edit.directive.js +++ b/horizon/static/framework/widgets/load-edit/load-edit.directive.js @@ -26,6 +26,7 @@ * * @param {object} title * @param {object} model + * @param {object} modelDefaultValue * @param {object} maxBytes * @param {object} key * @param {object} required @@ -49,6 +50,7 @@ scope: { title: '@', model: '=', + modelDefaultValue: '=', maxBytes: '@', key: '@', required: '=', @@ -141,6 +143,12 @@ textarea.focus(); } + $scope.$watch('modelDefaultValue', function(newValue, oldValue) { + if (newValue !== undefined && oldValue !== newValue) { + updateTextArea(newValue); + } + }); + /* The length property for string shows only number of character. * If text includes multibyte string, it doesn't mean number of bytes. * So to count bytes, convert to Blob object and get its size. diff --git a/horizon/static/framework/widgets/load-edit/load-edit.directive.spec.js b/horizon/static/framework/widgets/load-edit/load-edit.directive.spec.js index b08514040e..c5031b96c8 100644 --- a/horizon/static/framework/widgets/load-edit/load-edit.directive.spec.js +++ b/horizon/static/framework/widgets/load-edit/load-edit.directive.spec.js @@ -39,8 +39,9 @@ $compile = $injector.get('$compile'); key = 'elementKey'; element = $compile( - '' + '' + + '' )($scope); $scope.$apply(); })); @@ -75,6 +76,15 @@ expect(element.isolateScope().model).toBe('user input'); }); + + it('should update text area with default user data', function () { + element.isolateScope().modelDefaultValue = 'default user data'; + $scope.$apply(); + textarea.trigger('input'); + $scope.$apply(); + + expect(element.isolateScope().model).toBe('default user data'); + }); }); describe('onFileLoadListener', function() { diff --git a/openstack_dashboard/api/rest/config.py b/openstack_dashboard/api/rest/config.py index eeea25e892..175d83f143 100644 --- a/openstack_dashboard/api/rest/config.py +++ b/openstack_dashboard/api/rest/config.py @@ -17,6 +17,7 @@ from datetime import datetime from django.conf import settings from django.http import JsonResponse +import django.template as django_template from django.views import generic import pytz @@ -59,6 +60,7 @@ class Settings(generic.View): in settings_allowed if k not in self.SPECIALS} plain_settings.update(self.SPECIALS) plain_settings.update(self.disk_formats(request)) + plain_settings.update(self.default_user_data(request)) return plain_settings def disk_formats(self, request): @@ -70,6 +72,18 @@ class Settings(generic.View): for (value, name) in api.glance.get_image_formats(request) ]} + def default_user_data(self, request): + template_code = settings.OPENSTACK_SERVER_DEFAULT_USER_DATA + if template_code: + engine = django_template.engine.Engine.get_default() + template = engine.from_string(template_code) + default_user_data = template.render( + django_template.Context(dict(request=request)) + ) + else: + default_user_data = "" + return {"OPENSTACK_SERVER_DEFAULT_USER_DATA": default_user_data} + @urls.register class Timezones(generic.View): diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.html index a9fb915825..1ef19adec8 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.html +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.html @@ -6,6 +6,7 @@ diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js index 810858829c..26086bc14e 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js @@ -182,6 +182,7 @@ description: null, // REQUIRED Server Key. Null allowed. user_data: '', + default_user_data: '', disk_config: 'AUTO', // REQUIRED flavor: null, @@ -249,6 +250,10 @@ function (response) { model.defaultBootSource = response; }); + settings.getSetting("OPENSTACK_SERVER_DEFAULT_USER_DATA").then( + function (response) { + model.newInstanceSpec.default_user_data = response.OPENSTACK_SERVER_DEFAULT_USER_DATA; + }); promise = $q.all([ launchInstanceDefaults.then(setDefaultValues, noop), diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js index 4b6acfdb1d..fa15af9100 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js @@ -186,7 +186,8 @@ disable_volume_snapshot: false, default_availability_zone: 'Any' }, - DEFAULT_BOOT_SOURCE: 'image' + DEFAULT_BOOT_SOURCE: 'image', + OPENSTACK_SERVER_DEFAULT_USER_DATA: '' }; IMAGE = {type: 'image', label: 'Image', selected: true}; VOLUME = {type: 'volume', label: 'Volume', selected: false}; @@ -883,7 +884,7 @@ // This is here to ensure that as people add/change items, they // don't forget to implement tests for them. it('has the right number of properties', function() { - expect(Object.keys(model.newInstanceSpec).length).toBe(22); + expect(Object.keys(model.newInstanceSpec).length).toBe(23); }); it('sets availability zone to null', function() { @@ -910,6 +911,10 @@ expect(model.newInstanceSpec.user_data).toBe(''); }); + it('sets default user data to an empty string', function() { + expect(model.newInstanceSpec.default_user_data).toBe(''); + }); + it('sets disk config to AUTO', function() { expect(model.newInstanceSpec.disk_config).toBe('AUTO'); }); diff --git a/openstack_dashboard/defaults.py b/openstack_dashboard/defaults.py index 0023d2aaf4..8d8e4ae8a1 100644 --- a/openstack_dashboard/defaults.py +++ b/openstack_dashboard/defaults.py @@ -256,6 +256,9 @@ LAUNCH_INSTANCE_DEFAULTS = { 'default_availability_zone': 'Any', } +# A Django template astring that will be used as default user_data for new VMs +OPENSTACK_SERVER_DEFAULT_USER_DATA = "" + # The absolute path to the directory where message files are collected. # The message file must have a .json file extension. When the user logins to # horizon, the message files collected are processed and displayed to the user. diff --git a/releasenotes/notes/add_default_user_data-76d9c17e474fc34e.yaml b/releasenotes/notes/add_default_user_data-76d9c17e474fc34e.yaml new file mode 100644 index 0000000000..0a533cfd38 --- /dev/null +++ b/releasenotes/notes/add_default_user_data-76d9c17e474fc34e.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The new setting ``OPENSTACK_SERVER_DEFAULT_USER_DATA`` allows an + administrator to specify a default user data (e.g. comments or + instructions for cloudinit) for new VMs. It can be a raw string or string + template that accepts the request.