From c5bfd0c45c33de1ade85c940f5d5e6eabc2e27de Mon Sep 17 00:00:00 2001
From: Scott Moser <smoser@ubuntu.com>
Date: Wed, 19 Jan 2011 17:32:52 -0500
Subject: add cloud-config-archive input type.

cloud-config-archive is a yaml formated document where the top
level should contain an array.  Each entry in the array can be one of
- dict { 'filename' : 'value' , 'content' : 'value', 'type' : 'value' }
   filename and type may not be present
- scalar(content)

if filename and type are not present, they are attempted to be guessed.

LP: #641504
---
 cloudinit/UserDataHandler.py          | 51 ++++++++++++++++++++++++++++++-----
 doc/examples/cloud-config-archive.txt | 16 +++++++++++
 2 files changed, 61 insertions(+), 6 deletions(-)
 create mode 100644 doc/examples/cloud-config-archive.txt

diff --git a/cloudinit/UserDataHandler.py b/cloudinit/UserDataHandler.py
index feb67149..fbb000fc 100644
--- a/cloudinit/UserDataHandler.py
+++ b/cloudinit/UserDataHandler.py
@@ -19,7 +19,9 @@ import email
 
 from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
-
+from email.mime.base import MIMEBase
+from email import encoders
+import yaml
 
 starts_with_mappings={
     '#include' : 'text/x-include-url',
@@ -27,7 +29,8 @@ starts_with_mappings={
     '#cloud-config' : 'text/cloud-config',
     '#upstart-job'  : 'text/upstart-job',
     '#part-handler' : 'text/part-handler',
-    '#cloud-boothook' : 'text/cloud-boothook'
+    '#cloud-boothook' : 'text/cloud-boothook',
+    '#cloud-config-archive' : 'text/cloud-config-archive',
 }
 
 # if 'str' is compressed return decompressed otherwise return it
@@ -43,12 +46,47 @@ def decomp_str(str):
 def do_include(str,parts):
     import urllib
     # is just a list of urls, one per line
+    # also support '#include <url here>'
     for line in str.splitlines():
         if line == "#include": continue
+        if line.startswith("#include"):
+            line = line[len("#include"):].lstrip()
         if line.startswith("#"): continue
         content = urllib.urlopen(line).read()
         process_includes(email.message_from_string(decomp_str(content)),parts)
 
+def explode_cc_archive(archive,parts):
+    for ent in yaml.load(archive):
+        # ent can be one of:
+        #  dict { 'filename' : 'value' , 'content' : 'value', 'type' : 'value' }
+        #    filename and type not be present
+        # or
+        #  scalar(payload)
+        filename = 'part-%03d' % len(parts['content'])
+        def_type = "text/cloud-config"
+        if isinstance(ent,str):
+            content = ent
+            mtype = type_from_startswith(content,def_type)
+        else:
+            content = ent.get('content', '')
+            filename = ent.get('filename', filename)
+            mtype = ent.get('type', None)
+            if mtype == None:
+                mtype = type_from_startswith(payload,def_type)
+
+        print "adding %s,%s" % (filename, mtype)
+        parts['content'].append(content)
+        parts['names'].append(filename)
+        parts['types'].append(mtype)
+    
+def type_from_startswith(payload, default=None):
+    # slist is sorted longest first
+    slist = sorted(starts_with_mappings.keys(), key=lambda e: 0-len(e))
+    for sstr in slist:
+        if payload.startswith(sstr):
+            return(starts_with_mappings[sstr])
+    return default
+
 def process_includes(msg,parts):
     # parts is a dictionary of arrays
     # parts['content']
@@ -67,10 +105,7 @@ def process_includes(msg,parts):
         ctype = None
         ctype_orig = part.get_content_type()
         if ctype_orig == "text/plain":
-            for sstr, gtype in starts_with_mappings.items():
-                if payload.startswith(sstr):
-                    ctype = gtype
-                    break
+            ctype = type_from_startswith(payload)
 
         if ctype is None:
             ctype = ctype_orig
@@ -79,6 +114,10 @@ def process_includes(msg,parts):
             do_include(payload,parts)
             continue
 
+        if ctype == "text/cloud-config-archive":
+            explode_cc_archive(payload,parts)
+            continue
+
         filename = part.get_filename()
         if not filename:
             filename = 'part-%03d' % len(parts['content'])
diff --git a/doc/examples/cloud-config-archive.txt b/doc/examples/cloud-config-archive.txt
new file mode 100644
index 00000000..23b1024c
--- /dev/null
+++ b/doc/examples/cloud-config-archive.txt
@@ -0,0 +1,16 @@
+#cloud-config-archive
+- type: foo/wark
+  filename: bar
+  content: |
+    This is my payload
+    hello
+- this is also payload
+- |
+  multi line payload
+  here
+-
+  type: text/upstart-job
+  filename: my-upstart.conf
+  content: |
+   whats this, yo?
+
-- 
cgit v1.2.3