[virt-tools-list] [virt-manager PATCH v3] Introduction of cloud-init configuration in virt-install

Athina Plaskasoviti athina.plaskasoviti at gmail.com
Fri Jun 28 16:05:18 UTC 2019


Usage:
--cloud-init

Signed-off-by: Athina Plaskasoviti <athina.plaskasoviti at gmail.com>
---
 virt-install                        |  5 +++
 virtinst/cli.py                     | 25 +++++++++++++
 virtinst/install/cloudinit.py       | 57 +++++++++++++++++++++++++++++
 virtinst/install/installer.py       | 17 +++++++++
 virtinst/install/installerinject.py | 20 +++++-----
 5 files changed, 115 insertions(+), 9 deletions(-)
 create mode 100644 virtinst/install/cloudinit.py

diff --git a/virt-install b/virt-install
index ee2b9006..1ccf7a31 100755
--- a/virt-install
+++ b/virt-install
@@ -444,6 +444,9 @@ def build_installer(options, guest, installdata):
         installer.set_initrd_injections(options.initrd_inject)
     if options.autostart:
         installer.autostart = True
+    if options.cloud_init:
+        cloudinit_data = cli.parse_cloud_init(options.cloud_init)
+        installer.set_cloudinit_data(cloudinit_data)
 
     return installer
 
@@ -821,6 +824,8 @@ def parse_args():
                     help=_("Perform an unattended installation"))
     insg.add_argument("--install",
             help=_("Specify fine grained install options"))
+    insg.add_argument("--cloud-init", nargs="?", const=1,
+                    help=_("Perform a cloud image installation, configuring cloud-init"))
 
     # Takes a URL and just prints to stdout the detected distro name
     insg.add_argument("--test-media-detection", help=argparse.SUPPRESS)
diff --git a/virtinst/cli.py b/virtinst/cli.py
index 9a1fe2f6..9a6badcd 100644
--- a/virtinst/cli.py
+++ b/virtinst/cli.py
@@ -28,6 +28,7 @@ from .nodedev import NodeDevice
 from .osdict import OSDB
 from .storage import StoragePool, StorageVolume
 from .install.unattended import UnattendedData
+from .install.cloudinit import CloudInitData
 
 
 ##########################
@@ -1602,6 +1603,30 @@ def parse_install(optstr):
     return installdata
 
 
+########################
+# --cloud-init parsing #
+########################
+
+class ParserCloudInit(VirtCLIParser):
+    cli_arg_name = "cloud-init"
+
+    @classmethod
+    def _init_class(cls, **kwargs):
+        VirtCLIParser._init_class(**kwargs)
+        cls.add_arg("root-password", "root_password")
+
+
+def parse_cloud_init(optstr):
+    ret = CloudInitData()
+    if optstr == 1:
+        # This means bare --cloud-init, so there's nothing to parse
+        return ret
+
+    parser = ParserCloudInit(optstr)
+    parser.parse(ret)
+    return ret
+
+
 ######################
 # --location parsing #
 ######################
diff --git a/virtinst/install/cloudinit.py b/virtinst/install/cloudinit.py
new file mode 100644
index 00000000..41667f4b
--- /dev/null
+++ b/virtinst/install/cloudinit.py
@@ -0,0 +1,57 @@
+import tempfile
+import random
+import string
+import time
+from ..logger import log
+
+
+class CloudInitData():
+    root_password = None
+
+
+def create_metadata(scratchdir, hostname=None):
+    if hostname:
+        instance = hostname
+    else:
+        hostname = instance = "localhost"
+    content = 'instance-id: %s\n' % instance
+    content += 'hostname: %s\n' % hostname
+    log.debug("Generated cloud-init metadata:\n%s", content)
+
+    fileobj = tempfile.NamedTemporaryFile(
+            prefix="virtinst-", suffix="-metadata",
+            dir=scratchdir, delete=False)
+    filename = fileobj.name
+
+    with open(filename, "w") as f:
+        f.write(content)
+    return filename
+
+
+def create_userdata(scratchdir, cloudinit_data, username=None, password=None):
+    if not password:
+        password = ""
+        for dummy in range(16):
+            password += random.choice(string.ascii_letters + string.digits)
+    content = "#cloud-config\n"
+    if username:
+        content += "name: %s\n" % username
+    if cloudinit_data.root_password == "generate":
+        pass
+    else:
+        content += "password: %s\n" % password
+        log.debug("Generated password for first boot: \n%s", password)
+        time.sleep(20)
+    content += "runcmd:\n"
+    content += "- [ sudo, touch, /etc/cloud/cloud-init.disabled ]\n"
+    log.debug("Generated cloud-init userdata:\n%s", content)
+
+
+    fileobj = tempfile.NamedTemporaryFile(
+            prefix="virtinst-", suffix="-userdata",
+            dir=scratchdir, delete=False)
+    filename = fileobj.name
+
+    with open(filename, "w+") as f:
+        f.write(content)
+    return filename
diff --git a/virtinst/install/installer.py b/virtinst/install/installer.py
index a5c7a708..7b8ba9bd 100644
--- a/virtinst/install/installer.py
+++ b/virtinst/install/installer.py
@@ -16,6 +16,7 @@ from ..devices import DeviceDisk
 from ..osdict import OSDB
 from ..logger import log
 from .. import progress
