From 6a803e2fba17ab74973038c1a93654c3127c223f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 26 Aug 2012 15:04:06 -0700 Subject: Add the capability to understand and filter on userdata based on a launch-index (or leave userdata alone if none is provided by the datasource). This works by doing the following. 1. Adjusting the userdata processor to attempt to inject a "Launch-Index" header into the messages headers (by either taking a header that already exists or by looking into the payload to see if it exists there). 2. Adjust the get_userdata ds function to apply a filter on the returned userdata (defaulting to false) that will now use the datasources get_launch_index value to restrict the 'final' message used in consuming user data (the same behavior if not existent). 3. Further down the line processes that use the 'resultant' userdata now will only see the ones for there own launch index (ie cloud-config will be restricted automatically and so on) and are unaffected (although they can now ask the cloud object or the datasource for its launch index via the above new ds method. --- cloudinit/cloud.py | 7 ++++-- cloudinit/sources/DataSourceEc2.py | 3 +++ cloudinit/sources/__init__.py | 49 ++++++++++++++++++++++++++++++++++--- cloudinit/stages.py | 2 +- cloudinit/user_data.py | 50 ++++++++++++++++++++++++++------------ 5 files changed, 90 insertions(+), 21 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py index 620b3c07..af69a541 100644 --- a/cloudinit/cloud.py +++ b/cloudinit/cloud.py @@ -70,12 +70,15 @@ class Cloud(object): return fn # The rest of thes are just useful proxies - def get_userdata(self): - return self.datasource.get_userdata() + def get_userdata(self, apply_filter=True): + return self.datasource.get_userdata(apply_filter) def get_instance_id(self): return self.datasource.get_instance_id() + def get_launch_index(self): + return self.datasource.get_launch_index() + def get_public_ssh_keys(self): return self.datasource.get_public_ssh_keys() diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index 556dcafb..3e450e7e 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -77,6 +77,9 @@ class DataSourceEc2(sources.DataSource): self.metadata_address) return False + def get_launch_index(self): + return self.metadata.get('ami-launch-index') + def get_instance_id(self): return self.metadata['instance-id'] diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 4719d254..a1939d58 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -20,6 +20,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from email.mime.multipart import MIMEMultipart + import abc from cloudinit import importer @@ -59,12 +61,53 @@ class DataSource(object): else: self.ud_proc = ud_proc - def get_userdata(self): + def get_userdata(self, apply_filter=False): if self.userdata is None: - raw_data = self.get_userdata_raw() - self.userdata = self.ud_proc.process(raw_data) + self.userdata = self.ud_proc.process(self.get_userdata_raw()) + if apply_filter: + return self._filter_userdata(self.userdata) return self.userdata + def get_launch_index(self): + return None + + def _filter_userdata(self, processed_ud): + idx = self.get_launch_index() + if idx is None: + return processed_ud + # First do a scan to see if any one with launch-index + # headers, if not just skip this.... + launch_idxs = 0 + for part in processed_ud.walk(): + # multipart/* are just containers + if part.get_content_maintype() == 'multipart': + continue + launch_idx_h = part.get('Launch-Index', None) + if launch_idx_h is not None: + launch_idxs += 1 + if not launch_idxs: + return processed_ud + # Reform a new message with those that either have + # no launch index or ones that have our launch index or ones + # that have some other garbage that we don't know what to do with + accumulating_msg = MIMEMultipart() + tot_attached = 0 + for part in processed_ud.walk(): + # multipart/* are just containers + if part.get_content_maintype() == 'multipart': + continue + try: + launch_idx_h = part.get('Launch-Index', None) + if launch_idx_h is None or int(launch_idx_h) == int(idx): + accumulating_msg.attach(part) + tot_attached += 1 + except: + # If any int conversion fails (or other error), keep the part + accumulating_msg.attach(part) + tot_attached += 1 + accumulating_msg[ud.ATTACHMENT_FIELD] = str(tot_attached) + return accumulating_msg + @property def is_disconnected(self): return False diff --git a/cloudinit/stages.py b/cloudinit/stages.py index c9634a90..af902925 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -347,7 +347,7 @@ class Init(object): sys.path.insert(0, idir) # Ensure datasource fetched before activation (just incase) - user_data_msg = self.datasource.get_userdata() + user_data_msg = self.datasource.get_userdata(True) # This keeps track of all the active handlers c_handlers = helpers.ContentHandlers() diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index af98b488..244e9223 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -58,10 +58,9 @@ class UserDataProcessor(object): self.paths = paths def process(self, blob): - base_msg = convert_string(blob) - process_msg = MIMEMultipart() - self._process_msg(base_msg, process_msg) - return process_msg + accumulating_msg = MIMEMultipart() + self._process_msg(convert_string(blob), accumulating_msg) + return accumulating_msg def _process_msg(self, base_msg, append_msg): for part in base_msg.walk(): @@ -97,11 +96,38 @@ class UserDataProcessor(object): self._attach_part(append_msg, part) + def _attach_launch_index(self, msg): + header_idx = msg.get('Launch-Index', None) + payload_idx = None + try: + payload = util.load_yaml(msg.get_payload(decode=True)) + if payload: + payload_idx = payload.get('launch-index') + except: + pass + # Header overrides contents... + if header_idx is not None: + payload_idx = header_idx + # Nothing found in payload, use header (if anything there) + if payload_idx is None: + payload_idx = header_idx + if payload_idx is not None: + try: + msg.add_header('Launch-Index', str(int(payload_idx))) + except: + pass + def _get_include_once_filename(self, entry): entry_fn = util.hash_blob(entry, 'md5', 64) return os.path.join(self.paths.get_ipath_cur('data'), 'urlcache', entry_fn) + def _process_before_attach(self, msg, attached_id): + if not msg.get_filename(): + msg.add_header('Content-Disposition', + 'attachment', filename=PART_FN_TPL % (attached_id)) + self._attach_launch_index(msg) + def _do_include(self, content, append_msg): # Include a list of urls, one per line # also support '#include ' @@ -204,21 +230,15 @@ class UserDataProcessor(object): outer_msg.replace_header(ATTACHMENT_FIELD, str(fetched_count)) return fetched_count - def _part_filename(self, _unnamed_part, count): - return PART_FN_TPL % (count + 1) - def _attach_part(self, outer_msg, part): """ - Attach an part to an outer message. outermsg must be a MIMEMultipart. - Modifies a header in the message to keep track of number of attachments. + Attach a message to an outer message. outermsg must be a MIMEMultipart. + Modifies a header in the outer message to keep track of number of attachments. """ - cur_c = self._multi_part_count(outer_msg) - if not part.get_filename(): - fn = self._part_filename(part, cur_c) - part.add_header('Content-Disposition', - 'attachment', filename=fn) + part_count = self._multi_part_count(outer_msg) + self._process_before_attach(part, part_count + 1) outer_msg.attach(part) - self._multi_part_count(outer_msg, cur_c + 1) + self._multi_part_count(outer_msg, part_count + 1) # Coverts a raw string into a mime message -- cgit v1.2.3 From ddc50b7450d5defcd1e05732fb3502d230926731 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 26 Aug 2012 15:11:41 -0700 Subject: Don't use the processed userdata if nothing is there. --- cloudinit/sources/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'cloudinit') diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index a1939d58..a3f4af1e 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -72,6 +72,8 @@ class DataSource(object): return None def _filter_userdata(self, processed_ud): + if not processed_ud: + return processed_ud idx = self.get_launch_index() if idx is None: return processed_ud -- cgit v1.2.3 From 7994ac0b1924cf0cc3e3e99ea6f11842f56bc4c8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 26 Aug 2012 20:52:41 -0700 Subject: Fix tests running and add in a check on the content type before we look into the payload as well as make the skip test a function that the datasource module can also use. --- cloudinit/sources/__init__.py | 17 +++++++++++------ cloudinit/user_data.py | 33 +++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 16 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index a3f4af1e..d49b67b2 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -81,8 +81,7 @@ class DataSource(object): # headers, if not just skip this.... launch_idxs = 0 for part in processed_ud.walk(): - # multipart/* are just containers - if part.get_content_maintype() == 'multipart': + if ud.is_skippable(part): continue launch_idx_h = part.get('Launch-Index', None) if launch_idx_h is not None: @@ -94,17 +93,23 @@ class DataSource(object): # that have some other garbage that we don't know what to do with accumulating_msg = MIMEMultipart() tot_attached = 0 + tot_processed = 0 for part in processed_ud.walk(): - # multipart/* are just containers - if part.get_content_maintype() == 'multipart': + if ud.is_skippable(part): continue try: + tot_processed += 1 launch_idx_h = part.get('Launch-Index', None) if launch_idx_h is None or int(launch_idx_h) == int(idx): accumulating_msg.attach(part) tot_attached += 1 - except: - # If any int conversion fails (or other error), keep the part + else: + LOG.debug(("Discarding multipart message %s, " + "launch-index provided destined for %s " + "and not %s"), + tot_processed, launch_idx_h, idx) + except (TypeError, ValueError): + # If any int conversion fails keep the message accumulating_msg.attach(part) tot_attached += 1 accumulating_msg[ud.ATTACHMENT_FIELD] = str(tot_attached) diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index 244e9223..d104d237 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -52,6 +52,9 @@ ARCHIVE_UNDEF_TYPE = "text/cloud-config" # Msg header used to track attachments ATTACHMENT_FIELD = 'Number-Attachments' +# Only the following content types can have there launch index examined +CAN_HAVE_LAUNCH_INDEX = ["text/cloud-config", "text/cloud-config-archive"] + class UserDataProcessor(object): def __init__(self, paths): @@ -64,8 +67,7 @@ class UserDataProcessor(object): def _process_msg(self, base_msg, append_msg): for part in base_msg.walk(): - # multipart/* are just containers - if part.get_content_maintype() == 'multipart': + if is_skippable(part): continue ctype = None @@ -99,13 +101,16 @@ class UserDataProcessor(object): def _attach_launch_index(self, msg): header_idx = msg.get('Launch-Index', None) payload_idx = None - try: - payload = util.load_yaml(msg.get_payload(decode=True)) - if payload: - payload_idx = payload.get('launch-index') - except: - pass - # Header overrides contents... + if msg.get_content_type() in CAN_HAVE_LAUNCH_INDEX: + try: + # See if it has a launch-index field + # that might affect the final header + payload = util.load_yaml(msg.get_payload(decode=True)) + if payload: + payload_idx = payload.get('launch-index') + except: + pass + # Header overrides contents, for now (?) or the other way around? if header_idx is not None: payload_idx = header_idx # Nothing found in payload, use header (if anything there) @@ -114,7 +119,7 @@ class UserDataProcessor(object): if payload_idx is not None: try: msg.add_header('Launch-Index', str(int(payload_idx))) - except: + except (ValueError, TypeError): pass def _get_include_once_filename(self, entry): @@ -241,6 +246,14 @@ class UserDataProcessor(object): self._multi_part_count(outer_msg, part_count + 1) +def is_skippable(part): + # multipart/* are just containers + part_maintype = part.get_content_maintype() or '' + if part_maintype.lower() == 'multipart': + return True + return False + + # Coverts a raw string into a mime message def convert_string(raw_data, headers=None): if not raw_data: -- cgit v1.2.3 From 33cfadd8f35b3f4beb77f871b4173600e42bbe49 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 26 Aug 2012 21:02:00 -0700 Subject: Ensure when an archive is exploded and if it contains the 'launch-index' key that we copy that key over to the right header (which will then be used later when assigning the 'real' header when the message is attached) --- cloudinit/user_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'cloudinit') diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index d104d237..2b76482a 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -209,9 +209,11 @@ class UserDataProcessor(object): if 'filename' in ent: msg.add_header('Content-Disposition', 'attachment', filename=ent['filename']) + if 'launch-index' in ent: + msg.add_header('Launch-Index', str(ent['launch-index'])) for header in list(ent.keys()): - if header in ('content', 'filename', 'type'): + if header in ('content', 'filename', 'type', 'launch-index'): continue msg.add_header(header, ent['header']) -- cgit v1.2.3 From 92b99b325b2d437825cc87253e76c756a136ff28 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 27 Aug 2012 10:56:47 -0700 Subject: Update so that the content types searched for launch-index variable has a little more meaning and by default look in metadata for 'launch-index' and have ec2 instead look for a different variable (thus allowing more datasources to just work). --- cloudinit/cloud.py | 5 +++-- cloudinit/sources/DataSourceEc2.py | 5 ++++- cloudinit/sources/__init__.py | 9 +++++++-- cloudinit/user_data.py | 5 +++-- 4 files changed, 17 insertions(+), 7 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py index af69a541..95e0cfb2 100644 --- a/cloudinit/cloud.py +++ b/cloudinit/cloud.py @@ -76,8 +76,9 @@ class Cloud(object): def get_instance_id(self): return self.datasource.get_instance_id() - def get_launch_index(self): - return self.datasource.get_launch_index() + @property + def launch_index(self): + return self.datasource.launch_index def get_public_ssh_keys(self): return self.datasource.get_public_ssh_keys() diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index 3e450e7e..2a9a70f7 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -77,7 +77,10 @@ class DataSourceEc2(sources.DataSource): self.metadata_address) return False - def get_launch_index(self): + @property + def launch_index(self): + if not self.metadata: + return None return self.metadata.get('ami-launch-index') def get_instance_id(self): diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index d49b67b2..74944e38 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -68,13 +68,18 @@ class DataSource(object): return self._filter_userdata(self.userdata) return self.userdata - def get_launch_index(self): + @property + def launch_index(self): + if not self.metadata: + return None + if 'launch-index' in self.metadata: + return self.metadata['launch-index'] return None def _filter_userdata(self, processed_ud): if not processed_ud: return processed_ud - idx = self.get_launch_index() + idx = self.launch_index if idx is None: return processed_ud # First do a scan to see if any one with launch-index diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index 2b76482a..5d550e1d 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -53,7 +53,8 @@ ARCHIVE_UNDEF_TYPE = "text/cloud-config" ATTACHMENT_FIELD = 'Number-Attachments' # Only the following content types can have there launch index examined -CAN_HAVE_LAUNCH_INDEX = ["text/cloud-config", "text/cloud-config-archive"] +# in there payload, evey other content type can still provide a header +EXAMINE_FOR_LAUNCH_INDEX = ["text/cloud-config", "text/cloud-config-archive"] class UserDataProcessor(object): @@ -101,7 +102,7 @@ class UserDataProcessor(object): def _attach_launch_index(self, msg): header_idx = msg.get('Launch-Index', None) payload_idx = None - if msg.get_content_type() in CAN_HAVE_LAUNCH_INDEX: + if msg.get_content_type() in EXAMINE_FOR_LAUNCH_INDEX: try: # See if it has a launch-index field # that might affect the final header -- cgit v1.2.3 From 2e51e2efc292870479a7b972c7ebc9ceac85da6f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 27 Aug 2012 20:51:00 -0700 Subject: For the userdata 'post-filtering' add in a new folder that can contain filters that serve this purpose only and add in the initial launch-index filter and replace the code in the datasource class that previously did this. --- cloudinit/filters/__init__.py | 21 +++++++++++++ cloudinit/filters/launch_index.py | 63 +++++++++++++++++++++++++++++++++++++++ cloudinit/sources/__init__.py | 51 ++++++------------------------- cloudinit/util.py | 11 ++++--- 4 files changed, 100 insertions(+), 46 deletions(-) create mode 100644 cloudinit/filters/__init__.py create mode 100644 cloudinit/filters/launch_index.py (limited to 'cloudinit') diff --git a/cloudinit/filters/__init__.py b/cloudinit/filters/__init__.py new file mode 100644 index 00000000..da124641 --- /dev/null +++ b/cloudinit/filters/__init__.py @@ -0,0 +1,21 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/cloudinit/filters/launch_index.py b/cloudinit/filters/launch_index.py new file mode 100644 index 00000000..b9e108c4 --- /dev/null +++ b/cloudinit/filters/launch_index.py @@ -0,0 +1,63 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import copy + +from cloudinit import log as logging +from cloudinit import user_data as ud +from cloudinit import util + +LOG = logging.getLogger(__name__) + + +class Filter(object): + def __init__(self, wanted_idx, allow_none=True): + self.wanted_idx = wanted_idx + self.allow_none = allow_none + + def _select(self, message): + if ud.is_skippable(message): + return False + msg_idx = message.get('Launch-Index', None) + if self.allow_none and msg_idx is None: + return True + msg_idx = util.safe_int(msg_idx) + if msg_idx != self.wanted_idx: + return False + return True + + def apply(self, base_message): + if not base_message.is_multipart() or self.wanted_idx is None: + return base_message + prev_msgs = base_message.get_payload(decode=False) + to_attach = [] + for sub_msg in base_message.walk(): + if self._select(sub_msg): + to_attach.append(sub_msg) + if len(prev_msgs) != len(to_attach): + LOG.debug(("Discarding %s multipart messages " + "which do not match launch index %s"), + (len(prev_msgs) - len(to_attach)), self.wanted_idx) + filtered_msg = copy.deepcopy(base_message) + filtered_msg.set_payload(to_attach) + filtered_msg[ud.ATTACHMENT_FIELD] = str(len(to_attach)) + return filtered_msg diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 74944e38..3f611d44 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -29,6 +29,8 @@ from cloudinit import log as logging from cloudinit import user_data as ud from cloudinit import util +from cloudinit.filters import launch_index + DEP_FILESYSTEM = "FILESYSTEM" DEP_NETWORK = "NETWORK" DS_PREFIX = 'DataSource' @@ -77,48 +79,13 @@ class DataSource(object): return None def _filter_userdata(self, processed_ud): - if not processed_ud: - return processed_ud - idx = self.launch_index - if idx is None: - return processed_ud - # First do a scan to see if any one with launch-index - # headers, if not just skip this.... - launch_idxs = 0 - for part in processed_ud.walk(): - if ud.is_skippable(part): - continue - launch_idx_h = part.get('Launch-Index', None) - if launch_idx_h is not None: - launch_idxs += 1 - if not launch_idxs: - return processed_ud - # Reform a new message with those that either have - # no launch index or ones that have our launch index or ones - # that have some other garbage that we don't know what to do with - accumulating_msg = MIMEMultipart() - tot_attached = 0 - tot_processed = 0 - for part in processed_ud.walk(): - if ud.is_skippable(part): - continue - try: - tot_processed += 1 - launch_idx_h = part.get('Launch-Index', None) - if launch_idx_h is None or int(launch_idx_h) == int(idx): - accumulating_msg.attach(part) - tot_attached += 1 - else: - LOG.debug(("Discarding multipart message %s, " - "launch-index provided destined for %s " - "and not %s"), - tot_processed, launch_idx_h, idx) - except (TypeError, ValueError): - # If any int conversion fails keep the message - accumulating_msg.attach(part) - tot_attached += 1 - accumulating_msg[ud.ATTACHMENT_FIELD] = str(tot_attached) - return accumulating_msg + filters = [ + launch_index.Filter(util.safe_int(self.launch_index)), + ] + new_ud = processed_ud + for f in filters: + new_ud = f.apply(new_ud) + return new_ud @property def is_disconnected(self): diff --git a/cloudinit/util.py b/cloudinit/util.py index 6872cc31..33da73eb 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1285,12 +1285,15 @@ def ensure_file(path, mode=0644): write_file(path, content='', omode="ab", mode=mode) -def chmod(path, mode): - real_mode = None +def safe_int(possible_int): try: - real_mode = int(mode) + return int(possible_int) except (ValueError, TypeError): - pass + return None + + +def chmod(path, mode): + real_mode = safe_int(mode) if path and real_mode: with SeLinuxGuard(path): os.chmod(path, real_mode) -- cgit v1.2.3 From ff60020fa3d8e457cf9d1d543af9193376bf598c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 29 Aug 2012 10:52:44 -0700 Subject: Don't use walk and handle the walking ourselves to ensure that we recreate all child messages correctly if they also contain submessages, ensuring that we don't flatten the message list when we previously used walk. --- cloudinit/filters/launch_index.py | 44 +++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/filters/launch_index.py b/cloudinit/filters/launch_index.py index b9e108c4..4299fb46 100644 --- a/cloudinit/filters/launch_index.py +++ b/cloudinit/filters/launch_index.py @@ -35,8 +35,6 @@ class Filter(object): self.allow_none = allow_none def _select(self, message): - if ud.is_skippable(message): - return False msg_idx = message.get('Launch-Index', None) if self.allow_none and msg_idx is None: return True @@ -45,19 +43,33 @@ class Filter(object): return False return True - def apply(self, base_message): - if not base_message.is_multipart() or self.wanted_idx is None: - return base_message - prev_msgs = base_message.get_payload(decode=False) - to_attach = [] - for sub_msg in base_message.walk(): - if self._select(sub_msg): - to_attach.append(sub_msg) - if len(prev_msgs) != len(to_attach): + def _do_filter(self, message): + # Don't use walk() here since we want to do the reforming of the + # messages ourselves and not flatten the message listings... + if not self._select(message): + return None + if message.is_multipart(): + # Recreate it and its child messages + prev_msgs = message.get_payload(decode=False) + new_msgs = [] + discarded = 0 + for m in prev_msgs: + m = self._do_filter(m) + if m is not None: + new_msgs.append(m) + else: + discarded += 1 LOG.debug(("Discarding %s multipart messages " "which do not match launch index %s"), - (len(prev_msgs) - len(to_attach)), self.wanted_idx) - filtered_msg = copy.deepcopy(base_message) - filtered_msg.set_payload(to_attach) - filtered_msg[ud.ATTACHMENT_FIELD] = str(len(to_attach)) - return filtered_msg + discarded, self.wanted_idx) + new_message = copy.copy(message) + new_message.set_payload(new_msgs) + new_message[ud.ATTACHMENT_FIELD] = str(len(new_msgs)) + return new_message + else: + return copy.copy(message) + + def apply(self, root_message): + if self.wanted_idx is None: + return root_message + return self._do_filter(root_message) -- cgit v1.2.3