← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~xiaofengw/cloud-init:xiaofengw-fix-post-script into cloud-init:master

 

Xiaofeng Wang has proposed merging ~xiaofengw/cloud-init:xiaofengw-fix-post-script into cloud-init:master.

Commit message:
Changes to be committed:
        modified:   cloudinit/sources/helpers/vmware/imc/config_custom_script.py
        modified:   tests/unittests/test_vmware/test_custom_script.py


Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~xiaofengw/cloud-init/+git/cloud-init/+merge/368902

VMWare: Trigger the post customization script via cc_scripts module.

1. Since cloud-init  doesn't need to reboot the VM, so add post customization script into rc.local won't
trigger the post customization script as we expected. With this update, the script is copied into
cc_scripts configuration dir and triggered by cc_scripts.
2. Remove the sh interpreter and execute the customization script directly.
3. Update the unit test.

-- 
Your team cloud-init commiters is requested to review the proposed merge of ~xiaofengw/cloud-init:xiaofengw-fix-post-script into cloud-init:master.
diff --git a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
index a7d4ad9..6345a0c 100644
--- a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
+++ b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
@@ -1,5 +1,5 @@
 # Copyright (C) 2017 Canonical Ltd.
-# Copyright (C) 2017 VMware Inc.
+# Copyright (C) 2017-2019 VMware Inc.
 #
 # Author: Maitreyee Saikia <msaikia@xxxxxxxxxx>
 #
@@ -8,11 +8,11 @@
 import logging
 import os
 import stat
-from textwrap import dedent
 
 from cloudinit import util
 
 LOG = logging.getLogger(__name__)
+HOME_DIR = os.path.expanduser('~')
 
 
 class CustomScriptNotFound(Exception):
@@ -20,12 +20,18 @@ class CustomScriptNotFound(Exception):
 
 
 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"
+    CUSTOM_TMP_DIR = os.path.join(HOME_DIR, ".customization")
+
+    # The user defined custom script
+    CUSTOM_SCRIPT_NAME = "customize.sh"
+    CUSTOM_SCRIPT = os.path.join(CUSTOM_TMP_DIR,
+                                 CUSTOM_SCRIPT_NAME)
+    POST_CUSTOM_PENDING_MARKER = "/.guest-customization-post-reboot-pending"
+    # The cc_scripts_per_instance script to launch custom script
+    POST_CUSTOM_SCRIPT_NAME = "post-customize-guest.sh"
+    CC_SCRIPT_PER_INSTANCE_DIR = "/var/lib/cloud/scripts/per-instance"
+    CC_CUSTOM_SCRIPT_PATH = os.path.join(CC_SCRIPT_PER_INSTANCE_DIR,
+                                         POST_CUSTOM_SCRIPT_NAME)
 
 
 class RunCustomScript(object):
@@ -39,10 +45,20 @@ class RunCustomScript(object):
             raise CustomScriptNotFound("Script %s not found!! "
                                        "Cannot execute custom script!"
                                        % self.scriptpath)
+
+        if not os.path.isdir(CustomScriptConstant.CUSTOM_TMP_DIR):
+            os.mkdir(CustomScriptConstant.CUSTOM_TMP_DIR)
+
+        LOG.debug("Copying custom script to %s",
+                  CustomScriptConstant.CUSTOM_SCRIPT)
+        util.copy(self.scriptpath, CustomScriptConstant.CUSTOM_SCRIPT)
+
         # 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)
+        content = util.load_file(
+            CustomScriptConstant.CUSTOM_SCRIPT).replace("\r", "")
+        util.write_file(CustomScriptConstant.CUSTOM_SCRIPT,
+                        content,
+                        mode=0o544)
 
 
 class PreCustomScript(RunCustomScript):
@@ -50,104 +66,28 @@ class PreCustomScript(RunCustomScript):
         """Executing custom script with precustomization argument."""
         LOG.debug("Executing pre-customization script")
         self.prepare_script()
