summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--cloudinit/config/cc_seed_random.py47
-rw-r--r--cloudinit/distros/freebsd.py2
-rw-r--r--cloudinit/netinfo.py17
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py6
-rw-r--r--cloudinit/sources/DataSourceConfigDrive.py2
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py12
-rw-r--r--cloudinit/sources/DataSourceSmartOS.py11
-rw-r--r--cloudinit/sources/helpers/__init__.py1
-rw-r--r--cloudinit/sources/helpers/openstack.py21
-rw-r--r--doc/status.txt12
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py24
-rw-r--r--tests/unittests/test_datasource/test_azure.py4
-rw-r--r--tests/unittests/test_datasource/test_configdrive.py2
-rw-r--r--tests/unittests/test_datasource/test_nocloud.py1
-rw-r--r--tests/unittests/test_datasource/test_smartos.py6
-rw-r--r--tests/unittests/test_handler/test_handler_growpart.py3
-rw-r--r--tests/unittests/test_handler/test_handler_seed_random.py67
-rwxr-xr-xtools/make-tarball8
19 files changed, 206 insertions, 46 deletions
diff --git a/ChangeLog b/ChangeLog
index dcf891d7..c7d42db4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -31,6 +31,12 @@
- Add GCE datasource [Vaidas Jablonskis]
- Add native Openstack datasource which reads openstack metadata
rather than relying on EC2 data in openstack metadata service.
+ - SmartOS, AltCloud: disable running on arm systems due to bug
+ (LP: #1243287, #1285686) [Oleg Strikov]
+ - Allow running a command to seed random, default is 'pollinate -q'
+ (LP: #1286316) [Dustin Kirkland]
+ - Write status to /run/cloud-init/status.json for consumption by
+ other programs (LP: #1284439)
0.7.4:
- fix issue mounting 'ephemeral0' if ephemeral0 was an alias for a
partitioned block device with target filesystem on ephemeral0.1.
diff --git a/cloudinit/config/cc_seed_random.py b/cloudinit/config/cc_seed_random.py
index 22a31f29..599280f6 100644
--- a/cloudinit/config/cc_seed_random.py
+++ b/cloudinit/config/cc_seed_random.py
@@ -1,8 +1,11 @@
# vi: ts=4 expandtab
#
# Copyright (C) 2013 Yahoo! Inc.
+# Copyright (C) 2014 Canonical, Ltd
#
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
+# Author: Dustin Kirkland <kirkland@ubuntu.com>
+# Author: Scott Moser <scott.moser@canonical.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
@@ -20,9 +23,11 @@ import base64
from StringIO import StringIO
from cloudinit.settings import PER_INSTANCE
+from cloudinit import log as logging
from cloudinit import util
frequency = PER_INSTANCE
+LOG = logging.getLogger(__name__)
def _decode(data, encoding=None):
@@ -38,24 +43,48 @@ def _decode(data, encoding=None):
raise IOError("Unknown random_seed encoding: %s" % (encoding))
-def handle(name, cfg, cloud, log, _args):
- if not cfg or "random_seed" not in cfg:
- log.debug(("Skipping module named %s, "
- "no 'random_seed' configuration found"), name)
+def handle_random_seed_command(command, required):
+ if not command and required:
+ raise ValueError("no command found but required=true")
+ elif not command:
+ LOG.debug("no command provided")
return
- my_cfg = cfg['random_seed']
- seed_path = my_cfg.get('file', '/dev/urandom')
+ cmd = command[0]
+ if not util.which(cmd):
+ if required:
+ raise ValueError("command '%s' not found but required=true", cmd)
+ else:
+ LOG.debug("command '%s' not found for seed_command", cmd)
+ return
+ util.subp(command)
+
+
+def handle(name, cfg, cloud, log, _args):
+ mycfg = cfg.get('random_seed', {})
+ seed_path = mycfg.get('file', '/dev/urandom')
+ seed_data = mycfg.get('data', '')
+
seed_buf = StringIO()
- seed_buf.write(_decode(my_cfg.get('data', ''),
- encoding=my_cfg.get('encoding')))
+ if seed_data:
+ seed_buf.write(_decode(seed_data, encoding=mycfg.get('encoding')))
+ # 'random_seed' is set up by Azure datasource, and comes already in
+ # openstack meta_data.json
metadata = cloud.datasource.metadata
if metadata and 'random_seed' in metadata:
seed_buf.write(metadata['random_seed'])
seed_data = seed_buf.getvalue()
if len(seed_data):
- log.debug("%s: adding %s bytes of random seed entrophy to %s", name,
+ log.debug("%s: adding %s bytes of random seed entropy to %s", name,
len(seed_data), seed_path)
util.append_file(seed_path, seed_data)
+
+ command = mycfg.get('command', ['pollinate', '-q'])
+ req = mycfg.get('command_required', False)
+ try:
+ handle_random_seed_command(command=command, required=req)
+ except ValueError as e:
+ log.warn("handling random command [%s] failed: %s", command, e)
+ raise e
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index afb502c9..d98f9578 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -79,7 +79,7 @@ class Distro(distros.Distro):
return val
def _read_system_hostname(self):
- sys_hostname = self._read_hostname()
+ sys_hostname = self._read_hostname(filename=None)
return ('rc.conf', sys_hostname)
def _read_hostname(self, filename, default=None):
diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
index ac3c011f..30b6f3b3 100644
--- a/cloudinit/netinfo.py
+++ b/cloudinit/netinfo.py
@@ -52,18 +52,23 @@ def netdev_info(empty=""):
fieldpost = "6"
for i in range(len(toks)):
- if toks[i] == "hwaddr" or toks[i] == "ether":
- try:
- devs[curdev]["hwaddr"] = toks[i + 1]
- except IndexError:
- pass
+ # older net-tools (ubuntu) show 'inet addr:xx.yy',
+ # newer (freebsd and fedora) show 'inet xx.yy'
+ # just skip this 'inet' entry. (LP: #1285185)
+ try:
+ if (toks[i] in ("inet", "inet6") and
+ toks[i + 1].startswith("addr:")):
+ continue
+ except IndexError:
+ pass
# Couple the different items we're interested in with the correct
# field since FreeBSD/CentOS/Fedora differ in the output.
ifconfigfields = {
"addr:": "addr", "inet": "addr",
"bcast:": "bcast", "broadcast": "bcast",
- "mask:": "mask", "netmask": "mask"
+ "mask:": "mask", "netmask": "mask",
+ "hwaddr": "hwaddr", "ether": "hwaddr",
}
for origfield, field in ifconfigfields.items():
target = "%s%s" % (field, fieldpost)
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index a834f8eb..1e913a6e 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -115,6 +115,12 @@ class DataSourceAltCloud(sources.DataSource):
'''
+ uname_arch = os.uname()[4]
+ if uname_arch.startswith("arm") or uname_arch == "aarch64":
+ # Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
+ LOG.debug("Disabling AltCloud datasource on arm (LP: #1243287)")
+ return 'UNKNOWN'
+
cmd = CMD_DMI_SYSTEM
try:
(cmd_out, _err) = util.subp(cmd)
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index 142e0eb8..0c35f83a 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -181,7 +181,7 @@ def get_previous_iid(paths):
# hasn't declared itself found.
fname = os.path.join(paths.get_cpath('data'), 'instance-id')
try:
- return util.load_file(fname)
+ return util.load_file(fname).rstrip("\n")
except IOError:
return None
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index 72ec8075..0970d07b 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -88,11 +88,11 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
md_urls = []
url2base = {}
for url in urls:
- md_url = url_helper.combine_url(url, 'openstack',
- openstack.OS_LATEST,
- 'meta_data.json')
- md_urls.append(md_url)
- url2base[md_url] = url
+ for version in openstack.OS_VERSIONS + (openstack.OS_LATEST,):
+ md_url = url_helper.combine_url(url, 'openstack',
+ version, 'meta_data.json')
+ md_urls.append(md_url)
+ url2base[md_url] = url
(max_wait, timeout) = self._get_url_settings()
start_time = time.time()
@@ -120,7 +120,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
read_metadata_service,
args=[self.metadata_address],
kwargs={'ssl_details': self.ssl_details,
- 'version': openstack.OS_LATEST})
+ 'version': openstack.OS_HAVANA})
except openstack.NonReadable:
return False
except (openstack.BrokenMetadata, IOError):
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index 8d4a16e6..7c1eb09a 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -174,6 +174,12 @@ class DataSourceSmartOS(sources.DataSource):
LOG.debug("Host does not appear to be on SmartOS")
return False
+ uname_arch = os.uname()[4]
+ if uname_arch.startswith("arm") or uname_arch == "aarch64":
+ # Disabling because dmidcode in dmi_data() crashes kvm process
+ LOG.debug("Disabling SmartOS datasource on arm (LP: #1243287)")
+ return False
+
dmi_info = dmi_data()
if dmi_info is False:
LOG.debug("No dmidata utility found")
@@ -203,7 +209,7 @@ class DataSourceSmartOS(sources.DataSource):
# executed. It may be of any format that would be considered
# executable in the guest instance.
#
- # We write 'user-script' and 'operator-script' into the
+ # We write 'user-script' and 'operator-script' into the
# instance/data directory. The default vendor-data then handles
# executing them later.
data_d = os.path.join(self.paths.get_cpath(), 'instances',
@@ -238,7 +244,8 @@ class DataSourceSmartOS(sources.DataSource):
md['vendor-data'] = BUILTIN_VENDOR_DATA % {
'user_script': user_script,
'operator_script': operator_script,
- 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"), 'per-boot'),
+ 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"),
+ 'per-boot'),
}
self.metadata = util.mergemanydict([md, self.metadata])
diff --git a/cloudinit/sources/helpers/__init__.py b/cloudinit/sources/helpers/__init__.py
index 2cf99ec8..386225d5 100644
--- a/cloudinit/sources/helpers/__init__.py
+++ b/cloudinit/sources/helpers/__init__.py
@@ -11,4 +11,3 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
index a17148d3..0fac0335 100644
--- a/cloudinit/sources/helpers/openstack.py
+++ b/cloudinit/sources/helpers/openstack.py
@@ -44,12 +44,15 @@ KEY_COPIES = (
('local-hostname', 'hostname', False),
('instance-id', 'uuid', True),
)
+OS_LATEST = 'latest'
+OS_FOLSOM = '2012-08-10'
+OS_GRIZZLY = '2013-04-04'
+OS_HAVANA = '2013-10-17'
OS_VERSIONS = (
- '2012-08-10', # folsom
- '2013-04-04', # grizzly
- '2013-10-17', # havana
+ OS_FOLSOM,
+ OS_GRIZZLY,
+ OS_HAVANA,
)
-OS_LATEST = 'latest'
class NonReadable(IOError):
@@ -176,12 +179,12 @@ class BaseReader(object):
potential_version)
if self._path_exists(path):
if potential_version != version:
- LOG.warn("Version '%s' not available, attempting to use"
- " version '%s' instead", version,
- potential_version)
+ LOG.debug("Version '%s' not available, attempting to use"
+ " version '%s' instead", version,
+ potential_version)
return potential_version
- LOG.warn("Version '%s' not available, attempting to use '%s'"
- " instead", version, OS_LATEST)
+ LOG.debug("Version '%s' not available, attempting to use '%s'"
+ " instead", version, OS_LATEST)
return OS_LATEST
def read_v2(self, version=None):
diff --git a/doc/status.txt b/doc/status.txt
index 5958fa85..60993216 100644
--- a/doc/status.txt
+++ b/doc/status.txt
@@ -3,7 +3,7 @@ wishing to use it to determine cloud-init status.
It will manage 2 files:
status.json
- finished.json
+ result.json
The files will be written to /var/lib/cloud/data/ .
A symlink will be created in /run/cloud-init. The link from /run is to ensure
@@ -33,18 +33,20 @@ status.json's format is:
of each of the above stages to determine the state.
}
-finished.json's format is:
+result.json's format is:
{
+ 'v1': {
'datasource': string describing the datasource found
'errors': [] # list of errors reported
+ }
}
Thus, to determine if cloud-init is finished:
- fin = "/run/cloud-init/finished.json"
+ fin = "/run/cloud-init/result.json"
if os.path.exists(fin):
ret = json.load(open(fin, "r"))
- if len(ret):
- print "Finished with errors:" + "\n".join(ret['errors'])
+ if len(ret['v1']['errors']):
+ print "Finished with errors:" + "\n".join(ret['v1']['errors'])
else:
print "Finished no errors"
else:
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index bda61c7e..eaaa90e6 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -33,6 +33,8 @@ import cloudinit.sources.DataSourceAltCloud
from cloudinit.sources.DataSourceAltCloud import DataSourceAltCloud
from cloudinit.sources.DataSourceAltCloud import read_user_data_callback
+OS_UNAME_ORIG = getattr(os, 'uname')
+
def _write_cloud_info_file(value):
'''
@@ -104,11 +106,16 @@ class TestGetCloudType(TestCase):
def setUp(self):
'''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ # We have a different code path for arm to deal with LP1243287
+ # We have to switch arch to x86_64 to avoid test failure
+ force_arch('x86_64')
def tearDown(self):
# Reset
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
['dmidecode', '--string', 'system-product-name']
+ # Return back to original arch
+ force_arch()
def test_rhev(self):
'''
@@ -238,6 +245,9 @@ class TestGetDataNoCloudInfoFile(TestCase):
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'no such file'
+ # We have a different code path for arm to deal with LP1243287
+ # We have to switch arch to x86_64 to avoid test failure
+ force_arch('x86_64')
def tearDown(self):
# Reset
@@ -245,6 +255,8 @@ class TestGetDataNoCloudInfoFile(TestCase):
'/etc/sysconfig/cloud-info'
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
['dmidecode', '--string', 'system-product-name']
+ # Return back to original arch
+ force_arch()
def test_rhev_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing RHEV.'''
@@ -442,4 +454,16 @@ class TestReadUserDataCallback(TestCase):
_remove_user_data_files(self.mount_dir)
self.assertEquals(None, read_user_data_callback(self.mount_dir))
+
+def force_arch(arch=None):
+
+ def _os_uname():
+ return ('LINUX', 'NODENAME', 'RELEASE', 'VERSION', arch)
+
+ if arch:
+ setattr(os, 'uname', _os_uname)
+ elif arch is None:
+ setattr(os, 'uname', OS_UNAME_ORIG)
+
+
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 44c537f4..ccfd672a 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -352,13 +352,13 @@ class TestAzureDataSource(MockerTestCase):
ovf_env_path = os.path.join(self.waagent_d, 'ovf-env.xml')
self.assertTrue(os.path.exists(ovf_env_path))
self.assertEqual(xml, load_file(ovf_env_path))
-
+
def test_existing_ovf_same(self):
# waagent/SharedConfig left alone if found ovf-env.xml same as cached
odata = {'UserData': base64.b64encode("SOMEUSERDATA")}
data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
- populate_dir(self.waagent_d,
+ populate_dir(self.waagent_d,
{'ovf-env.xml': data['ovfcontent'],
'otherfile': 'otherfile-content',
'SharedConfig.xml': 'mysharedconfig'})
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 937b88c1..4404668e 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -166,7 +166,7 @@ class TestConfigDriveDataSource(MockerTestCase):
my_mock.replay()
device = cfg_ds.device_name_to_device(name)
self.assertEquals(dev_name, device)
-
+
def test_dev_ec2_map(self):
populate_dir(self.tmp, CFG_DRIVE_FILES_V2)
cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN,
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index af575a10..a65833eb 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -133,6 +133,7 @@ class TestNoCloudDataSource(MockerTestCase):
self.assertFalse(dsrc.vendordata)
self.assertTrue(ret)
+
class TestParseCommandLineData(MockerTestCase):
def test_parse_cmdline_data_valid(self):
diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
index 11d9a264..8f9fa27d 100644
--- a/tests/unittests/test_datasource/test_smartos.py
+++ b/tests/unittests/test_datasource/test_smartos.py
@@ -158,6 +158,11 @@ class TestSmartOSDataSource(helpers.FilesystemMockingTestCase):
def _dmi_data():
return dmi_data
+ def _os_uname():
+ # LP: #1243287. tests assume this runs, but running test on
+ # arm would cause them all to fail.
+ return ('LINUX', 'NODENAME', 'RELEASE', 'VERSION', 'x86_64')
+
if sys_cfg is None:
sys_cfg = {}
@@ -168,6 +173,7 @@ class TestSmartOSDataSource(helpers.FilesystemMockingTestCase):
self.apply_patches([(mod, 'LEGACY_USER_D', self.legacy_user_d)])
self.apply_patches([(mod, 'get_serial', _get_serial)])
self.apply_patches([(mod, 'dmi_data', _dmi_data)])
+ self.apply_patches([(os, 'uname', _os_uname)])
dsrc = mod.DataSourceSmartOS(sys_cfg, distro=None,
paths=self.paths)
return dsrc
diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py
index 996526d3..f2ed4597 100644
--- a/tests/unittests/test_handler/test_handler_growpart.py
+++ b/tests/unittests/test_handler/test_handler_growpart.py
@@ -9,7 +9,6 @@ import errno
import logging
import os
import re
-import unittest
# growpart:
# mode: auto # off, on, auto, 'growpart'
@@ -203,7 +202,7 @@ def simple_device_part_info(devpath):
return x
-class Bunch:
+class Bunch(object):
st_mode = None # fix pylint complaint
def __init__(self, **kwds):
diff --git a/tests/unittests/test_handler/test_handler_seed_random.py b/tests/unittests/test_handler/test_handler_seed_random.py
index 2b21ac02..00c50fc1 100644
--- a/tests/unittests/test_handler/test_handler_seed_random.py
+++ b/tests/unittests/test_handler/test_handler_seed_random.py
@@ -42,10 +42,29 @@ class TestRandomSeed(t_help.TestCase):
def setUp(self):
super(TestRandomSeed, self).setUp()
self._seed_file = tempfile.mktemp()
+ self.unapply = []
+
+ # by default 'which' has nothing in its path
+ self.apply_patches([(util, 'which', self._which)])
+ self.apply_patches([(util, 'subp', self._subp)])
+ self.subp_called = []
+ self.whichdata = {}
def tearDown(self):
+ apply_patches([i for i in reversed(self.unapply)])
util.del_file(self._seed_file)
+ def apply_patches(self, patches):
+ ret = apply_patches(patches)
+ self.unapply += ret
+
+ def _which(self, program):
+ return self.whichdata.get(program)
+
+ def _subp(self, args):
+ self.subp_called.append(tuple(args))
+ return
+
def _compress(self, text):
contents = StringIO()
gz_fh = gzip.GzipFile(mode='wb', fileobj=contents)
@@ -148,3 +167,51 @@ class TestRandomSeed(t_help.TestCase):
cc_seed_random.handle('test', cfg, c, LOG, [])
contents = util.load_file(self._seed_file)
self.assertEquals('tiny-tim-was-here-so-was-josh', contents)
+
+ def test_seed_command_not_provided_pollinate_available(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'pollinate': '/usr/bin/pollinate'}
+ cc_seed_random.handle('test', {}, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('pollinate', '-q')])
+
+ def test_seed_command_not_provided_pollinate_not_available(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {}
+ cc_seed_random.handle('test', {}, c, LOG, [])
+
+ # subp should not have been called as which would say not available
+ self.assertEquals(self.subp_called, list())
+
+ def test_unavailable_seed_command_and_required_raises_error(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {}
+ self.assertRaises(ValueError, cc_seed_random.handle,
+ 'test', {'random_seed': {'command_required': True}}, c, LOG, [])
+
+ def test_seed_command_and_required(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'foo': 'foo'}
+ cfg = {'random_seed': {'command_required': True, 'command': ['foo']}}
+ cc_seed_random.handle('test', cfg, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('foo',)])
+
+ def test_seed_command_non_default(self):
+ c = self._get_cloud('ubuntu', {})
+ self.whichdata = {'foo': 'foo'}
+ cfg = {'random_seed': {'command_required': True, 'command': ['foo']}}
+ cc_seed_random.handle('test', cfg, c, LOG, [])
+
+ self.assertEquals(self.subp_called, [('foo',)])
+
+
+def apply_patches(patches):
+ ret = []
+ for (ref, name, replace) in patches:
+ if replace is None:
+ continue
+ orig = getattr(ref, name)
+ setattr(ref, name, replace)
+ ret.append((ref, name, orig))
+ return ret
diff --git a/tools/make-tarball b/tools/make-tarball
index 27f5f374..b7039150 100755
--- a/tools/make-tarball
+++ b/tools/make-tarball
@@ -27,7 +27,13 @@ else
ARCHIVE_FN="$PWD/cloud-init-$VERSION~bzr$REVNO.tar.gz"
fi
-bzr export --format=tgz --root="cloud-init-$VERSION~bzr$REVNO" \
+export_uncommitted=""
+if [ "${UNCOMMITTED:-0}" != "0" ]; then
+ export_uncommitted="--uncommitted"
+fi
+
+bzr export ${export_uncommitted} \
+ --format=tgz --root="cloud-init-$VERSION~bzr$REVNO" \
"--revision=${REVNO}" "${ARCHIVE_FN}" "$ROOT_DIR"
echo "$ARCHIVE_FN"