cloud-init/cloudinit/handlers/cloud_config.py
Joshua Harlow 8867c8096b Fix the cloud config merging so that it is backwards compat.
The new change for merging works well in the mergedict case
but the default merging type for cloud config needs to reflect
how yaml was loaded in bulk, which is the same as the replacing
keys merging type that is now provided.
2013-05-09 22:34:31 -07:00

129 lines
4.8 KiB
Python

# 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 <scott.moser@canonical.com>
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
# 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 <http://www.gnu.org/licenses/>.
from cloudinit import handlers
from cloudinit import log as logging
from cloudinit import mergers
from cloudinit import util
from cloudinit.settings import (PER_ALWAYS)
LOG = logging.getLogger(__name__)
MERGE_HEADER = 'Merge-Type'
# Due to the way the loading of yaml configuration was done previously,
# where previously each cloud config part was appended to a larger yaml
# file and then finally that file was loaded as one big yaml file we need
# to mimic that behavior by altering the default strategy to be replacing
# keys of later mergers.
DEF_MERGERS = mergers.string_extract_mergers('dict(replace)+list()+str()')
class CloudConfigPartHandler(handlers.Handler):
def __init__(self, paths, **_kwargs):
handlers.Handler.__init__(self, PER_ALWAYS, version=3)
self.cloud_buf = None
self.cloud_fn = paths.get_ipath("cloud_config")
self.file_names = []
def list_types(self):
return [
handlers.type_from_starts_with("#cloud-config"),
]
def _write_cloud_config(self):
if not self.cloud_fn:
return
# Capture which files we merged from...
file_lines = []
if self.file_names:
file_lines.append("# from %s files" % (len(self.file_names)))
for fn in self.file_names:
if not fn:
fn = '?'
file_lines.append("# %s" % (fn))
file_lines.append("")
if self.cloud_buf is not None:
# Something was actually gathered....
lines = [
"#cloud-config",
'',
]
lines.extend(file_lines)
lines.append(util.yaml_dumps(self.cloud_buf))
else:
lines = []
util.write_file(self.cloud_fn, "\n".join(lines), 0600)
def _extract_mergers(self, payload, headers):
merge_header_headers = ''
for h in [MERGE_HEADER, 'X-%s' % (MERGE_HEADER)]:
tmp_h = headers.get(h, '')
if tmp_h:
merge_header_headers = tmp_h
break
# Select either the merge-type from the content
# or the merge type from the headers or default to our own set
# if neither exists (or is empty) from the later.
payload_yaml = util.load_yaml(payload)
mergers_yaml = mergers.dict_extract_mergers(payload_yaml)
mergers_header = mergers.string_extract_mergers(merge_header_headers)
all_mergers = []
all_mergers.extend(mergers_yaml)
all_mergers.extend(mergers_header)
if not all_mergers:
all_mergers = DEF_MERGERS
return (payload_yaml, all_mergers)
def _merge_part(self, payload, headers):
(payload_yaml, my_mergers) = self._extract_mergers(payload, headers)
LOG.debug("Merging by applying %s", my_mergers)
merger = mergers.construct(my_mergers)
if self.cloud_buf is None:
# First time through, merge with an empty dict...
self.cloud_buf = {}
self.cloud_buf = merger.merge(self.cloud_buf, payload_yaml)
def _reset(self):
self.file_names = []
self.cloud_buf = None
def handle_part(self, _data, ctype, filename, # pylint: disable=W0221
payload, _frequency, headers): # pylint: disable=W0613
if ctype == handlers.CONTENT_START:
self._reset()
return
if ctype == handlers.CONTENT_END:
self._write_cloud_config()
self._reset()
return
try:
self._merge_part(payload, headers)
# Ensure filename is ok to store
for i in ("\n", "\r", "\t"):
filename = filename.replace(i, " ")
self.file_names.append(filename.strip())
except:
util.logexc(LOG, "Failed at merging in cloud config part from %s",
filename)