diff options
Diffstat (limited to 'cloudinit')
-rw-r--r-- | cloudinit/user_data.py | 196 |
1 files changed, 10 insertions, 186 deletions
diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index 28c9f2ce..2f01579a 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -20,7 +20,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import abc import os import email @@ -28,56 +27,24 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase -from cloudinit import importer +from cloudinit import handlers from cloudinit import log as logging from cloudinit import url_helper from cloudinit import util -from cloudinit.settings import (PER_ALWAYS, PER_INSTANCE, FREQUENCIES) - LOG = logging.getLogger(__name__) -# Special content types that signal the start and end of processing -CONTENT_END = "__end__" -CONTENT_START = "__begin__" -CONTENT_SIGNALS = [CONTENT_START, CONTENT_END] - -# Used when a part-handler type is encountered -# to allow for registration of new types. -PART_CONTENT_TYPES = ["text/part-handler"] -PART_HANDLER_FN_TMPL = 'part-handler-%03d' - -# For parts without filenames -PART_FN_TPL = 'part-%03d' - -# Used as the content type when a message is not multipart -# and it doesn't contain its own content-type -NOT_MULTIPART_TYPE = "text/x-not-multipart" -OCTET_TYPE = 'application/octet-stream' - -# Different file beginnings to there content type -INCLUSION_TYPES_MAP = { - '#include': 'text/x-include-url', - '#include-once': 'text/x-include-once-url', - '#!': 'text/x-shellscript', - '#cloud-config': 'text/cloud-config', - '#upstart-job': 'text/upstart-job', - '#part-handler': 'text/part-handler', - '#cloud-boothook': 'text/cloud-boothook', - '#cloud-config-archive': 'text/cloud-config-archive', -} +# Constants copied in from the handler module +NOT_MULTIPART_TYPE = handlers.NOT_MULTIPART_TYPE +PART_FN_TPL = handlers.PART_FN_TPL +OCTET_TYPE = handlers.OCTET_TYPE -# Sorted longest first -INCLUSION_SRCH = sorted(list(INCLUSION_TYPES_MAP.keys()), - key=(lambda e: 0 - len(e))) - -# Various special content types +# Various special content types that cause special actions TYPE_NEEDED = ["text/plain", "text/x-not-multipart"] INCLUDE_TYPES = ['text/x-include-url', 'text/x-include-once-url'] ARCHIVE_TYPES = ["text/cloud-config-archive"] UNDEF_TYPE = "text/plain" ARCHIVE_UNDEF_TYPE = "text/cloud-config" -OCTET_TYPE = 'application/octet-stream' # Msg header used to track attachments ATTACHMENT_FIELD = 'Number-Attachments' @@ -107,7 +74,7 @@ class UserDataProcessor(object): ctype_orig = UNDEF_TYPE if ctype_orig in TYPE_NEEDED: - ctype = type_from_starts_with(payload) + ctype = handlers.type_from_starts_with(payload) if ctype is None: ctype = ctype_orig @@ -181,7 +148,8 @@ class UserDataProcessor(object): content = ent.get('content', '') mtype = ent.get('type') if not mtype: - mtype = type_from_starts_with(content, ARCHIVE_UNDEF_TYPE) + mtype = handlers.type_from_starts_with(content, + ARCHIVE_UNDEF_TYPE) maintype, subtype = mtype.split('/', 1) if maintype == "text": @@ -232,146 +200,6 @@ class UserDataProcessor(object): self._multi_part_count(outer_msg, cur + 1) -class PartHandler(object): - - __metaclass__ = abc.ABCMeta - - def __init__(self, frequency, version=2): - self.handler_version = version - self.frequency = frequency - - def __repr__(self): - return "%s: [%s]" % (util.obj_name(self), self.list_types()) - - @abc.abstractmethod - def list_types(self): - raise NotImplementedError() - - def handle_part(self, data, ctype, filename, payload, frequency): - return self._handle_part(data, ctype, filename, payload, frequency) - - @abc.abstractmethod - def _handle_part(self, data, ctype, filename, payload, frequency): - raise NotImplementedError() - - -def fixup_handler(mod, def_freq=PER_INSTANCE): - if not hasattr(mod, "handler_version"): - setattr(mod, "handler_version", 1) - if not hasattr(mod, 'list_types'): - def empty_types(): - return [] - setattr(mod, 'list_types', empty_types) - if not hasattr(mod, 'frequency'): - setattr(mod, 'frequency', def_freq) - else: - freq = mod.frequency - if freq and freq not in FREQUENCIES: - LOG.warn("Module %s has an unknown frequency %s", mod, freq) - if not hasattr(mod, 'handle_part'): - def empty_handler(_data, _ctype, _filename, _payload): - pass - setattr(mod, 'handle_part', empty_handler) - return mod - - -def run_part(mod, data, ctype, filename, payload, frequency): - mod_freq = mod.frequency - if not (mod_freq == PER_ALWAYS or - (frequency == PER_INSTANCE and mod_freq == PER_INSTANCE)): - return - mod_ver = mod.handler_version - try: - if mod_ver == 1: - mod.handle_part(data, ctype, filename, payload) - else: - mod.handle_part(data, ctype, filename, payload, frequency) - except: - util.logexc(LOG, ("Failed calling mod %s (%s, %s, %s)" - " with frequency %s"), - mod, ctype, filename, - mod_ver, frequency) - - -def call_begin(mod, data, frequency): - run_part(mod, data, CONTENT_START, None, None, frequency) - - -def call_end(mod, data, frequency): - run_part(mod, data, CONTENT_END, None, None, frequency) - - -def walker_handle_handler(pdata, _ctype, _filename, payload): - curcount = pdata['handlercount'] - modname = PART_HANDLER_FN_TMPL % (curcount) - frequency = pdata['frequency'] - modfname = os.path.join(pdata['handlerdir'], "%s" % (modname)) - if not modfname.endswith(".py"): - modfname = "%s.py" % (modfname) - # TODO: Check if path exists?? - util.write_file(modfname, payload, 0600) - handlers = pdata['handlers'] - try: - mod = fixup_handler(importer.import_module(modname)) - handlers.register(mod) - call_begin(mod, pdata['data'], frequency) - pdata['handlercount'] = curcount + 1 - except: - util.logexc(LOG, "Failed at registered python file: %s", modfname) - - -def extract_first_or_bytes(blob, size): - # Extract the first line upto X bytes or X bytes from more than the - # first line if the first line does not contain enough bytes - first_line = blob.split("\n", 1)[0] - if len(first_line) >= size: - start = first_line[:size] - else: - start = blob[0:size] - return start - - -def walker_callback(pdata, ctype, filename, payload): - if ctype in PART_CONTENT_TYPES: - walker_handle_handler(pdata, ctype, filename, payload) - return - handlers = pdata['handlers'] - if ctype not in handlers: - # Extract the first line or 24 bytes for displaying in the log - start = extract_first_or_bytes(payload, 24) - details = "'%s...'" % (start.encode("string-escape")) - if ctype == NOT_MULTIPART_TYPE: - LOG.warning("Unhandled non-multipart (%s) userdata: %s", - ctype, details) - else: - LOG.warning("Unhandled unknown content-type (%s) userdata: %s", - ctype, details) - else: - run_part(handlers[ctype], pdata['data'], ctype, filename, - payload, pdata['frequency']) - - -# Callback is a function that will be called with -# (data, content_type, filename, payload) -def walk(msg, callback, data): - partnum = 0 - for part in msg.walk(): - # multipart/* are just containers - if part.get_content_maintype() == 'multipart': - continue - - ctype = part.get_content_type() - if ctype is None: - ctype = OCTET_TYPE - - filename = part.get_filename() - if not filename: - filename = PART_FN_TPL % (partnum) - - callback(data, ctype, filename, part.get_payload(decode=True)) - partnum = partnum + 1 - - # Coverts a raw string into a mime message def convert_string(raw_data, headers=None): if not raw_data: @@ -394,9 +222,5 @@ def convert_string(raw_data, headers=None): return msg -def type_from_starts_with(payload, default=None): - for text in INCLUSION_SRCH: - if payload.startswith(text): - return INCLUSION_TYPES_MAP[text] - return default + |