summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--cloudinit/config/cc_snappy.py4
-rw-r--r--cloudinit/distros/__init__.py15
-rw-r--r--cloudinit/handlers/__init__.py11
-rw-r--r--cloudinit/user_data.py9
-rw-r--r--cloudinit/util.py8
-rw-r--r--systemd/cloud-config.service4
-rw-r--r--systemd/cloud-final.service4
-rw-r--r--systemd/cloud-init.service4
-rw-r--r--tests/unittests/test_data.py32
10 files changed, 72 insertions, 23 deletions
diff --git a/ChangeLog b/ChangeLog
index 6651b8eb..c5ad7c60 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,10 @@
- SmartOS: use v2 metadata service (LP: #1436417) [Daniel Watkins]
- NoCloud: fix local datasource claiming found without explicit dsmode
- Snappy: add support for installing snappy packages and configuring.
+ - systemd: use network-online instead of network.target (LP: #1440180)
+ [Steve Langasek]
+ - Add functionality to fixate the uid of a newly added user.
+ - Don't overwrite the hostname if the user has changed it after we set it.
0.7.6:
- open 0.7.6
- Enable vendordata on CloudSigma datasource (LP: #1303986)
diff --git a/cloudinit/config/cc_snappy.py b/cloudinit/config/cc_snappy.py
index 6a7ae09b..bfe76558 100644
--- a/cloudinit/config/cc_snappy.py
+++ b/cloudinit/config/cc_snappy.py
@@ -72,7 +72,7 @@ def parse_filename(fname):
name = fname_noext.partition("_")[0]
shortname = name.partition(".")[0]
return(name, shortname, fname_noext)
-
+
def get_fs_package_ops(fspath):
if not fspath:
@@ -98,7 +98,7 @@ def makeop(op, name, config=None, path=None, cfgfile=None):
def get_package_config(configs, name):
# load the package's config from the configs dict.
- # prefer full-name entry (config-example.canonical)
+ # prefer full-name entry (config-example.canonical)
# over short name entry (config-example)
if name in configs:
return configs[name]
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index ab874b45..05721922 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -208,6 +208,15 @@ class Distro(object):
and sys_hostname != hostname):
update_files.append(sys_fn)
+ # If something else has changed the hostname after we set it
+ # initially, we should not overwrite those changes (we should
+ # only be setting the hostname once per instance)
+ if (sys_hostname and prev_hostname and
+ sys_hostname != prev_hostname):
+ LOG.info("%s differs from %s, assuming user maintained hostname.",
+ prev_hostname_fn, sys_fn)
+ return
+
# Remove duplicates (incase the previous config filename)
# is the same as the system config filename, don't bother
# doing it twice
@@ -222,11 +231,6 @@ class Distro(object):
util.logexc(LOG, "Failed to write hostname %s to %s", hostname,
fn)
- if (sys_hostname and prev_hostname and
- sys_hostname != prev_hostname):
- LOG.debug("%s differs from %s, assuming user maintained hostname.",
- prev_hostname_fn, sys_fn)
-
# If the system hostname file name was provided set the
# non-fqdn as the transient hostname.
if sys_fn in update_files:
@@ -318,6 +322,7 @@ class Distro(object):
"gecos": '--comment',
"homedir": '--home',
"primary_group": '--gid',
+ "uid": '--uid',
"groups": '--groups',
"passwd": '--password',
"shell": '--shell',
diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py
index d62fcd19..53d5604a 100644
--- a/cloudinit/handlers/__init__.py
+++ b/cloudinit/handlers/__init__.py
@@ -170,12 +170,12 @@ def _extract_first_or_bytes(blob, size):
start = blob.split("\n", 1)[0]
else:
# We want to avoid decoding the whole blob (it might be huge)
- # By taking 4*size bytes we have a guarantee to decode size utf8 chars
- start = blob[:4*size].decode(errors='ignore').split("\n", 1)[0]
+ # By taking 4*size bytes we guarantee to decode size utf8 chars
+ start = blob[:4 * size].decode(errors='ignore').split("\n", 1)[0]
if len(start) >= size:
start = start[:size]
except UnicodeDecodeError:
- # Bytes array doesn't contain a text object -- return chunk of raw bytes
+ # Bytes array doesn't contain text so return chunk of raw bytes
start = blob[0:size]
return start
@@ -263,7 +263,10 @@ def fixup_handler(mod, def_freq=PER_INSTANCE):
def type_from_starts_with(payload, default=None):
- payload_lc = payload.lower()
+ try:
+ payload_lc = util.decode_binary(payload).lower()
+ except UnicodeDecodeError:
+ return default
payload_lc = payload_lc.lstrip()
for text in INCLUSION_SRCH:
if payload_lc.startswith(text):
diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
index eb3c7336..f7c5787c 100644
--- a/cloudinit/user_data.py
+++ b/cloudinit/user_data.py
@@ -49,6 +49,7 @@ 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"
+ARCHIVE_UNDEF_BINARY_TYPE = "application/octet-stream"
# This seems to hit most of the gzip possible content types.
DECOMP_TYPES = [
@@ -265,11 +266,15 @@ class UserDataProcessor(object):
content = ent.get('content', '')
mtype = ent.get('type')
if not mtype:
- mtype = handlers.type_from_starts_with(content,
- ARCHIVE_UNDEF_TYPE)
+ default = ARCHIVE_UNDEF_TYPE
+ if isinstance(content, six.binary_type):
+ default = ARCHIVE_UNDEF_BINARY_TYPE
+ mtype = handlers.type_from_starts_with(content, default)
maintype, subtype = mtype.split('/', 1)
if maintype == "text":
+ if isinstance(content, six.binary_type):
+ content = content.decode()
msg = MIMEText(content, _subtype=subtype)
else:
msg = MIMEBase(maintype, subtype)
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 971c1c2d..cae57770 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -121,8 +121,12 @@ def fully_decoded_payload(part):
if (six.PY3 and
part.get_content_maintype() == 'text' and
isinstance(cte_payload, bytes)):
- charset = part.get_charset() or 'utf-8'
- return cte_payload.decode(charset, errors='surrogateescape')
+ charset = part.get_charset()
+ if charset and charset.input_codec:
+ encoding = charset.input_codec
+ else:
+ encoding = 'utf-8'
+ return cte_payload.decode(encoding, errors='surrogateescape')
return cte_payload
diff --git a/systemd/cloud-config.service b/systemd/cloud-config.service
index ac25c776..f9f1996e 100644
--- a/systemd/cloud-config.service
+++ b/systemd/cloud-config.service
@@ -1,7 +1,7 @@
[Unit]
Description=Apply the settings specified in cloud-config
-After=network.target cloud-config.target syslog.target
-Wants=network.target cloud-config.target
+After=network-online.target cloud-config.target syslog.target
+Wants=network-online.target cloud-config.target
[Service]
Type=oneshot
diff --git a/systemd/cloud-final.service b/systemd/cloud-final.service
index bbcdf30b..c023ad94 100644
--- a/systemd/cloud-final.service
+++ b/systemd/cloud-final.service
@@ -1,7 +1,7 @@
[Unit]
Description=Execute cloud user/final scripts
-After=network.target cloud-config.service syslog.target rc-local.service
-Wants=network.target cloud-config.service
+After=network-online.target cloud-config.service syslog.target rc-local.service
+Wants=network-online.target cloud-config.service
[Service]
Type=oneshot
diff --git a/systemd/cloud-init.service b/systemd/cloud-init.service
index 398b90ea..48920283 100644
--- a/systemd/cloud-init.service
+++ b/systemd/cloud-init.service
@@ -1,8 +1,8 @@
[Unit]
Description=Initial cloud-init job (metadata service crawler)
-After=local-fs.target network.target cloud-init-local.service
+After=local-fs.target network-online.target cloud-init-local.service
Before=sshd.service sshd-keygen.service systemd-user-sessions.service
-Requires=network.target
+Requires=network-online.target
Wants=local-fs.target cloud-init-local.service sshd.service sshd-keygen.service
[Service]
diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py
index 4f24e2dd..1b15dafa 100644
--- a/tests/unittests/test_data.py
+++ b/tests/unittests/test_data.py
@@ -494,10 +494,10 @@ c: 4
])
def test_mime_application_octet_stream(self):
- """Mime message of type application/octet-stream is ignored but shows warning."""
+ """Mime type application/octet-stream is ignored but shows warning."""
ci = stages.Init()
message = MIMEBase("application", "octet-stream")
- message.set_payload(b'\xbf\xe6\xb2\xc3\xd3\xba\x13\xa4\xd8\xa1\xcc\xbf')
+ message.set_payload(b'\xbf\xe6\xb2\xc3\xd3\xba\x13\xa4\xd8\xa1\xcc')
encoders.encode_base64(message)
ci.datasource = FakeDataSource(message.as_string().encode())
@@ -511,6 +511,34 @@ c: 4
mockobj.assert_called_once_with(
ci.paths.get_ipath("cloud_config"), "", 0o600)
+
+ def test_cloud_config_archive(self):
+ non_decodable = b'\x11\xc9\xb4gTH\xee\x12'
+ data = [{'content': '#cloud-config\npassword: gocubs\n'},
+ {'content': '#cloud-config\nlocale: chicago\n'},
+ {'content': non_decodable}]
+ message = b'#cloud-config-archive\n' + util.yaml_dumps(data).encode()
+
+ ci = stages.Init()
+ ci.datasource = FakeDataSource(message)
+
+ fs = {}
+
+ def fsstore(filename, content, mode=0o0644, omode="wb"):
+ fs[filename] = content
+
+ # consuming the user-data provided should write 'cloud_config' file
+ # which will have our yaml in it.
+ with mock.patch('cloudinit.util.write_file') as mockobj:
+ mockobj.side_effect = fsstore
+ ci.fetch()
+ ci.consume_data()
+
+ cfg = util.load_yaml(fs[ci.paths.get_ipath("cloud_config")])
+ self.assertEqual(cfg.get('password'), 'gocubs')
+ self.assertEqual(cfg.get('locale'), 'chicago')
+
+
class TestUDProcess(helpers.ResourceUsingTestCase):
def test_bytes_in_userdata(self):