summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--cloudinit/config/cc_chef.py2
-rw-r--r--cloudinit/config/cc_disk_setup.py6
-rw-r--r--cloudinit/config/cc_write_files.py4
-rw-r--r--cloudinit/distros/__init__.py7
-rw-r--r--cloudinit/distros/freebsd.py50
-rw-r--r--cloudinit/sources/DataSourceCloudStack.py6
-rw-r--r--cloudinit/sources/DataSourceEc2.py4
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py2
-rw-r--r--cloudinit/sources/__init__.py3
-rw-r--r--config/cloud.cfg-freebsd10
-rw-r--r--systemd/cloud-config.service2
-rw-r--r--systemd/cloud-final.service2
-rw-r--r--tests/unittests/test_handler/test_handler_write_files.py112
-rwxr-xr-xtools/Z99-cloud-locale-test.sh9
15 files changed, 188 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index 6d6da417..9fb6e6d4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -96,6 +96,12 @@
avoid dependency on network metadata service on every boot (LP: #1553815)
- support network configuration in cloud-init --local with support
device naming via systemd.link.
+ - FreeBSD: add support for installing packages, setting password and
+ timezone. Change default user to 'freebsd'. [Ben Arblaster]
+ - locale: list unsupported environment settings in warning (LP: #1558069)
+ - disk_setup: correctly send --force to mkfs on block devices (LP: #1548772)
+ - chef: fix chef install from gems (LP: #1553345)
+ - systemd: do not specify After of obsolete syslog.target (LP: #1536964)
0.7.6:
- open 0.7.6
diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py
index e18c5405..28711a59 100644
--- a/cloudinit/config/cc_chef.py
+++ b/cloudinit/config/cc_chef.py
@@ -285,7 +285,7 @@ def install_chef(cloud, chef_cfg, log):
chef_version = util.get_cfg_option_str(chef_cfg, 'version', None)
ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version',
RUBY_VERSION_DEFAULT)
- install_chef_from_gems(cloud.distro, ruby_version, chef_version)
+ install_chef_from_gems(ruby_version, chef_version, cloud.distro)
# Retain backwards compat, by preferring True instead of False
# when not provided/overriden...
run = util.get_cfg_option_bool(chef_cfg, 'exec', default=True)
diff --git a/cloudinit/config/cc_disk_setup.py b/cloudinit/config/cc_disk_setup.py
index 0ecc2e4c..bbaf9646 100644
--- a/cloudinit/config/cc_disk_setup.py
+++ b/cloudinit/config/cc_disk_setup.py
@@ -847,9 +847,9 @@ def mkfs(fs_cfg):
if label:
fs_cmd.extend(["-L", label])
- # File systems that support the -F flag
- if not fs_cmd and (overwrite or device_type(device) == "disk"):
- fs_cmd.append(lookup_force_flag(fs_type))
+ # File systems that support the -F flag
+ if overwrite or device_type(device) == "disk":
+ fs_cmd.append(lookup_force_flag(fs_type))
# Add the extends FS options
if fs_opts:
diff --git a/cloudinit/config/cc_write_files.py b/cloudinit/config/cc_write_files.py
index 4b03ea91..351cfc8c 100644
--- a/cloudinit/config/cc_write_files.py
+++ b/cloudinit/config/cc_write_files.py
@@ -92,10 +92,10 @@ def decode_perms(perm, default, log):
def extract_contents(contents, extraction_types):
- result = str(contents)
+ result = contents
for t in extraction_types:
if t == 'application/x-gzip':
- result = util.decomp_gzip(result, quiet=False)
+ result = util.decomp_gzip(result, quiet=False, decode=False)
elif t == 'application/base64':
result = base64.b64decode(result)
elif t == UNKNOWN_ENC:
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 71da7ec5..5879dabf 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -371,7 +371,7 @@ class Distro(object):
kwargs['groups'] = ",".join(groups)
else:
groups = groups.split(",")
-
+
primary_group = kwargs.get('primary_group')
if primary_group:
groups.append(primary_group)
@@ -940,7 +940,10 @@ def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone",
# This ensures that the correct tz will be used for the system
if tz_local and tz_file:
# use a symlink if there exists a symlink or tz_local is not present
- if os.path.islink(tz_local) or not os.path.exists(tz_local):
+ islink = os.path.islink(tz_local)
+ if islink or not os.path.exists(tz_local):
+ if islink:
+ util.del_file(tz_local)
os.symlink(tz_file, tz_local)
else:
util.copy(tz_file, tz_local)
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index 72012056..91bf4a4e 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
import six
from six import StringIO
@@ -30,6 +31,8 @@ from cloudinit import util
from cloudinit.distros import net_util
from cloudinit.distros.parsers.resolv_conf import ResolvConf
+from cloudinit.settings import PER_INSTANCE
+
LOG = logging.getLogger(__name__)
@@ -236,9 +239,21 @@ class Distro(distros.Distro):
util.logexc(LOG, "Failed to create user %s", name)
raise e
- # TODO:
def set_passwd(self, user, passwd, hashed=False):
- return False
+ cmd = ['pw', 'usermod', user]
+
+ if hashed:
+ cmd.append('-H')
+ else:
+ cmd.append('-h')
+
+ cmd.append('0')
+
+ try:
+ util.subp(cmd, passwd, logstring="chpasswd for %s" % user)
+ except Exception as e:
+ util.logexc(LOG, "Failed to set password for %s", user)
+ raise e
def lock_passwd(self, name):
try:
@@ -369,13 +384,34 @@ class Distro(distros.Distro):
LOG.warn("Error running %s: %s", cmd, err)
def install_packages(self, pkglist):
- return
+ self.update_package_sources()
+ self.package_command('install', pkgs=pkglist)
+
+ def package_command(self, command, args=None, pkgs=None):
+ if pkgs is None:
+ pkgs = []
+
+ e = os.environ.copy()
+ e['ASSUME_ALWAYS_YES'] = 'YES'
+
+ cmd = ['pkg']
+ if args and isinstance(args, str):
+ cmd.append(args)
+ elif args and isinstance(args, list):
+ cmd.extend(args)
+
+ if command:
+ cmd.append(command)
+
+ pkglist = util.expand_package_list('%s-%s', pkgs)
+ cmd.extend(pkglist)
- def package_command(self, cmd, args=None, pkgs=None):
- return
+ # Allow the output of this to flow outwards (ie not be captured)
+ util.subp(cmd, env=e, capture=False)
def set_timezone(self, tz):
- return
+ distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
def update_package_sources(self):
- return
+ self._runner.run("update-sources", self.package_command,
+ ["update"], freq=PER_INSTANCE)
diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
index 64595020..455a4652 100644
--- a/cloudinit/sources/DataSourceCloudStack.py
+++ b/cloudinit/sources/DataSourceCloudStack.py
@@ -89,8 +89,6 @@ class DataSourceCloudStack(sources.DataSource):
def _get_url_settings(self):
mcfg = self.ds_cfg
- if not mcfg:
- mcfg = {}
max_wait = 120
try:
max_wait = int(mcfg.get("max_wait", max_wait))
@@ -109,10 +107,6 @@ class DataSourceCloudStack(sources.DataSource):
return (max_wait, timeout)
def wait_for_metadata_service(self):
- mcfg = self.ds_cfg
- if not mcfg:
- mcfg = {}
-
(max_wait, timeout) = self._get_url_settings()
urls = [uhelp.combine_url(self.metadata_address,
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 3ef2c6af..6fe2a0bb 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -84,8 +84,6 @@ class DataSourceEc2(sources.DataSource):
def _get_url_settings(self):
mcfg = self.ds_cfg
- if not mcfg:
- mcfg = {}
max_wait = 120
try:
max_wait = int(mcfg.get("max_wait", max_wait))
@@ -102,8 +100,6 @@ class DataSourceEc2(sources.DataSource):
def wait_for_metadata_service(self):
mcfg = self.ds_cfg
- if not mcfg:
- mcfg = {}
(max_wait, timeout) = self._get_url_settings()
if max_wait <= 0:
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index f7f4590b..3af17b10 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -45,8 +45,6 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
self.version = None
self.files = {}
self.ec2_metadata = None
- if not self.ds_cfg:
- self.ds_cfg = {}
def __str__(self):
root = sources.DataSource.__str__(self)
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 82cd3553..6bf2c33b 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -70,6 +70,9 @@ class DataSource(object):
self.ds_cfg = util.get_cfg_by_path(self.sys_cfg,
("datasource", name), {})
+ if not self.ds_cfg:
+ self.ds_cfg = {}
+
if not ud_proc:
self.ud_proc = ud.UserDataProcessor(self.paths)
else:
diff --git a/config/cloud.cfg-freebsd b/config/cloud.cfg-freebsd
index 5ac181ff..be664f5d 100644
--- a/config/cloud.cfg-freebsd
+++ b/config/cloud.cfg-freebsd
@@ -49,10 +49,10 @@ cloud_config_modules:
# - mounts
- ssh-import-id
- locale
-# - set-passwords
-# - package-update-upgrade-install
+ - set-passwords
+ - package-update-upgrade-install
# - landscape
-# - timezone
+ - timezone
# - puppet
# - chef
# - salt-minion
@@ -80,9 +80,9 @@ cloud_final_modules:
system_info:
distro: freebsd
default_user:
- name: beastie
+ name: freebsd
lock_passwd: True
gecos: FreeBSD
groups: [wheel]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
- shell: /bin/sh
+ shell: /bin/tcsh
diff --git a/systemd/cloud-config.service b/systemd/cloud-config.service
index 45d2a63b..3309e08a 100644
--- a/systemd/cloud-config.service
+++ b/systemd/cloud-config.service
@@ -1,6 +1,6 @@
[Unit]
Description=Apply the settings specified in cloud-config
-After=network-online.target cloud-config.target syslog.target
+After=network-online.target cloud-config.target
Wants=network-online.target cloud-config.target
[Service]
diff --git a/systemd/cloud-final.service b/systemd/cloud-final.service
index bfb08d4a..3927710f 100644
--- a/systemd/cloud-final.service
+++ b/systemd/cloud-final.service
@@ -1,6 +1,6 @@
[Unit]
Description=Execute cloud user/final scripts
-After=network-online.target cloud-config.service syslog.target rc-local.service
+After=network-online.target cloud-config.service rc-local.service
Wants=network-online.target cloud-config.service
[Service]
diff --git a/tests/unittests/test_handler/test_handler_write_files.py b/tests/unittests/test_handler/test_handler_write_files.py
new file mode 100644
index 00000000..f1c7f7b4
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_write_files.py
@@ -0,0 +1,112 @@
+from cloudinit import util
+from cloudinit import log as logging
+from cloudinit.config.cc_write_files import write_files
+
+from ..helpers import FilesystemMockingTestCase
+
+import base64
+import gzip
+import shutil
+import six
+import tempfile
+
+LOG = logging.getLogger(__name__)
+
+YAML_TEXT = """
+write_files:
+ - encoding: gzip
+ content: !!binary |
+ H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA=
+ path: /usr/bin/hello
+ permissions: '0755'
+ - content: !!binary |
+ Zm9vYmFyCg==
+ path: /wark
+ permissions: '0755'
+ - content: |
+ hi mom line 1
+ hi mom line 2
+ path: /tmp/message
+"""
+
+YAML_CONTENT_EXPECTED = {
+ '/usr/bin/hello': "#!/bin/sh\necho hello world\n",
+ '/wark': "foobar\n",
+ '/tmp/message': "hi mom line 1\nhi mom line 2\n",
+}
+
+
+class TestWriteFiles(FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestWriteFiles, self).setUp()
+ self.tmp = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.tmp)
+
+ def test_simple(self):
+ self.patchUtils(self.tmp)
+ expected = "hello world\n"
+ filename = "/tmp/my.file"
+ write_files(
+ "test_simple", [{"content": expected, "path": filename}], LOG)
+ self.assertEqual(util.load_file(filename), expected)
+
+ def test_yaml_binary(self):
+ self.patchUtils(self.tmp)
+ data = util.load_yaml(YAML_TEXT)
+ write_files("testname", data['write_files'], LOG)
+ for path, content in YAML_CONTENT_EXPECTED.items():
+ self.assertEqual(util.load_file(path), content)
+
+ def test_all_decodings(self):
+ self.patchUtils(self.tmp)
+
+ # build a 'files' array that has a dictionary of encodings
+ # for 'gz', 'gzip', 'gz+base64' ...
+ data = b"foobzr"
+ utf8_valid = b"foobzr"
+ utf8_invalid = b'ab\xaadef'
+ files = []
+ expected = []
+
+ gz_aliases = ('gz', 'gzip')
+ gz_b64_aliases = ('gz+base64', 'gzip+base64', 'gz+b64', 'gzip+b64')
+ b64_aliases = ('base64', 'b64')
+
+ datum = (("utf8", utf8_valid), ("no-utf8", utf8_invalid))
+ for name, data in datum:
+ gz = (_gzip_bytes(data), gz_aliases)
+ gz_b64 = (base64.b64encode(_gzip_bytes(data)), gz_b64_aliases)
+ b64 = (base64.b64encode(data), b64_aliases)
+ for content, aliases in (gz, gz_b64, b64):
+ for enc in aliases:
+ cur = {'content': content,
+ 'path': '/tmp/file-%s-%s' % (name, enc),
+ 'encoding': enc}
+ files.append(cur)
+ expected.append((cur['path'], data))
+
+ write_files("test_decoding", files, LOG)
+
+ for path, content in expected:
+ self.assertEqual(util.load_file(path, decode=False), content)
+
+ # make sure we actually wrote *some* files.
+ flen_expected = (
+ len(gz_aliases + gz_b64_aliases + b64_aliases) * len(datum))
+ self.assertEqual(len(expected), flen_expected)
+
+
+def _gzip_bytes(data):
+ buf = six.BytesIO()
+ fp = None
+ try:
+ fp = gzip.GzipFile(fileobj=buf, mode="wb")
+ fp.write(data)
+ fp.close()
+ return buf.getvalue()
+ finally:
+ if fp:
+ fp.close()
+
+
+# vi: ts=4 expandtab
diff --git a/tools/Z99-cloud-locale-test.sh b/tools/Z99-cloud-locale-test.sh
index 3c51f22d..8e0469ed 100755
--- a/tools/Z99-cloud-locale-test.sh
+++ b/tools/Z99-cloud-locale-test.sh
@@ -10,7 +10,7 @@
#
locale_warn() {
- local bad_names="" bad_lcs="" key="" val="" var="" vars=""
+ local bad_names="" bad_lcs="" key="" val="" var="" vars="" bad_kv=""
local w1 w2 w3 w4 remain
# if shell is zsh, act like sh only for this function (-L).
@@ -37,15 +37,18 @@ locale_warn() {
[ "${bad}" = "${var%=*}" ] || continue
val=${var#*=}
[ "${bad_lcs#* ${val}}" = "${bad_lcs}" ] &&
- bad_lcs="${bad_lcs} ${val}"
+ bad_lcs="${bad_lcs} ${val}"
+ bad_kv="${bad_kv} $bad=$val"
break
done
done
bad_lcs=${bad_lcs# }
+ bad_kv=${bad_kv# }
[ -n "$bad_lcs" ] || return 0
printf "_____________________________________________________________________\n"
printf "WARNING! Your environment specifies an invalid locale.\n"
+ printf " The unknown environment variables are:\n %s\n" "$bad_kv"
printf " This can affect your user experience significantly, including the\n"
printf " ability to manage packages. You may install the locales by running:\n\n"
@@ -76,7 +79,7 @@ locale_warn() {
printf "\n"
fi
for bad in ${invalid}; do
- printf "WARNING: '${bad}' is an invalid locale\n"
+ printf "WARNING: '${bad}' is an invalid locale\n"
done
printf "To see all available language packs, run:\n"