summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
authorMaitreyee Saikia <msaikia@vmware.com>2017-12-08 10:10:40 -0700
committerChad Smith <chad.smith@canonical.com>2017-12-08 10:15:00 -0700
commitce33e423cde806a0590fec635778d62836e1bd37 (patch)
tree630ee644a3c56be85d8e85050b317e53b6c459b7 /cloudinit
parent05b2308aa7e30337c2a455b5d2c67871b233e25c (diff)
downloadvyos-cloud-init-ce33e423cde806a0590fec635778d62836e1bd37.tar.gz
vyos-cloud-init-ce33e423cde806a0590fec635778d62836e1bd37.zip
VMware: Support for user provided pre and post-customization scripts
In the VMware customization workflow, we have some options for the user to upload scripts for additional customization. Based on user request, those custom scripts can be either run before regular customization or after. For post customization scripts, we decide whether to run the scripts just after customization or post system reboot.
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/sources/DataSourceOVF.py125
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config.py4
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config_custom_script.py153
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config_nic.py2
4 files changed, 246 insertions, 38 deletions
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index 6ac621f2..6e62f984 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -21,6 +21,8 @@ from cloudinit import util
from cloudinit.sources.helpers.vmware.imc.config \
import Config
+from cloudinit.sources.helpers.vmware.imc.config_custom_script \
+ import PreCustomScript, PostCustomScript
from cloudinit.sources.helpers.vmware.imc.config_file \
import ConfigFile
from cloudinit.sources.helpers.vmware.imc.config_nic \
@@ -30,7 +32,7 @@ from cloudinit.sources.helpers.vmware.imc.config_passwd \
from cloudinit.sources.helpers.vmware.imc.guestcust_error \
import GuestCustErrorEnum
from cloudinit.sources.helpers.vmware.imc.guestcust_event \
- import GuestCustEventEnum
+ import GuestCustEventEnum as GuestCustEvent
from cloudinit.sources.helpers.vmware.imc.guestcust_state \
import GuestCustStateEnum
from cloudinit.sources.helpers.vmware.imc.guestcust_util import (
@@ -127,17 +129,31 @@ class DataSourceOVF(sources.DataSource):
self._vmware_cust_conf = Config(cf)
(md, ud, cfg) = read_vmware_imc(self._vmware_cust_conf)
self._vmware_nics_to_enable = get_nics_to_enable(nicspath)
- markerid = self._vmware_cust_conf.marker_id
- markerexists = check_marker_exists(markerid)
+ imcdirpath = os.path.dirname(vmwareImcConfigFilePath)
+ product_marker = self._vmware_cust_conf.marker_id
+ hasmarkerfile = check_marker_exists(
+ product_marker, os.path.join(self.paths.cloud_dir, 'data'))
+ special_customization = product_marker and not hasmarkerfile
+ customscript = self._vmware_cust_conf.custom_script_name
except Exception as e:
- LOG.debug("Error parsing the customization Config File")
- LOG.exception(e)
- set_customization_status(
- GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
- GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
- raise e
- finally:
- util.del_dir(os.path.dirname(vmwareImcConfigFilePath))
+ _raise_error_status(
+ "Error parsing the customization Config File",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ vmwareImcConfigFilePath)
+
+ if special_customization:
+ if customscript:
+ try:
+ precust = PreCustomScript(customscript, imcdirpath)
+ precust.execute()
+ except Exception as e:
+ _raise_error_status(
+ "Error executing pre-customization script",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ vmwareImcConfigFilePath)
+
try:
LOG.debug("Preparing the Network configuration")
self._network_config = get_network_config_from_conf(
@@ -146,13 +162,13 @@ class DataSourceOVF(sources.DataSource):
True,
self.distro.osfamily)
except Exception as e:
- LOG.exception(e)
- set_customization_status(
- GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
- GuestCustEventEnum.GUESTCUST_EVENT_NETWORK_SETUP_FAILED)
- raise e
+ _raise_error_status(
+ "Error preparing Network Configuration",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_NETWORK_SETUP_FAILED,
+ vmwareImcConfigFilePath)
- if markerid and not markerexists:
+ if special_customization:
LOG.debug("Applying password customization")
pwdConfigurator = PasswordConfigurator()
adminpwd = self._vmware_cust_conf.admin_password
@@ -164,27 +180,41 @@ class DataSourceOVF(sources.DataSource):
else:
LOG.debug("Changing password is not needed")
except Exception as e:
- LOG.debug("Error applying Password Configuration: %s", e)
- set_customization_status(
- GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
- GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
- return False
- if markerid:
- LOG.debug("Handle marker creation")
+ _raise_error_status(
+ "Error applying Password Configuration",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ vmwareImcConfigFilePath)
+
+ if customscript:
+ try:
+ postcust = PostCustomScript(customscript, imcdirpath)
+ postcust.execute()
+ except Exception as e:
+ _raise_error_status(
+ "Error executing post-customization script",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ vmwareImcConfigFilePath)
+
+ if product_marker:
try:
- setup_marker_files(markerid)
+ setup_marker_files(
+ product_marker,
+ os.path.join(self.paths.cloud_dir, 'data'))
except Exception as e:
- LOG.debug("Error creating marker files: %s", e)
- set_customization_status(
- GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
- GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
- return False
+ _raise_error_status(
+ "Error creating marker files",
+ e,
+ GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ vmwareImcConfigFilePath)
self._vmware_cust_found = True
found.append('vmware-tools')
# TODO: Need to set the status to DONE only when the
# customization is done successfully.
+ util.del_dir(os.path.dirname(vmwareImcConfigFilePath))
enable_nics(self._vmware_nics_to_enable)
set_customization_status(
GuestCustStateEnum.GUESTCUST_STATE_DONE,
@@ -539,31 +569,52 @@ def get_datasource_list(depends):
# To check if marker file exists
-def check_marker_exists(markerid):
+def check_marker_exists(markerid, marker_dir):
"""
Check the existence of a marker file.
Presence of marker file determines whether a certain code path is to be
executed. It is needed for partial guest customization in VMware.
+ @param markerid: is an unique string representing a particular product
+ marker.
+ @param: marker_dir: The directory in which markers exist.
"""
if not markerid:
return False
- markerfile = "/.markerfile-" + markerid
+ markerfile = os.path.join(marker_dir, ".markerfile-" + markerid + ".txt")
if os.path.exists(markerfile):
return True
return False
# Create a marker file
-def setup_marker_files(markerid):
+def setup_marker_files(markerid, marker_dir):
"""
Create a new marker file.
Marker files are unique to a full customization workflow in VMware
environment.
+ @param markerid: is an unique string representing a particular product
+ marker.
+ @param: marker_dir: The directory in which markers exist.
+
"""
- if not markerid:
- return
- markerfile = "/.markerfile-" + markerid
- util.del_file("/.markerfile-*.txt")
+ LOG.debug("Handle marker creation")
+ markerfile = os.path.join(marker_dir, ".markerfile-" + markerid + ".txt")
+ for fname in os.listdir(marker_dir):
+ if fname.startswith(".markerfile"):
+ util.del_file(os.path.join(marker_dir, fname))
open(markerfile, 'w').close()
+
+def _raise_error_status(prefix, error, event, config_file):
+ """
+ Raise error and send customization status to the underlying VMware
+ Virtualization Platform. Also, cleanup the imc directory.
+ """
+ LOG.debug('%s: %s', prefix, error)
+ set_customization_status(
+ GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
+ event)
+ util.del_dir(os.path.dirname(config_file))
+ raise error
+
# vi: ts=4 expandtab
diff --git a/cloudinit/sources/helpers/vmware/imc/config.py b/cloudinit/sources/helpers/vmware/imc/config.py
index 49d441db..2eaeff34 100644
--- a/cloudinit/sources/helpers/vmware/imc/config.py
+++ b/cloudinit/sources/helpers/vmware/imc/config.py
@@ -100,4 +100,8 @@ class Config(object):
"""Returns marker id."""
return self._configFile.get(Config.MARKERID, None)
+ @property
+ def custom_script_name(self):
+ """Return the name of custom (pre/post) script."""
+ return self._configFile.get(Config.CUSTOM_SCRIPT, None)
# vi: ts=4 expandtab
diff --git a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
new file mode 100644
index 00000000..a7d4ad91
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
@@ -0,0 +1,153 @@
+# Copyright (C) 2017 Canonical Ltd.
+# Copyright (C) 2017 VMware Inc.
+#
+# Author: Maitreyee Saikia <msaikia@vmware.com>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import logging
+import os
+import stat
+from textwrap import dedent
+
+from cloudinit import util
+
+LOG = logging.getLogger(__name__)
+
+
+class CustomScriptNotFound(Exception):
+ pass
+
+
+class CustomScriptConstant(object):
+ RC_LOCAL = "/etc/rc.local"
+ POST_CUST_TMP_DIR = "/root/.customization"
+ POST_CUST_RUN_SCRIPT_NAME = "post-customize-guest.sh"
+ POST_CUST_RUN_SCRIPT = os.path.join(POST_CUST_TMP_DIR,
+ POST_CUST_RUN_SCRIPT_NAME)
+ POST_REBOOT_PENDING_MARKER = "/.guest-customization-post-reboot-pending"
+
+
+class RunCustomScript(object):
+ def __init__(self, scriptname, directory):
+ self.scriptname = scriptname
+ self.directory = directory
+ self.scriptpath = os.path.join(directory, scriptname)
+
+ def prepare_script(self):
+ if not os.path.exists(self.scriptpath):
+ raise CustomScriptNotFound("Script %s not found!! "
+ "Cannot execute custom script!"
+ % self.scriptpath)
+ # Strip any CR characters from the decoded script
+ util.load_file(self.scriptpath).replace("\r", "")
+ st = os.stat(self.scriptpath)
+ os.chmod(self.scriptpath, st.st_mode | stat.S_IEXEC)
+
+
+class PreCustomScript(RunCustomScript):
+ def execute(self):
+ """Executing custom script with precustomization argument."""
+ LOG.debug("Executing pre-customization script")
+ self.prepare_script()
+ util.subp(["/bin/sh", self.scriptpath, "precustomization"])
+
+
+class PostCustomScript(RunCustomScript):
+ def __init__(self, scriptname, directory):
+ super(PostCustomScript, self).__init__(scriptname, directory)
+ # Determine when to run custom script. When postreboot is True,
+ # the user uploaded script will run as part of rc.local after
+ # the machine reboots. This is determined by presence of rclocal.
+ # When postreboot is False, script will run as part of cloud-init.
+ self.postreboot = False
+
+ def _install_post_reboot_agent(self, rclocal):
+ """
+ Install post-reboot agent for running custom script after reboot.
+ As part of this process, we are editing the rclocal file to run a
+ VMware script, which in turn is resposible for handling the user
+ script.
+ @param: path to rc local.
+ """
+ LOG.debug("Installing post-reboot customization from %s to %s",
+ self.directory, rclocal)
+ if not self.has_previous_agent(rclocal):
+ LOG.info("Adding post-reboot customization agent to rc.local")
+ new_content = dedent("""
+ # Run post-reboot guest customization
+ /bin/sh %s
+ exit 0
+ """) % CustomScriptConstant.POST_CUST_RUN_SCRIPT
+ existing_rclocal = util.load_file(rclocal).replace('exit 0\n', '')
+ st = os.stat(rclocal)
+ # "x" flag should be set
+ mode = st.st_mode | stat.S_IEXEC
+ util.write_file(rclocal, existing_rclocal + new_content, mode)
+
+ else:
+ # We don't need to update rclocal file everytime a customization
+ # is requested. It just needs to be done for the first time.
+ LOG.info("Post-reboot guest customization agent is already "
+ "registered in rc.local")
+ LOG.debug("Installing post-reboot customization agent finished: %s",
+ self.postreboot)
+
+ def has_previous_agent(self, rclocal):
+ searchstring = "# Run post-reboot guest customization"
+ if searchstring in open(rclocal).read():
+ return True
+ return False
+
+ def find_rc_local(self):
+ """
+ Determine if rc local is present.
+ """
+ rclocal = ""
+ if os.path.exists(CustomScriptConstant.RC_LOCAL):
+ LOG.debug("rc.local detected.")
+ # resolving in case of symlink
+ rclocal = os.path.realpath(CustomScriptConstant.RC_LOCAL)
+ LOG.debug("rc.local resolved to %s", rclocal)
+ else:
+ LOG.warning("Can't find rc.local, post-customization "
+ "will be run before reboot")
+ return rclocal
+
+ def install_agent(self):
+ rclocal = self.find_rc_local()
+ if rclocal:
+ self._install_post_reboot_agent(rclocal)
+ self.postreboot = True
+
+ def execute(self):
+ """
+ This method executes post-customization script before or after reboot
+ based on the presence of rc local.
+ """
+ self.prepare_script()
+ self.install_agent()
+ if not self.postreboot:
+ LOG.warning("Executing post-customization script inline")
+ util.subp(["/bin/sh", self.scriptpath, "postcustomization"])
+ else:
+ LOG.debug("Scheduling custom script to run post reboot")
+ if not os.path.isdir(CustomScriptConstant.POST_CUST_TMP_DIR):
+ os.mkdir(CustomScriptConstant.POST_CUST_TMP_DIR)
+ # Script "post-customize-guest.sh" and user uploaded script are
+ # are present in the same directory and needs to copied to a temp
+ # directory to be executed post reboot. User uploaded script is
+ # saved as customize.sh in the temp directory.
+ # post-customize-guest.sh excutes customize.sh after reboot.
+ LOG.debug("Copying post-customization script")
+ util.copy(self.scriptpath,
+ CustomScriptConstant.POST_CUST_TMP_DIR + "/customize.sh")
+ LOG.debug("Copying script to run post-customization script")
+ util.copy(
+ os.path.join(self.directory,
+ CustomScriptConstant.POST_CUST_RUN_SCRIPT_NAME),
+ CustomScriptConstant.POST_CUST_RUN_SCRIPT)
+ LOG.info("Creating post-reboot pending marker")
+ util.ensure_file(CustomScriptConstant.POST_REBOOT_PENDING_MARKER)
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/sources/helpers/vmware/imc/config_nic.py b/cloudinit/sources/helpers/vmware/imc/config_nic.py
index 2fb07c59..2d8900e2 100644
--- a/cloudinit/sources/helpers/vmware/imc/config_nic.py
+++ b/cloudinit/sources/helpers/vmware/imc/config_nic.py
@@ -161,7 +161,7 @@ class NicConfigurator(object):
if nic.primary and v4.gateways:
self.ipv4PrimaryGateway = v4.gateways[0]
subnet.update({'gateway': self.ipv4PrimaryGateway})
- return [subnet]
+ return ([subnet], route_list)
# Add routes if there is no primary nic
if not self._primaryNic: