Simone Tiraboschi has uploaded a new change for review. Change subject: packaging: setup: generate cloud-init ISO image on fly ......................................................................
packaging: setup: generate cloud-init ISO image on fly Ask a few question and generate cloud-init ISO image on fly to customize an appliance. Change-Id: Iec4f409203e3a2d6da314208e9a0f422be00ce1b Bug-Url: https://bugzilla.redhat.com/1198138 Signed-off-by: Simone Tiraboschi <stira...@redhat.com> --- M ovirt-hosted-engine-setup.spec.in M src/ovirt_hosted_engine_setup/constants.py M src/plugins/ovirt-hosted-engine-setup/vm/__init__.py M src/plugins/ovirt-hosted-engine-setup/vm/boot_cdrom.py A src/plugins/ovirt-hosted-engine-setup/vm/cloud_init.py 5 files changed, 320 insertions(+), 1 deletion(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-setup refs/changes/11/38811/1 diff --git a/ovirt-hosted-engine-setup.spec.in b/ovirt-hosted-engine-setup.spec.in index 2625f86..3dfcf45 100644 --- a/ovirt-hosted-engine-setup.spec.in +++ b/ovirt-hosted-engine-setup.spec.in @@ -53,6 +53,7 @@ Requires: ovirt-hosted-engine-ha >= 1.3.0 Requires: sanlock >= 2.8 Requires: sanlock-python >= 2.8 +Requires: genisoimage Requires: lsof Requires: iptables BuildRequires: gettext diff --git a/src/ovirt_hosted_engine_setup/constants.py b/src/ovirt_hosted_engine_setup/constants.py index d043083..0a021f3 100644 --- a/src/ovirt_hosted_engine_setup/constants.py +++ b/src/ovirt_hosted_engine_setup/constants.py @@ -291,6 +291,9 @@ HA_NOTIF_SMTP_SOURCE_EMAIL = 'source-email' HA_NOTIF_SMTP_DEST_EMAILS = 'destination-emails' BLANK_UUID = '00000000-0000-0000-0000-000000000000' + CLOUD_INIT_GENERATE = 'generate' + CLOUD_INIT_SKIP = 'skip' + CLOUD_INIT_EXISTING = 'existing' @util.export @@ -644,6 +647,12 @@ def CDROM(self): return 'OVEHOSTED_VM/vmCDRom' + GENERATE_CLOUD_INIT_ISO = 'OVEHOSTED_VM/cloudInitISO' + + CLOUD_INIT_ROOTPWD = 'OVEHOSTED_VM/cloudinitRootPwd' + + CLOUD_INIT_INSTANCEHNAME = 'OVEHOSTED_VM/cloudinitInstanceHostName' + @ohostedattrs( answerfile=True, ) @@ -772,6 +781,7 @@ CONFIG_STORAGE_BLOCKD = 'ohosted.storage.blockd.configuration.available' CONFIG_STORAGE_NFS = 'ohosted.storage.nfs.configuration.available' CONFIG_ADDITIONAL_HOST = 'ohosted.core.additional.host' + CONFIG_CLOUD_INIT_OPTIONS = 'ohosted.boot.configuration.cloud_init_options' REQUIRE_ANSWER_FILE = 'ohosted.core.require.answerfile' CONFIG_OVF_IMPORT = 'ohosted.configuration.ovf' VDSMD_START = 'ohosted.vdsm.started' diff --git a/src/plugins/ovirt-hosted-engine-setup/vm/__init__.py b/src/plugins/ovirt-hosted-engine-setup/vm/__init__.py index 04d3e57..f11fd7f 100644 --- a/src/plugins/ovirt-hosted-engine-setup/vm/__init__.py +++ b/src/plugins/ovirt-hosted-engine-setup/vm/__init__.py @@ -26,6 +26,7 @@ from . import boot_cdrom from . import boot_disk +from . import cloud_init from . import configurevm from . import cpu from . import image @@ -39,6 +40,7 @@ def createPlugins(context): boot_cdrom.Plugin(context=context) boot_disk.Plugin(context=context) + cloud_init.Plugin(context=context) configurevm.Plugin(context=context) cpu.Plugin(context=context) image.Plugin(context=context) diff --git a/src/plugins/ovirt-hosted-engine-setup/vm/boot_cdrom.py b/src/plugins/ovirt-hosted-engine-setup/vm/boot_cdrom.py index 3307c43..82b9fa6 100644 --- a/src/plugins/ovirt-hosted-engine-setup/vm/boot_cdrom.py +++ b/src/plugins/ovirt-hosted-engine-setup/vm/boot_cdrom.py @@ -112,6 +112,7 @@ after=( ohostedcons.Stages.DIALOG_TITLES_S_VM, ohostedcons.Stages.CONFIG_BOOT_DEVICE, + ohostedcons.Stages.CONFIG_CLOUD_INIT_OPTIONS, ), before=( ohostedcons.Stages.DIALOG_TITLES_E_VM, @@ -121,7 +122,10 @@ self.environment[ohostedcons.VMEnv.BOOT] == 'cdrom' or self.environment[ohostedcons.VMEnv.BOOT] == 'disk' ) and - not self.environment[ohostedcons.CoreEnv.IS_ADDITIONAL_HOST] + not self.environment[ohostedcons.CoreEnv.IS_ADDITIONAL_HOST] and + not self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] == ohostedcons.Const.CLOUD_INIT_SKIP ) ) def _customization(self): diff --git a/src/plugins/ovirt-hosted-engine-setup/vm/cloud_init.py b/src/plugins/ovirt-hosted-engine-setup/vm/cloud_init.py new file mode 100644 index 0000000..59aff85 --- /dev/null +++ b/src/plugins/ovirt-hosted-engine-setup/vm/cloud_init.py @@ -0,0 +1,302 @@ +# +# ovirt-hosted-engine-setup -- ovirt hosted engine setup +# Copyright (C) 2015 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + + +""" +VM cdrom configuration plugin. +""" + + +import crypt +import gettext +import os +import pwd +import tempfile +import uuid + +from otopi import constants as otopicons +from otopi import plugin +from otopi import util + + +from ovirt_hosted_engine_setup import constants as ohostedcons + + +def _(m): + return gettext.dgettext(message=m, domain='ovirt-hosted-engine-setup') + + +@util.export +class Plugin(plugin.PluginBase): + """ + VM cloud-init configuration plugin. + """ + + def __init__(self, context): + super(Plugin, self).__init__(context=context) + self._enable = False + self._directory_name = '' + + @plugin.event( + stage=plugin.Stages.STAGE_INIT, + ) + def _init(self): + self.environment.setdefault( + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO, + None + ) + self.environment.setdefault( + ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD, + None + ) + self.environment[otopicons.CoreEnv.LOG_FILTER_KEYS].append( + ohostedcons.EngineEnv.CLOUD_INIT_ROOTPWD + ) + self.environment.setdefault( + ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME, + None + ) + + @plugin.event( + stage=plugin.Stages.STAGE_SETUP, + ) + def _setup(self): + self.command.detect('genisoimage') + + @plugin.event( + stage=plugin.Stages.STAGE_CUSTOMIZATION, + after=( + ohostedcons.Stages.DIALOG_TITLES_S_VM, + ohostedcons.Stages.CONFIG_BOOT_DEVICE, + ), + before=( + ohostedcons.Stages.DIALOG_TITLES_E_VM, + ), + condition=lambda self: ( + self.environment[ohostedcons.VMEnv.BOOT] == 'disk' and + self.environment[ohostedcons.VMEnv.CDROM] is None and + not self.environment[ohostedcons.CoreEnv.IS_ADDITIONAL_HOST] + + ), + name=ohostedcons.Stages.CONFIG_CLOUD_INIT_OPTIONS, + ) + def _customization(self): + interactive = True + if ( + self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] is not None or + self.environment[ + ohostedcons.CLOUD_INIT_ROOTPWD + ] is not None or + self.environment[ + ohostedcons.CLOUD_INIT_INSTANCEHNAME + ] is not None + ): + interactive = False + + if interactive: + if self.dialog.queryString( + name='CLOUD_INIT_USE', + note=_( + 'Would you like to use cloud-init to customize the ' + 'appliance on the first boot ' + '(@VALUES@)[@DEFAULT@]? ' + ), + prompt=True, + validValues=(_('Yes'), _('No')), + caseSensitive=False, + default=_('Yes') + ) == _('Yes').lower(): + if self.dialog.queryString( + name='CLOUD_INIT_GENERATE', + note=_( + 'Would you like to generate on-fly a cloud-init ' + 'no-cloud ISO image\n' + 'or do you have an existing one ' + '(@VALUES@)[@DEFAULT@]? ' + ), + prompt=True, + validValues=(_('Generate'), _('Existing')), + caseSensitive=False, + default=_('Generate') + ) == _('Generate').lower(): + self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] = ohostedcons.Const.CLOUD_INIT_GENERATE + else: + self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] = ohostedcons.Const.CLOUD_INIT_EXISTING + else: + self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] = ohostedcons.Const.CLOUD_INIT_SKIP + if self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] == ohostedcons.Const.CLOUD_INIT_GENERATE: + instancehname = self.dialog.queryString( + name='CI_INSTANCE_HOSTNAME', + note=_( + "Enter appliance hostname (leave it empty to skip): " + ), + prompt=True, + default='', + ) + if instancehname: + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME + ] = instancehname + else: + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME + ] = False + while self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD + ] is None: + password = self.dialog.queryString( + name='CI_ROOT_PASSWORD', + note=_( + "Enter root password that " + 'will be used for the engine appliance ' + '(leave it empty to skip): ' + ), + prompt=True, + hidden=True, + default='', + ) + if password: + password_check = self.dialog.queryString( + name='CI_ROOT_PASSWORD', + note=_( + "Confirm appliance root password: " + ), + prompt=True, + hidden=True, + ) + if password == password_check: + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD + ] = password + else: + self.logger.error(_('Passwords do not match')) + else: + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD + ] = False + if ( + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD + ] or + self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME + ] + ): + self.environment[ + ohostedcons.VMEnv.GENERATE_CLOUD_INIT_ISO + ] = ohostedcons.Const.CLOUD_INIT_GENERATE + self._enable = True + + @plugin.event( + stage=plugin.Stages.STAGE_MISC, + condition=lambda self: self._enable, + ) + def _misc(self): + self._directory_name = tempfile.mkdtemp() + spassword = crypt.crypt( + self.environment[ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD], + '$6${salt}$'.format(salt=uuid.uuid4()) + ) + user_data = '' + f_user_data = os.path.join(self._directory_name, 'user-data') + if self.environment[ohostedcons.VMEnv.CLOUD_INIT_ROOTPWD]: + user_data = ( + '#cloud-config\n' + 'ssh_pwauth: True\n' + 'chpasswd:\n' + ' list: |\n' + ' root:{spassword}\n' + ' expire: False\n' + ).format( + spassword=spassword + ) + f = open(f_user_data, 'w') + f.write(user_data) + f.close() + + meta_data = '' + f_meta_data = os.path.join(self._directory_name, 'meta-data') + if self.environment[ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME]: + meta_data = ( + 'instance-id: {instance}\n' + 'local-hostname: {hostname}\n' + ).format( + instance=self.environment[ + ohostedcons.VMEnv.VM_UUID + ], + hostname=self.environment[ + ohostedcons.VMEnv.CLOUD_INIT_INSTANCEHNAME + ], + ) + f = open(f_meta_data, 'w') + f.write(meta_data) + f.close() + + f_cloud_init_iso = os.path.join(self._directory_name, 'seed.iso') + rc, stdout, stderr = self.execute( + ( + self.command.get('genisoimage'), + '-output', + f_cloud_init_iso, + '-volid', + 'cidata', + '-joliet', + '-rock', + '-input-charset', + 'utf-8', + f_meta_data, + f_user_data, + ) + ) + if rc != 0: + raise RuntimeError(_('Error generating cloud-init ISO image')) + os.unlink(f_meta_data) + os.unlink(f_user_data) + self.environment[ohostedcons.VMEnv.CDROM] = f_cloud_init_iso + os.chown( + self._directory_name, + pwd.getpwnam('qemu').pw_uid, + pwd.getpwnam('qemu').pw_uid, + ) + os.fchmod(f_cloud_init_iso, 0o600) + os.chown( + f_cloud_init_iso, + pwd.getpwnam('qemu').pw_uid, + pwd.getpwnam('qemu').pw_uid, + ) + + @plugin.event( + stage=plugin.Stages.STAGE_CLEANUP, + condition=lambda self: self._enable, + ) + def _cleanup(self): + os.removedirs(self._directory_name) + + +# vim: expandtab tabstop=4 shiftwidth=4 -- To view, visit https://gerrit.ovirt.org/38811 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iec4f409203e3a2d6da314208e9a0f422be00ce1b Gerrit-PatchSet: 1 Gerrit-Project: ovirt-hosted-engine-setup Gerrit-Branch: master Gerrit-Owner: Simone Tiraboschi <stira...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches