From 17fabe47dcbbe6b2180e73cac6211f7394d61a8a Mon Sep 17 00:00:00 2001
From: Scott Moser <smoser@ubuntu.com>
Date: Fri, 18 Jun 2010 00:23:25 -0400
Subject: add 'cloud-boothook' type

if user data is of type text/cloud-boothook, or begins with
#cloud-boothook, then assume it to be code to be executed.

Boothooks are a very simple format.  Basically, its a one line header
('#cloud-config\n') and then executable payload.

The executable payload is written to a file, then that file is executed
at the time it is read.  The file is left in
/var/lib/cloud/data/boothooks

There is no "first-time-only" protection.  If running only once is
desired, the boothook must handle that itself.
---
 cloudinit/DataSourceEc2.py   |  3 ++-
 cloudinit/UserDataHandler.py |  3 ++-
 cloudinit/__init__.py        | 37 ++++++++++++++++++++++++++++++++++++-
 tools/write-mime-multipart   |  3 ++-
 4 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/cloudinit/DataSourceEc2.py b/cloudinit/DataSourceEc2.py
index ebee61c6..1094b8a6 100644
--- a/cloudinit/DataSourceEc2.py
+++ b/cloudinit/DataSourceEc2.py
@@ -40,6 +40,7 @@ class DataSourceEc2(DataSource.DataSource):
 
     def get_data(self):
         try:
+            cloudinit.log.info("looking at %s/user-data.raw" % self.cachedir)
             udf = open(self.cachedir + "/user-data.raw")
             self.userdata_raw = udf.read()
             udf.close()
@@ -132,7 +133,7 @@ class DataSourceEc2(DataSource.DataSource):
                 (time.strftime("%H:%M:%S"), x+1, sleeps, reason))
             time.sleep(sleeptime)
 
-        log.critical("giving up on md after %i seconds\n" %
+        cloudinit.log.critical("giving up on md after %i seconds\n" %
                   int(time.time()-starttime))
         return False
 
diff --git a/cloudinit/UserDataHandler.py b/cloudinit/UserDataHandler.py
index b2c6e174..5c95173a 100644
--- a/cloudinit/UserDataHandler.py
+++ b/cloudinit/UserDataHandler.py
@@ -26,7 +26,8 @@ starts_with_mappings={
     '#!' : 'text/x-shellscript',
     '#cloud-config' : 'text/cloud-config',
     '#upstart-job'  : 'text/upstart-job',
-    '#part-handler' : 'text/part-handler'
+    '#part-handler' : 'text/part-handler',
+    '#cloud-boothook' : 'text/cloud-boothook'
 }
 
 # if 'str' is compressed return decompressed otherwise return it
diff --git a/cloudinit/__init__.py b/cloudinit/__init__.py
index 2b76a73f..b0ee5fe8 100644
--- a/cloudinit/__init__.py
+++ b/cloudinit/__init__.py
@@ -25,6 +25,7 @@ cachedir = datadir + '/cache'
 userdata_raw = datadir + '/user-data.txt'
 userdata = datadir + '/user-data.txt.i'
 user_scripts_dir = datadir + "/scripts"
+boothooks_dir = datadir + "/boothooks"
 cloud_config = datadir + '/cloud-config.txt'
 #cloud_config = '/tmp/cloud-config.txt'
 data_source_cache = cachedir + '/obj.pkl'
@@ -174,7 +175,8 @@ class CloudInit:
             'text/x-shellscript' : self.handle_user_script,
             'text/cloud-config' : self.handle_cloud_config,
             'text/upstart-job' : self.handle_upstart_job,
-            'text/part-handler' : self.handle_handler
+            'text/part-handler' : self.handle_handler,
+            'text/cloud-boothook' : self.handle_cloud_boothook
         }
         self.sysconfig=sysconfig
         self.cfg=self.read_cfg()
@@ -412,6 +414,39 @@ class CloudInit:
 
         self.cloud_config_str+="\n#%s\n%s" % (filename,payload)
 
+    def handle_cloud_boothook(self,data,ctype,filename,payload):
+        if ctype == "__end__": return
+        if ctype == "__begin__": return
+
+        filename=filename.replace(os.sep,'_')
+        prefix="#cloud-boothooks"
+        dos=False
+        start = 0
+        if payload.startswith(prefix):
+            start = len(prefix)+1
+            if payload[start] == '\r':
+                start=start+1
+                dos = True
+        else:
+            if payload.find('\r\n',0,100) >= 0:
+                dos = True
+    
+        if dos:
+            payload=payload[start:].replace('\r\n','\n')
+        elif start != 0:
+            payload=payload[start:]
+    
+        filepath = "%s/%s" % (boothooks_dir,filename)
+        util.write_file(filepath, payload, 0700)
+        try:
+            ret = subprocess.check_call([filepath])
+        except subprocess.CalledProcessError as e:
+            log.error("boothooks script %s returned %i" %
+                (filepath,e.returncode))
+        except Exception as e:
+            log.error("boothooks unknown exception %s when running %s" %
+                (e,filepath))
+
     def get_public_ssh_keys(self):
         return(self.datasource.get_public_ssh_keys())
 
diff --git a/tools/write-mime-multipart b/tools/write-mime-multipart
index d7ce0992..6466c3ff 100755
--- a/tools/write-mime-multipart
+++ b/tools/write-mime-multipart
@@ -25,7 +25,8 @@ starts_with_mappings={
     '#!' : 'text/x-shellscript',
     '#cloud-config' : 'text/cloud-config',
     '#upstart-job'  : 'text/upstart-job',
-    '#part-handler' : 'text/part-handler'
+    '#part-handler' : 'text/part-handler',
+    '#cloud-boothook' : 'text/cloud-boothook'
 }
 
 def get_type(fname,deftype):
-- 
cgit v1.2.3