From fbc01e536cdd023a954703fdc3bbe76df1d9ed1a Mon Sep 17 00:00:00 2001 From: Ben Arblaster Date: Sun, 18 Jan 2015 01:39:35 +0000 Subject: freebsd: add pkg support --- cloudinit/distros/freebsd.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index f1b4a256..10f9e7e0 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os + from StringIO import StringIO import re @@ -29,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__) @@ -367,13 +371,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, cmd, args=None, pkgs=None): - return + 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) + + # 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 def update_package_sources(self): - return + self._runner.run("update-sources", self.package_command, + ["update"], freq=PER_INSTANCE) -- cgit v1.2.3 From 93f5af9f5075a416c65c1d0350c374e16f32f0d5 Mon Sep 17 00:00:00 2001 From: Ben Arblaster Date: Tue, 20 Jan 2015 00:52:04 +0000 Subject: More FreeBSD improvements - Implement set_passwd - Implement set_timezone - Use /bin/tcsh as default user shell (FreeBSD default) - Change default username to freebsd - Enable set-passwords, package-update-upgrade-install and timezone modules - Remove trailing whitespace --- cloudinit/distros/freebsd.py | 20 ++++++++++++++++---- config/cloud.cfg-freebsd | 10 +++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index 10f9e7e0..c59a074b 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -238,9 +238,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: @@ -394,10 +406,10 @@ class Distro(distros.Distro): cmd.extend(pkglist) # Allow the output of this to flow outwards (ie not be captured) - util.subp(cmd, env=e, capture=False) + 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): self._runner.run("update-sources", self.package_command, 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 -- cgit v1.2.3 From 638191cd07ba1afe5ea61ff5268351ab77541139 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 30 Mar 2016 20:16:46 -0400 Subject: fix adding of users without a group revision 1179 regressed adding a user that did not have a 'groups' entry present. This should handle that correctly, making 'add_user' able to take: a.) groups="group1,group2" b.) groups=["group1", "group2"] c.) groups=None d.) no groups parameter LP: #1562918 --- cloudinit/distros/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 418421b9..5563ae43 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -362,15 +362,18 @@ class Distro(object): redact_opts = ['passwd'] + # support kwargs having groups=[list] or groups="g1,g2" groups = kwargs.get('groups') if groups: if isinstance(groups, (list, tuple)): + # kwargs.items loop below wants a comma delimeted string + # that can go right through to the command. kwargs['groups'] = ",".join(groups) else: groups = groups.split(",") - if create_groups: - for group in kwargs.get('groups').split(","): + if create_groups and groups: + for group in groups: if not util.is_group(group): self.create_group(group) LOG.debug("created group %s for user %s", name, group) -- cgit v1.2.3 From 9f8d6be677bfd62181f2e54bfc39a044844a195e Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 12:10:58 -0400 Subject: add ChangeLog entry --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 6d6da417..e06bf60f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -96,6 +96,8 @@ 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] 0.7.6: - open 0.7.6 -- cgit v1.2.3 From 3b69f410718f7d8868b8cbaa04e3969811ee89a9 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 12:31:28 -0400 Subject: DataSource: set ds_cfg to be a dictionary if the Datasource does not have an entry in config, then set it to be a empty dictionary rather than None. Also remove places that did this elsewhere. --- cloudinit/sources/DataSourceCloudStack.py | 6 ------ cloudinit/sources/DataSourceEc2.py | 4 ---- cloudinit/sources/DataSourceOpenStack.py | 2 -- cloudinit/sources/__init__.py | 3 +++ 4 files changed, 3 insertions(+), 12 deletions(-) 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: -- cgit v1.2.3 From e3138283fa5f4c3c2579ee54c8a160fda2c17473 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 14:04:14 -0400 Subject: locale: list unsupported environment settings in warning Now if you log in with unsupported locale, you'll see: The unknown environment variables are: LC_CTYPE=en_GB.utf-8 LC_MESSAGES=en_GB.utf-8 LC_ALL=en_GB.utf-8 LP: #1558069 --- ChangeLog | 1 + tools/Z99-cloud-locale-test.sh | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index e06bf60f..805df93b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -98,6 +98,7 @@ 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) 0.7.6: - open 0.7.6 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" -- cgit v1.2.3 From 3de63003f63801a046b2526b264caa92371d8e9f Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 15:02:00 -0400 Subject: disk_setup: correctly send --force to mkfs on block devices Send the --force flag to mkfs or other filesystems when target is a block device. This fixes a general code flow issue where we were setting the --force flag. LP: #1548772 --- ChangeLog | 1 + cloudinit/config/cc_disk_setup.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 805df93b..df3f5f19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -99,6 +99,7 @@ - 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) 0.7.6: - open 0.7.6 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: -- cgit v1.2.3 From cc77a528367b295b8033f73610143713c18a1bb9 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 15:17:28 -0400 Subject: chef: fix chef installation from gems Installation from gems was previously always broken. This fixes the order or parameters calling install_chef_from_gems. LP: #1553345 --- ChangeLog | 1 + cloudinit/config/cc_chef.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index df3f5f19..3fade4db 100644 --- a/ChangeLog +++ b/ChangeLog @@ -100,6 +100,7 @@ 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) 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) -- cgit v1.2.3 From 35802e8866be53016779f4f1eae9f026ccd18a61 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Mon, 4 Apr 2016 16:07:07 -0400 Subject: systemd: do not specify After of obsolete syslog.target (LP: #1536964) syslog.target is obsolete in debian, this fixes a lintian warning. LP: #1536964 --- ChangeLog | 1 + systemd/cloud-config.service | 2 +- systemd/cloud-final.service | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3fade4db..9fb6e6d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -101,6 +101,7 @@ - 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/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] -- cgit v1.2.3 From 6a660b490ee6384055d2afb07f8cac1628168ba2 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 5 Apr 2016 20:43:05 -0400 Subject: write_files: fix decompression of content When provided with gzipped data, an exception would be raised because of a conversion to string. This fixes the issue and adds a test for write_files. LP: #1565638 --- cloudinit/config/cc_write_files.py | 4 +- cloudinit/distros/__init__.py | 5 +- .../test_handler/test_handler_write_files.py | 112 +++++++++++++++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 tests/unittests/test_handler/test_handler_write_files.py 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 418421b9..12983c0a 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -933,7 +933,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/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 -- cgit v1.2.3 From 2c95e4cf2a61d13de72833c79d04648ba1687ef9 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 6 Apr 2016 11:03:55 -0400 Subject: support adding the primary group also --- cloudinit/distros/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 5563ae43..71da7ec5 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -371,6 +371,10 @@ class Distro(object): kwargs['groups'] = ",".join(groups) else: groups = groups.split(",") + + primary_group = kwargs.get('primary_group') + if primary_group: + groups.append(primary_group) if create_groups and groups: for group in groups: -- cgit v1.2.3