diff --git a/subiquity/common/resources.py b/subiquity/common/resources.py index 3deb2284..96e639a4 100644 --- a/subiquity/common/resources.py +++ b/subiquity/common/resources.py @@ -33,7 +33,8 @@ def get_users_and_groups(chroot_prefix=[]): users_and_groups_path = resource_path('users-and-groups') groups = ['admin'] if os.path.exists(users_and_groups_path): - groups = open(users_and_groups_path).read().split() + with open(users_and_groups_path) as f: + groups = f.read().split() groups.append('sudo') command = chroot_prefix + ['getent', 'group'] diff --git a/subiquity/models/subiquity.py b/subiquity/models/subiquity.py index 9a61689d..ac06e77b 100644 --- a/subiquity/models/subiquity.py +++ b/subiquity/models/subiquity.py @@ -14,7 +14,6 @@ # along with this program. If not, see . import asyncio -import copy from collections import OrderedDict import functools import logging @@ -49,6 +48,27 @@ from .updates import UpdatesModel log = logging.getLogger('subiquity.models.subiquity') +def merge_cloud_init_config(target, source): + # type: (dict, dict) -> None + """ + Merges the ``source`` dictionary into the ``target`` dictionary: + + * If both items are dictionaries, they are merged recursively. + * If both items are lists, contents of the source list are appended + to the target list. + * Otherwise, the source item overwrites the target item. + + Based on the ``curtin.config.merge_config`` function. + """ + for k, v in source.items(): + if isinstance(v, dict) and isinstance(target.get(k, None), dict): + merge_cloud_init_config(target[k], v) + elif isinstance(v, list) and isinstance(target.get(k, None), list): + target[k].extend(v) + else: + target[k] = v + + def setup_yaml(): """ http://stackoverflow.com/a/8661021 """ represent_dict_order = ( @@ -249,9 +269,8 @@ class SubiquityModel: model = getattr(self, model_name) if getattr(model, 'make_cloudconfig', None): merge_config(config, model.make_cloudconfig()) - userdata = copy.deepcopy(self.userdata) - merge_config(userdata, config) - return userdata + merge_cloud_init_config(config, self.userdata) + return config async def target_packages(self): packages = list(self.packages) diff --git a/subiquity/models/tests/test_subiquity.py b/subiquity/models/tests/test_subiquity.py index 4fae23e4..083ec118 100644 --- a/subiquity/models/tests/test_subiquity.py +++ b/subiquity/models/tests/test_subiquity.py @@ -20,6 +20,7 @@ import yaml from subiquitycore.pubsub import MessageHub from subiquitycore.tests.util import run_coro +from subiquity.common.types import IdentityData from subiquity.models.subiquity import ModelNames, SubiquityModel from subiquity.server.server import ( INSTALL_MODEL_NAMES, @@ -189,3 +190,26 @@ class TestSubiquityModel(unittest.TestCase): self.assertEqual( get_mirror(config["apt"], "primary", get_architecture()), mirror_val) + + def test_cloud_init_user_list_merge(self): + main_user = IdentityData( + username='mainuser', + crypted_password='dummy_value', + hostname='somehost') + secondary_user = {'name': 'user2'} + + with self.subTest('Main user + secondary user'): + model = self.make_model() + model.identity.add_user(main_user) + model.userdata = {'users': [secondary_user]} + cloud_init_config = model._cloud_init_config() + self.assertEqual(len(cloud_init_config['users']), 2) + self.assertEqual(cloud_init_config['users'][0]['name'], 'mainuser') + self.assertEqual(cloud_init_config['users'][1]['name'], 'user2') + + with self.subTest('Secondary user only'): + model = self.make_model() + model.userdata = {'users': [secondary_user]} + cloud_init_config = model._cloud_init_config() + self.assertEqual(len(cloud_init_config['users']), 1) + self.assertEqual(cloud_init_config['users'][0]['name'], 'user2')