summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2014-09-12 15:46:00 -0400
committerScott Moser <smoser@ubuntu.com>2014-09-12 15:46:00 -0400
commit3ac51517c6e561a4c3590af972b95602aa496afd (patch)
tree797e98be2637f9523defa2e08c01ff5a1d39a19f
parent26c1cf9eeeb39e9bfcc1a37743679e5c76ab97b0 (diff)
parenta515846e2cf59568d402baee70950a0b1b1d3182 (diff)
downloadvyos-cloud-init-3ac51517c6e561a4c3590af972b95602aa496afd.tar.gz
vyos-cloud-init-3ac51517c6e561a4c3590af972b95602aa496afd.zip
OpenStack: fix consumption of vendor-data to allow namespacing
Not all vendor data is destined for cloud-init. This sanely reads the vendor data as a dict, array or a string.
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py13
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py12
-rw-r--r--cloudinit/sources/helpers/openstack.py31
-rw-r--r--tests/unittests/test_datasource/test_openstack.py35
4 files changed, 77 insertions, 14 deletions
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index a27d07fb..4e5d90de 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -126,12 +126,13 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
self.version = results['version']
self.files.update(results.get('files', {}))
- # If there is no vendordata, set vd to an empty dict instead of None
- vd = results.get('vendordata', {})
- # if vendordata includes 'cloud-init', then read that explicitly
- # for cloud-init (for namespacing).
- if 'cloud-init' in vd:
- self.vendordata_raw = vd['cloud-init']
+ vd = results.get('vendordata')
+ self.vendordata_pure = vd
+ try:
+ self.vendordata_raw = openstack.convert_vendordata_json(vd)
+ except ValueError as e:
+ LOG.warn("Invalid content in vendor-data: %s", e)
+ self.vendordata_raw = None
return True
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index 765137c6..469c2e2a 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -140,13 +140,13 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
self.version = results['version']
self.files.update(results.get('files', {}))
- # if vendordata includes 'cloud-init', then read that explicitly
- # for cloud-init (for namespacing).
vd = results.get('vendordata')
- if isinstance(vd, dict) and 'cloud-init' in vd:
- self.vendordata_raw = vd['cloud-init']
- else:
- self.vendordata_raw = vd
+ self.vendordata_pure = vd
+ try:
+ self.vendordata_raw = openstack.convert_vendordata_json(vd)
+ except ValueError as e:
+ LOG.warn("Invalid content in vendor-data: %s", e)
+ self.vendordata_raw = None
return True
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
index a7dd05df..b7e19314 100644
--- a/cloudinit/sources/helpers/openstack.py
+++ b/cloudinit/sources/helpers/openstack.py
@@ -21,6 +21,7 @@
import abc
import base64
import copy
+import functools
import os
from cloudinit import ec2_utils
@@ -203,6 +204,9 @@ class BaseReader(object):
If not a valid location, raise a NonReadable exception.
"""
+ load_json_anytype = functools.partial(
+ util.load_json, root_types=(dict, basestring, list))
+
def datafiles(version):
files = {}
files['metadata'] = (
@@ -221,7 +225,7 @@ class BaseReader(object):
files['vendordata'] = (
self._path_join("openstack", version, 'vendor_data.json'),
False,
- util.load_json,
+ load_json_anytype,
)
return files
@@ -459,3 +463,28 @@ class MetadataReader(BaseReader):
return ec2_utils.get_instance_metadata(ssl_details=self.ssl_details,
timeout=self.timeout,
retries=self.retries)
+
+
+def convert_vendordata_json(data, recurse=True):
+ """ data: a loaded json *object* (strings, arrays, dicts).
+ return something suitable for cloudinit vendordata_raw.
+
+ if data is:
+ None: return None
+ string: return string
+ list: return data
+ the list is then processed in UserDataProcessor
+ dict: return convert_vendordata_json(data.get('cloud-init'))
+ """
+ if not data:
+ return None
+ if isinstance(data, (str, unicode, basestring)):
+ return data
+ if isinstance(data, list):
+ return copy.deepcopy(data)
+ if isinstance(data, dict):
+ if recurse is True:
+ return convert_vendordata_json(data.get('cloud-init'),
+ recurse=False)
+ raise ValueError("vendordata['cloud-init'] cannot be dict")
+ raise ValueError("Unknown data type for vendordata: %s" % type(data))
diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py
index 6809823e..7b4e651a 100644
--- a/tests/unittests/test_datasource/test_openstack.py
+++ b/tests/unittests/test_datasource/test_openstack.py
@@ -19,6 +19,7 @@
import copy
import json
import re
+import unittest
from StringIO import StringIO
@@ -256,7 +257,8 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
self.assertEquals(EC2_META, ds_os.ec2_metadata)
self.assertEquals(USER_DATA, ds_os.userdata_raw)
self.assertEquals(2, len(ds_os.files))
- self.assertEquals(VENDOR_DATA, ds_os.vendordata_raw)
+ self.assertEquals(VENDOR_DATA, ds_os.vendordata_pure)
+ self.assertEquals(ds_os.vendordata_raw, None)
@hp.activate
def test_bad_datasource_meta(self):
@@ -314,3 +316,34 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
found = ds_os.get_data()
self.assertFalse(found)
self.assertIsNone(ds_os.version)
+
+
+class TestVendorDataLoading(unittest.TestCase):
+ def cvj(self, data):
+ return openstack.convert_vendordata_json(data)
+
+ def test_vd_load_none(self):
+ # non-existant vendor-data should return none
+ self.assertIsNone(self.cvj(None))
+
+ def test_vd_load_string(self):
+ self.assertEqual(self.cvj("foobar"), "foobar")
+
+ def test_vd_load_list(self):
+ data = [{'foo': 'bar'}, 'mystring', list(['another', 'list'])]
+ self.assertEqual(self.cvj(data), data)
+
+ def test_vd_load_dict_no_ci(self):
+ self.assertEqual(self.cvj({'foo': 'bar'}), None)
+
+ def test_vd_load_dict_ci_dict(self):
+ self.assertRaises(ValueError, self.cvj,
+ {'foo': 'bar', 'cloud-init': {'x': 1}})
+
+ def test_vd_load_dict_ci_string(self):
+ data = {'foo': 'bar', 'cloud-init': 'VENDOR_DATA'}
+ self.assertEqual(self.cvj(data), data['cloud-init'])
+
+ def test_vd_load_dict_ci_list(self):
+ data = {'foo': 'bar', 'cloud-init': ['VD_1', 'VD_2']}
+ self.assertEqual(self.cvj(data), data['cloud-init'])