+from .cloudinit import create_metadata, create_userdata
 
 
 def _make_testsuite_path(path):
@@ -60,6 +61,7 @@ class Installer(object):
         self._tmpfiles = []
         self._defaults_are_set = False
         self._unattended_data = None
+        self._cloudinit_data = None
 
         self._install_bootdev = install_bootdev
         self._no_install = no_install
@@ -266,6 +268,9 @@ class Installer(object):
         elif unattended_script:
             self._prepare_unattended_data(guest, unattended_script)
 
+        elif self._cloudinit_data:
+            self._install_cloudinit(guest)
+
     def _cleanup(self, guest):
         if self._treemedia:
             self._treemedia.cleanup(guest)
@@ -401,6 +406,18 @@ class Installer(object):
     def set_unattended_data(self, unattended_data):
         self._unattended_data = unattended_data
 
+    def set_cloudinit_data(self, cloudinit_data):
+        self._cloudinit_data = cloudinit_data
+
+    def _install_cloudinit(self, guest):
+        metadata = create_metadata(guest.conn.get_app_cache_dir())
+        userdata = create_userdata(guest.conn.get_app_cache_dir(), self._cloudinit_data)
+
+        iso = perform_cdrom_injections([(metadata, "meta-data"), (userdata, "user-data")],
+                guest.conn.get_app_cache_dir(), cloudinit=True)
+        self._tmpfiles.append(iso)
+        self._add_unattended_install_cdrom_device(guest, iso)
+
 
     ##########################
     # guest install handling #
diff --git a/virtinst/install/installerinject.py b/virtinst/install/installerinject.py
index d7cfcfb4..7ad4833f 100644
--- a/virtinst/install/installerinject.py
+++ b/virtinst/install/installerinject.py
@@ -44,19 +44,21 @@ def _run_initrd_commands(initrd, tempdir):
         log.debug("gzip stderr=%s", gziperr)
 
 
-def _run_iso_commands(iso, tempdir):
+def _run_iso_commands(iso, tempdir, cloudinit=False):
     cmd = ["genisoimage",
            "-o", iso,
            "-J",
            "-input-charset", "utf8",
-           "-rational-rock",
-           tempdir]
+           "-rational-rock"]
+    if cloudinit:
+        cmd.extend(["-V", "cidata"])
+    cmd.append(tempdir)
     log.debug("Running iso build command: %s", cmd)
     output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
     log.debug("cmd output: %s", output)
 
 
-def _perform_generic_injections(injections, scratchdir, media, cb):
+def _perform_generic_injections(injections, scratchdir, media, cb, cloudinit=False):
     if not injections:
         return
 
@@ -74,20 +76,20 @@ def _perform_generic_injections(injections, scratchdir, media, cb):
                     filename, dst, media)
             shutil.copy(filename, os.path.join(tempdir, dst))
 
-        return cb(media, tempdir)
+        return cb(media, tempdir, cloudinit)
     finally:
         shutil.rmtree(tempdir)
 
 
-def perform_initrd_injections(initrd, injections, scratchdir):
+def perform_initrd_injections(initrd, injections, scratchdir, cloudinit=False):
     """
     Insert files into the root directory of the initial ram disk
     """
     _perform_generic_injections(injections, scratchdir, initrd,
-            _run_initrd_commands)
+            _run_initrd_commands, cloudinit)
 
 
-def perform_cdrom_injections(injections, scratchdir):
+def perform_cdrom_injections(injections, scratchdir, cloudinit=False):
     """
     Insert files into the root directory of a generated cdrom
     """
@@ -98,7 +100,7 @@ def perform_cdrom_injections(injections, scratchdir):
 
     try:
         _perform_generic_injections(injections, scratchdir, iso,
-            _run_iso_commands)
+            _run_iso_commands, cloudinit)
     except Exception:  # pragma: no cover
         os.unlink(iso)
         raise
-- 
2.20.1




More information about the virt-tools-list mailing list