~ubuntu-branches/ubuntu/quantal/cloud-init/quantal

« back to all changes in this revision

Viewing changes to cloudinit/distros/__init__.py

  • Committer: Package Import Robot
  • Author(s): Scott Moser
  • Date: 2012-09-30 14:29:04 UTC
  • Revision ID: package-import@ubuntu.com-20120930142904-nq8fkve62i0xytqz
* add CloudStack to DataSources listed by dpkg-reconfigure (LP: #1002155)
* New upstream snapshot.
  * 0440 permissions on /etc/sudoers.d files rather than 0644
  * get host ssh keys to the console (LP: #1055688)
  * MAAS DataSource adjust timestamp in oauth header to one based on the
    timestamp in the response of a 403.  This accounts for a bad local
    clock. (LP: #978127)
  * re-start the salt daemon rather than start to ensure config changes
    are taken.
  * allow for python unicode types in yaml that is loaded.
  * cleanup in how config modules get at users and groups.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
from StringIO import StringIO
25
25
 
26
26
import abc
27
 
import grp
 
27
import itertools
28
28
import os
29
 
import pwd
30
29
import re
31
30
 
32
31
from cloudinit import importer
34
33
from cloudinit import ssh_util
35
34
from cloudinit import util
36
35
 
37
 
# TODO(harlowja): Make this via config??
38
 
IFACE_ACTIONS = {
39
 
    'up': ['ifup', '--all'],
40
 
    'down': ['ifdown', '--all'],
41
 
}
42
 
 
43
36
LOG = logging.getLogger(__name__)
44
37
 
45
38
 
54
47
        self._cfg = cfg
55
48
        self.name = name
56
49
 
57
 
    def add_default_user(self):
58
 
        # Adds the distro user using the rules:
59
 
        #  - Password is same as username but is locked
60
 
        #  - nopasswd sudo access
61
 
 
62
 
        user = self.get_default_user()
63
 
        groups = self.get_default_user_groups()
64
 
 
65
 
        if not user:
66
 
            raise NotImplementedError("No Default user")
67
 
 
68
 
        user_dict = {
69
 
                    'name': user,
70
 
                    'plain_text_passwd': user,
71
 
                    'home': "/home/%s" % user,
72
 
                    'shell': "/bin/bash",
73
 
                    'lock_passwd': True,
74
 
                    'gecos': "%s%s" % (user[0:1].upper(), user[1:]),
75
 
                    'sudo': "ALL=(ALL) NOPASSWD:ALL",
76
 
                    }
77
 
 
78
 
        if groups:
79
 
            user_dict['groups'] = groups
80
 
 
81
 
        self.create_user(**user_dict)
82
 
 
83
 
        LOG.info("Added default '%s' user with passwordless sudo", user)
84
 
 
85
50
    @abc.abstractmethod
86
51
    def install_packages(self, pkglist):
87
52
        raise NotImplementedError()
118
83
        return arch
119
84
 
120
85
    def _get_arch_package_mirror_info(self, arch=None):
121
 
        mirror_info = self.get_option("package_mirrors", None)
 
86
        mirror_info = self.get_option("package_mirrors", [])
122
87
        if arch == None:
123
88
            arch = self.get_primary_arch()
124
89
        return _get_arch_package_mirror_info(mirror_info, arch)
128
93
        # this resolves the package_mirrors config option
129
94
        # down to a single dict of {mirror_name: mirror_url}
130
95
        arch_info = self._get_arch_package_mirror_info(arch)
131
 
 
132
96
        return _get_package_mirror_info(availability_zone=availability_zone,
133
97
                                        mirror_info=arch_info)
134
98
 
135
99
    def apply_network(self, settings, bring_up=True):
136
100
        # Write it out
137
 
        self._write_network(settings)
 
101
        dev_names = self._write_network(settings)
138
102
        # Now try to bring them up
139
103
        if bring_up:
140
 
            return self._interface_action('up')
 
104
            return self._bring_up_interfaces(dev_names)
141
105
        return False
142
106
 
143
107
    @abc.abstractmethod
189
153
            util.write_file(self._paths.join(False, "/etc/hosts"),
190
154
                            contents, mode=0644)
191
155
 
192
 
    def _interface_action(self, action):
193
 
        if action not in IFACE_ACTIONS:
194
 
            raise NotImplementedError("Unknown interface action %s" % (action))
195
 
        cmd = IFACE_ACTIONS[action]
 
156
    def _bring_up_interface(self, device_name):
 
157
        cmd = ['ifup', device_name]
 
158
        LOG.debug("Attempting to run bring up interface %s using command %s",
 
159
                   device_name, cmd)
196
160
        try:
197
 
            LOG.debug("Attempting to run %s interface action using command %s",
198
 
                      action, cmd)
199
161
            (_out, err) = util.subp(cmd)
200
162
            if len(err):
201
163
                LOG.warn("Running %s resulted in stderr output: %s", cmd, err)
204
166
            util.logexc(LOG, "Running interface command %s failed", cmd)
205
167
            return False
206
168
 
207
 
    def isuser(self, name):
208
 
        try:
209
 
            if pwd.getpwnam(name):
210
 
                return True
211
 
        except KeyError:
212
 
            return False
 
169
    def _bring_up_interfaces(self, device_names):
 
170
        am_failed = 0
 
171
        for d in device_names:
 
172
            if not self._bring_up_interface(d):
 
173
                am_failed += 1
 
174
        if am_failed == 0:
 
175
            return True
 
176
        return False
213
177
 
214
178
    def get_default_user(self):
215
 
        return self.default_user
216
 
 
217
 
    def get_default_user_groups(self):
218
 
        return self.default_user_groups
 
179
        if not self.default_user:
 
180
            return None
 
181
        user_cfg = {
 
182
            'name': self.default_user,
 
183
            'plain_text_passwd': self.default_user,
 
184
            'home': "/home/%s" % (self.default_user),
 
185
            'shell': "/bin/bash",
 
186
            'lock_passwd': True,
 
187
            'gecos': "%s" % (self.default_user.title()),
 
188
            'sudo': "ALL=(ALL) NOPASSWD:ALL",
 
189
        }
 
190
        def_groups = self.default_user_groups
 
191
        if not def_groups:
 
192
            def_groups = []
 
193
        user_cfg['groups'] = util.uniq_merge_sorted(def_groups)
 
194
        return user_cfg
219
195
 
220
196
    def create_user(self, name, **kwargs):
221
197
        """
272
248
            adduser_cmd.append('-m')
273
249
 
274
250
        # Create the user
275
 
        if self.isuser(name):
 
251
        if util.is_user(name):
276
252
            LOG.warn("User %s already exists, skipping." % name)
277
253
        else:
278
254
            LOG.debug("Creating name %s" % name)
339
315
            content += "\n"
340
316
 
341
317
        if not os.path.exists(sudo_file):
342
 
            util.write_file(sudo_file, content, 0644)
 
318
            util.write_file(sudo_file, content, 0440)
343
319
 
344
320
        else:
345
321
            try:
349
325
                util.logexc(LOG, "Failed to write %s" % sudo_file, e)
350
326
                raise e
351
327
 
352
 
    def isgroup(self, name):
353
 
        try:
354
 
            if grp.getgrnam(name):
355
 
                return True
356
 
        except:
357
 
            return False
358
 
 
359
328
    def create_group(self, name, members):
360
329
        group_add_cmd = ['groupadd', name]
361
330
 
362
331
        # Check if group exists, and then add it doesn't
363
 
        if self.isgroup(name):
 
332
        if util.is_group(name):
364
333
            LOG.warn("Skipping creation of existing group '%s'" % name)
365
334
        else:
366
335
            try:
372
341
        # Add members to the group, if so defined
373
342
        if len(members) > 0:
374
343
            for member in members:
375
 
                if not self.isuser(member):
 
344
                if not util.is_user(member):
376
345
                    LOG.warn("Unable to add group member '%s' to group '%s'"
377
346
                            "; user does not exist." % (member, name))
378
347
                    continue
386
355
    # given a arch specific 'mirror_info' entry (from package_mirrors)
387
356
    # search through the 'search' entries, and fallback appropriately
388
357
    # return a dict with only {name: mirror} entries.
 
358
    if not mirror_info:
 
359
        mirror_info = {}
389
360
 
390
361
    ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" %
391
362
        "north|northeast|east|southeast|south|southwest|west|northwest")
430
401
    return default
431
402
 
432
403
 
 
404
# Normalizes a input group configuration
 
405
# which can be a comma seperated list of
 
406
# group names, or a list of group names
 
407
# or a python dictionary of group names
 
408
# to a list of members of that group.
 
409
#
 
410
# The output is a dictionary of group
 
411
# names => members of that group which
 
412
# is the standard form used in the rest
 
413
# of cloud-init
 
414
def _normalize_groups(grp_cfg):
 
415
    if isinstance(grp_cfg, (str, basestring, list)):
 
416
        c_grp_cfg = {}
 
417
        for i in util.uniq_merge(grp_cfg):
 
418
            c_grp_cfg[i] = []
 
419
        grp_cfg = c_grp_cfg
 
420
 
 
421
    groups = {}
 
422
    if isinstance(grp_cfg, (dict)):
 
423
        for (grp_name, grp_members) in grp_cfg.items():
 
424
            groups[grp_name] = util.uniq_merge_sorted(grp_members)
 
425
    else:
 
426
        raise TypeError(("Group config must be list, dict "
 
427
                         " or string types only and not %s") %
 
428
                        util.obj_name(grp_cfg))
 
429
    return groups
 
430
 
 
431
 
 
432
# Normalizes a input group configuration
 
433
# which can be a comma seperated list of
 
434
# user names, or a list of string user names
 
435
# or a list of dictionaries with components
 
436
# that define the user config + 'name' (if
 
437
# a 'name' field does not exist then the
 
438
# default user is assumed to 'own' that
 
439
# configuration.
 
440
#
 
441
# The output is a dictionary of user
 
442
# names => user config which is the standard 
 
443
# form used in the rest of cloud-init. Note
 
444
# the default user will have a special config
 
445
# entry 'default' which will be marked as true
 
446
# all other users will be marked as false.
 
447
def _normalize_users(u_cfg, def_user_cfg=None):
 
448
    if isinstance(u_cfg, (dict)):
 
449
        ad_ucfg = []
 
450
        for (k, v) in u_cfg.items():
 
451
            if isinstance(v, (bool, int, basestring, str, float)):
 
452
                if util.is_true(v):
 
453
                    ad_ucfg.append(str(k))
 
454
            elif isinstance(v, (dict)):
 
455
                v['name'] = k
 
456
                ad_ucfg.append(v)
 
457
            else:
 
458
                raise TypeError(("Unmappable user value type %s"
 
459
                                 " for key %s") % (util.obj_name(v), k))
 
460
        u_cfg = ad_ucfg
 
461
    elif isinstance(u_cfg, (str, basestring)):
 
462
        u_cfg = util.uniq_merge_sorted(u_cfg)
 
463
 
 
464
    users = {}
 
465
    for user_config in u_cfg:
 
466
        if isinstance(user_config, (str, basestring, list)):
 
467
            for u in util.uniq_merge(user_config):
 
468
                if u and u not in users:
 
469
                    users[u] = {}
 
470
        elif isinstance(user_config, (dict)):
 
471
            if 'name' in user_config:
 
472
                n = user_config.pop('name')
 
473
                prev_config = users.get(n) or {}
 
474
                users[n] = util.mergemanydict([prev_config,
 
475
                                               user_config])
 
476
            else:
 
477
                # Assume the default user then
 
478
                prev_config = users.get('default') or {}
 
479
                users['default'] = util.mergemanydict([prev_config,
 
480
                                                       user_config])
 
481
        else:
 
482
            raise TypeError(("User config must be dictionary/list "
 
483
                             " or string types only and not %s") %
 
484
                            util.obj_name(user_config))
 
485
 
 
486
    # Ensure user options are in the right python friendly format
 
487
    if users:
 
488
        c_users = {}
 
489
        for (uname, uconfig) in users.items():
 
490
            c_uconfig = {}
 
491
            for (k, v) in uconfig.items():
 
492
                k = k.replace('-', '_').strip()
 
493
                if k:
 
494
                    c_uconfig[k] = v
 
495
            c_users[uname] = c_uconfig
 
496
        users = c_users
 
497
 
 
498
    # Fixup the default user into the real
 
499
    # default user name and replace it...
 
500
    def_user = None
 
501
    if users and 'default' in users:
 
502
        def_config = users.pop('default')
 
503
        if def_user_cfg:
 
504
            # Pickup what the default 'real name' is
 
505
            # and any groups that are provided by the
 
506
            # default config
 
507
            def_user = def_user_cfg.pop('name')
 
508
            def_groups = def_user_cfg.pop('groups', [])
 
509
            # Pickup any config + groups for that user name
 
510
            # that we may have previously extracted
 
511
            parsed_config = users.pop(def_user, {})
 
512
            parsed_groups = parsed_config.get('groups', [])
 
513
            # Now merge our extracted groups with
 
514
            # anything the default config provided
 
515
            users_groups = util.uniq_merge_sorted(parsed_groups, def_groups)
 
516
            parsed_config['groups'] = ",".join(users_groups)
 
517
            # The real config for the default user is the
 
518
            # combination of the default user config provided
 
519
            # by the distro, the default user config provided
 
520
            # by the above merging for the user 'default' and
 
521
            # then the parsed config from the user's 'real name'
 
522
            # which does not have to be 'default' (but could be)
 
523
            users[def_user] = util.mergemanydict([def_user_cfg,
 
524
                                                  def_config,
 
525
                                                  parsed_config])
 
526
 
 
527
    # Ensure that only the default user that we
 
528
    # found (if any) is actually marked as being
 
529
    # the default user
 
530
    if users:
 
531
        for (uname, uconfig) in users.items():
 
532
            if def_user and uname == def_user:
 
533
                uconfig['default'] = True
 
534
            else:
 
535
                uconfig['default'] = False
 
536
 
 
537
    return users
 
538
 
 
539
 
 
540
# Normalizes a set of user/users and group
 
541
# dictionary configuration into a useable
 
542
# format that the rest of cloud-init can
 
543
# understand using the default user
 
544
# provided by the input distrobution (if any)
 
545
# to allow for mapping of the 'default' user.
 
546
#
 
547
# Output is a dictionary of group names -> [member] (list)
 
548
# and a dictionary of user names -> user configuration (dict)
 
549
#
 
550
# If 'user' exists it will override
 
551
# the 'users'[0] entry (if a list) otherwise it will
 
552
# just become an entry in the returned dictionary (no override)
 
553
def normalize_users_groups(cfg, distro):
 
554
    if not cfg:
 
555
        cfg = {}
 
556
    users = {}
 
557
    groups = {}
 
558
    if 'groups' in cfg:
 
559
        groups = _normalize_groups(cfg['groups'])
 
560
 
 
561
    # Handle the previous style of doing this...
 
562
    old_user = None
 
563
    if 'user' in cfg and cfg['user']:
 
564
        old_user = str(cfg['user'])
 
565
        if not 'users' in cfg:
 
566
            cfg['users'] = old_user
 
567
            old_user = None
 
568
    if 'users' in cfg:
 
569
        default_user_config = None
 
570
        try:
 
571
            default_user_config = distro.get_default_user()
 
572
        except NotImplementedError:
 
573
            LOG.warn(("Distro has not implemented default user "
 
574
                      "access. No default user will be normalized."))
 
575
        base_users = cfg['users']
 
576
        if old_user:
 
577
            if isinstance(base_users, (list)):
 
578
                if len(base_users):
 
579
                    # The old user replaces user[0]
 
580
                    base_users[0] = {'name': old_user}
 
581
                else:
 
582
                    # Just add it on at the end...
 
583
                    base_users.append({'name': old_user})
 
584
            elif isinstance(base_users, (dict)):
 
585
                if old_user not in base_users:
 
586
                    base_users[old_user] = True
 
587
            elif isinstance(base_users, (str, basestring)):
 
588
                # Just append it on to be re-parsed later
 
589
                base_users += ",%s" % (old_user)
 
590
        users = _normalize_users(base_users, default_user_config)
 
591
    return (users, groups)
 
592
 
 
593
 
 
594
# Given a user dictionary config it will
 
595
# extract the default user name and user config
 
596
# from that list and return that tuple or
 
597
# return (None, None) if no default user is
 
598
# found in the given input
 
599
def extract_default(users, default_name=None, default_config=None):
 
600
    if not users:
 
601
        users = {}
 
602
 
 
603
    def safe_find(entry):
 
604
        config = entry[1]
 
605
        if not config or 'default' not in config:
 
606
            return False
 
607
        else:
 
608
            return config['default']
 
609
 
 
610
    tmp_users = users.items()
 
611
    tmp_users = dict(itertools.ifilter(safe_find, tmp_users))
 
612
    if not tmp_users:
 
613
        return (default_name, default_config)
 
614
    else:
 
615
        name = tmp_users.keys()[0]
 
616
        config = tmp_users[name]
 
617
        config.pop('default', None)
 
618
        return (name, config)
 
619
 
 
620
 
433
621
def fetch(name):
434
622
    locs = importer.find_module(name,
435
623
                                ['', __name__],