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)
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
@ -842,6 +845,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)

View File

@ -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
HAS_VIRTVIEWER = shutil.which("virt-viewer")
@ -467,7 +468,7 @@ def get_meter():
def _get_completer_parsers():
return VIRT_PARSERS + [ParserCheck, ParserLocation,
ParserUnattended, ParserInstall]
ParserUnattended, ParserInstall, ParserCloudInit]
def _virtparser_completer(prefix, **kwargs):
@ -1614,6 +1615,31 @@ def parse_install(optstr):
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 #
######################

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 ..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
@ -279,6 +281,9 @@ class Installer(object):
elif unattended_scripts:
self._prepare_unattended_data(guest, meter, unattended_scripts)
elif self._cloudinit_data:
self._install_cloudinit(guest)
def _cleanup(self, guest):
if self._treemedia:
self._treemedia.cleanup(guest)
@ -414,6 +419,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 #

View File

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