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

Reply via email to