-        util.subp(["/bin/sh", self.scriptpath, "precustomization"])
+        util.subp([CustomScriptConstant.CUSTOM_SCRIPT, "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.
+        This method copy the post customize run script to
+        cc_scripts_per_instance directory and let this
+        module to run post custom script.
         """
         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)
+
+        LOG.debug("Copying post customize run script to %s",
+                  CustomScriptConstant.CC_CUSTOM_SCRIPT_PATH)
+        util.copy(
+            os.path.join(self.directory,
+                         CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME),
+            CustomScriptConstant.CC_CUSTOM_SCRIPT_PATH)
+        st = os.stat(CustomScriptConstant.CUSTOM_SCRIPT)
+        os.chmod(CustomScriptConstant.CC_CUSTOM_SCRIPT_PATH,
+                 st.st_mode | stat.S_IEXEC)
+        LOG.info("Creating post customization pending marker")
+        util.ensure_file(CustomScriptConstant.POST_CUSTOM_PENDING_MARKER)
 
 # vi: ts=4 expandtab
diff --git a/tests/unittests/test_vmware/test_custom_script.py b/tests/unittests/test_vmware/test_custom_script.py
index 2d9519b..6dac9d6 100644
--- a/tests/unittests/test_vmware/test_custom_script.py
+++ b/tests/unittests/test_vmware/test_custom_script.py
@@ -1,10 +1,12 @@
 # Copyright (C) 2015 Canonical Ltd.
-# Copyright (C) 2017 VMware INC.
+# Copyright (C) 2017-2019 VMware INC.
 #
 # Author: Maitreyee Saikia <msaikia@xxxxxxxxxx>
 #
 # This file is part of cloud-init. See LICENSE file for license information.
 
+import os
+import stat
 from cloudinit import util
 from cloudinit.sources.helpers.vmware.imc.config_custom_script import (
     CustomScriptConstant,
@@ -14,10 +16,15 @@ from cloudinit.sources.helpers.vmware.imc.config_custom_script import (
 )
 from cloudinit.tests.helpers import CiTestCase, mock
 
+CUSTOM_SCRIPT = os.path.join(os.path.expanduser('~'),
+                             ".customization/customize.sh")
+
 
 class TestVmwareCustomScript(CiTestCase):
     def setUp(self):
         self.tmpDir = self.tmp_dir()
+        if os.path.exists(CUSTOM_SCRIPT):
+            os.unlink(CUSTOM_SCRIPT)
 
     def test_prepare_custom_script(self):
         """
@@ -37,63 +44,53 @@ class TestVmwareCustomScript(CiTestCase):
 
         # Custom script exists.
         custScript = self.tmp_path("test-cust", self.tmpDir)
-        util.write_file(custScript, "test-CR-strip/r/r")
+        util.write_file(custScript, "test-CR-strip\r\r")
         postCust = PostCustomScript("test-cust", self.tmpDir)
         self.assertEqual("test-cust", postCust.scriptname)
         self.assertEqual(self.tmpDir, postCust.directory)
         self.assertEqual(custScript, postCust.scriptpath)
-        self.assertFalse(postCust.postreboot)
         postCust.prepare_script()
-        # Check if all carraige returns are stripped from script.
-        self.assertFalse("/r" in custScript)
-
-    def test_rc_local_exists(self):
-        """
-        This test is designed to verify the different scenarios associated
-        with the presence of rclocal.
-        """
-        # test when rc local does not exist
-        postCust = PostCustomScript("test-cust", self.tmpDir)
-        with mock.patch.object(CustomScriptConstant, "RC_LOCAL", "/no/path"):
-            rclocal = postCust.find_rc_local()
-            self.assertEqual("", rclocal)
 
-        # test when rc local exists
-        rclocalFile = self.tmp_path("vmware-rclocal", self.tmpDir)
-        util.write_file(rclocalFile, "# Run post-reboot guest customization",
-                        omode="w")
-        with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalFile):
-            rclocal = postCust.find_rc_local()
-            self.assertEqual(rclocalFile, rclocal)
-            self.assertTrue(postCust.has_previous_agent, rclocal)
-
-        # test when rc local is a symlink
-        rclocalLink = self.tmp_path("dummy-rclocal-link", self.tmpDir)
-        util.sym_link(rclocalFile, rclocalLink, True)
-        with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalLink):
-            rclocal = postCust.find_rc_local()
-            self.assertEqual(rclocalFile, rclocal)
+        # Custom script is copied with exec privilege
+        self.assertTrue(os.path.exists(CUSTOM_SCRIPT))
+        st = os.stat(CustomScriptConstant.CUSTOM_SCRIPT)
+        self.assertTrue(st.st_mode & stat.S_IEXEC)
+        with open(CUSTOM_SCRIPT, "r") as f:
+            content = f.read()
+        self.assertEqual(content, "test-CR-strip")
+        # Check if all carraige returns are stripped from script.
+        self.assertFalse("\r" in content)
 
     def test_execute_post_cust(self):
         """
-        This test is to identify if rclocal was properly populated to be
-        run after reboot.
+        This test is designed to verify the behavior after execute post
+        customization.
         """
-        customscript = self.tmp_path("vmware-post-cust-script", self.tmpDir)
-        rclocal = self.tmp_path("vmware-rclocal", self.tmpDir)
-        # Create a temporary rclocal file
-        open(customscript, "w")
-        util.write_file(rclocal, "tests\nexit 0", omode="w")
-        postCust = PostCustomScript("vmware-post-cust-script", self.tmpDir)
-        with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocal):
-            # Test that guest customization agent is not installed initially.
-            self.assertFalse(postCust.postreboot)
-            self.assertIs(postCust.has_previous_agent(rclocal), False)
-            postCust.install_agent()
+        # Prepare the customize package
+        postCustRun = self.tmp_path("post-customize-guest.sh", self.tmpDir)
+        util.write_file(postCustRun, "This is the script to run post cust")
+        userScript = self.tmp_path("test-cust", self.tmpDir)
+        util.write_file(userScript, "This is the post cust script")
 
-            # Assert rclocal has been modified to have guest customization
-            # agent.
-            self.assertTrue(postCust.postreboot)
-            self.assertTrue(postCust.has_previous_agent, rclocal)
+        # Mock the cc_scripts_per_instance dir and marker file.
+        # Create another tmp dir for cc_scripts_per_instance.
+        ccScriptDir = self.tmp_dir()
+        ccScript = os.path.join(ccScriptDir, "post-customize-guest.sh")
+        markerFile = os.path.join(ccScriptDir, ".markerFile")
+        with mock.patch.object(CustomScriptConstant,
+                               "CC_CUSTOM_SCRIPT_PATH",
+                               ccScript):
+            with mock.patch.object(CustomScriptConstant,
+                                   "POST_CUSTOM_PENDING_MARKER",
+                                   markerFile):
+                postCust = PostCustomScript("test-cust", self.tmpDir)
+                postCust.execute()
+                # Check cc_scripts_per_instance and marker file are created.
+                self.assertTrue(os.path.exists(ccScript))
+                with open(ccScript, "r") as f:
+                    content = f.read()
+                self.assertEqual(content,
+                                 "This is the script to run post cust")
+                self.assertTrue(os.path.exists(markerFile))
 
 # vi: ts=4 expandtab

Follow ups