summaryrefslogtreecommitdiff
path: root/cloudinit/sources
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/sources')
-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: