Introduction of cloud-init configuration in virt-install

Usage:
--cloud-init

Signed-off-by: Athina Plaskasoviti <athina.plaskasoviti@gmail.com>
This commit is contained in:
Athina Plaskasoviti 2019-06-28 19:05:18 +03:00 committed by Cole Robinson
parent c311b28979
commit 19317024cc
5 changed files with 117 additions and 10 deletions

View File

@ -456,6 +456,9 @@ def build_installer(options, guest, installdata):
installer.set_initrd_injections(options.initrd_inject) installer.set_initrd_injections(options.initrd_inject)
if options.autostart: if options.autostart:
installer.autostart = True installer.autostart = True
if options.cloud_init:
cloudinit_data = cli.parse_cloud_init(options.cloud_init)
installer.set_cloudinit_data(cloudinit_data)
return installer return installer
@ -842,6 +845,8 @@ def parse_args():
help=_("Perform an unattended installation")) help=_("Perform an unattended installation"))
insg.add_argument("--install", insg.add_argument("--install",
help=_("Specify fine grained install options")) 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 # Takes a URL and just prints to stdout the detected distro name
insg.add_argument("--test-media-detection", help=argparse.SUPPRESS) insg.add_argument("--test-media-detection", help=argparse.SUPPRESS)

View File

@ -28,6 +28,7 @@ from .nodedev import NodeDevice
from .osdict import OSDB from .osdict import OSDB
from .storage import StoragePool, StorageVolume from .storage import StoragePool, StorageVolume
from .install.unattended import UnattendedData from .install.unattended import UnattendedData
from .install.cloudinit import CloudInitData
HAS_VIRTVIEWER = shutil.which("virt-viewer") HAS_VIRTVIEWER = shutil.which("virt-viewer")
@ -467,7 +468,7 @@ def get_meter():
def _get_completer_parsers(): def _get_completer_parsers():
return VIRT_PARSERS + [ParserCheck, ParserLocation, return VIRT_PARSERS + [ParserCheck, ParserLocation,
ParserUnattended, ParserInstall] ParserUnattended, ParserInstall, ParserCloudInit]
def _virtparser_completer(prefix, **kwargs): def _virtparser_completer(prefix, **kwargs):
@ -1614,6 +1615,31 @@ def parse_install(optstr):
return installdata return installdata
########################
# --cloud-init parsing #
########################
class ParserCloudInit(VirtCLIParser):
cli_arg_name = "cloud_init"
supports_clearxml = False
@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 # # --location parsing #
###################### ######################

View File

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

View File

@ -16,6 +16,7 @@ from ..devices import DeviceDisk
from ..osdict import OSDB from ..osdict import OSDB
from ..logger import log from ..logger import log
from .. import progress from .. import progress
from .cloudinit import create_metadata, create_userdata
def _make_testsuite_path(path): def _make_testsuite_path(path):
@ -60,6 +61,7 @@ class Installer(object):
self._tmpfiles = [] self._tmpfiles = []
self._defaults_are_set = False self._defaults_are_set = False
self._unattended_data = None self._unattended_data = None
self._cloudinit_data = None
self._install_bootdev = install_bootdev self._install_bootdev = install_bootdev
self._no_install = no_install self._no_install = no_install
@ -279,6 +281,9 @@ class Installer(object):
elif unattended_scripts: elif unattended_scripts:
self._prepare_unattended_data(guest, meter, unattended_scripts) self._prepare_unattended_data(guest, meter, unattended_scripts)
elif self._cloudinit_data:
self._install_cloudinit(guest)
def _cleanup(self, guest): def _cleanup(self, guest):
if self._treemedia: if self._treemedia:
self._treemedia.cleanup(guest) self._treemedia.cleanup(guest)
@ -414,6 +419,18 @@ class Installer(object):
def set_unattended_data(self, unattended_data): def set_unattended_data(self, unattended_data):
self._unattended_data = 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 # # guest install handling #

View File

@ -44,19 +44,21 @@ def _run_initrd_commands(initrd, tempdir):
log.debug("gzip stderr=%s", gziperr) log.debug("gzip stderr=%s", gziperr)
def _run_iso_commands(iso, tempdir): def _run_iso_commands(iso, tempdir, cloudinit=False):
cmd = ["genisoimage", cmd = ["genisoimage",
"-o", iso, "-o", iso,
"-J", "-J",
"-input-charset", "utf8", "-input-charset", "utf8",
"-rational-rock", "-rational-rock"]
tempdir] if cloudinit:
cmd.extend(["-V", "cidata"])
cmd.append(tempdir)
log.debug("Running iso build command: %s", cmd) log.debug("Running iso build command: %s", cmd)
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
log.debug("cmd output: %s", output) 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: if not injections:
return return
@ -74,20 +76,20 @@ def _perform_generic_injections(injections, scratchdir, media, cb):
filename, dst, media) filename, dst, media)
shutil.copy(filename, os.path.join(tempdir, dst)) shutil.copy(filename, os.path.join(tempdir, dst))
return cb(media, tempdir) return cb(media, tempdir, cloudinit)
finally: finally:
shutil.rmtree(tempdir) 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 Insert files into the root directory of the initial ram disk
""" """
_perform_generic_injections(injections, scratchdir, initrd, _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 Insert files into the root directory of a generated cdrom
""" """
@ -98,7 +100,7 @@ def perform_cdrom_injections(injections, scratchdir):
try: try:
_perform_generic_injections(injections, scratchdir, iso, _perform_generic_injections(injections, scratchdir, iso,
_run_iso_commands) _run_iso_commands, cloudinit)
except Exception: # pragma: no cover except Exception: # pragma: no cover
os.unlink(iso) os.unlink(iso)
raise raise