| # -*- coding: utf-8 -*- |
| ''' |
| Virtual machine image management tools |
| ''' |
| |
| from __future__ import absolute_import |
| |
| # Import python libs |
| import os |
| import shutil |
| import logging |
| import tempfile |
| |
| # Import salt libs |
| import salt.crypt |
| import salt.utils |
| import salt.utils.cloud |
| import salt.config |
| import salt.syspaths |
| import uuid |
| |
| |
| # Set up logging |
| log = logging.getLogger(__name__) |
| |
| # Don't shadow built-in's. |
| __func_alias__ = { |
| 'apply_': 'apply' |
| } |
| |
| |
| def _file_or_content(file_): |
| if os.path.exists(file_): |
| with salt.utils.fopen(file_) as fic: |
| return fic.read() |
| return file_ |
| |
| |
| def prep_bootstrap(mpt): |
| ''' |
| Update and get the random script to a random place |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt '*' seed.prep_bootstrap /tmp |
| |
| ''' |
| # Verify that the boostrap script is downloaded |
| bs_ = __salt__['config.gather_bootstrap_script']() |
| fpd_ = os.path.join(mpt, 'tmp', "{0}".format( |
| uuid.uuid4())) |
| if not os.path.exists(fpd_): |
| os.makedirs(fpd_) |
| os.chmod(fpd_, 0o700) |
| fp_ = os.path.join(fpd_, os.path.basename(bs_)) |
| # Copy script into tmp |
| shutil.copy(bs_, fp_) |
| tmppath = fpd_.replace(mpt, '') |
| return fp_, tmppath |
| |
| |
| def _mount(path, ftype, root=None): |
| mpt = None |
| if ftype == 'block': |
| mpt = tempfile.mkdtemp() |
| if not __salt__['mount.mount'](mpt, path): |
| os.rmdir(mpt) |
| return None |
| elif ftype == 'dir': |
| return path |
| elif ftype == 'file': |
| if 'guestfs.mount' in __salt__: |
| util = 'guestfs' |
| elif 'qemu_nbd.init' in __salt__: |
| util = 'qemu_nbd' |
| else: |
| return None |
| mpt = __salt__['mount.mount'](path, device=root, util=util) |
| if not mpt: |
| return None |
| return mpt |
| |
| |
| def _umount(mpt, ftype): |
| if ftype == 'block': |
| __salt__['mount.umount'](mpt) |
| os.rmdir(mpt) |
| elif ftype == 'file': |
| __salt__['mount.umount'](mpt, util='qemu_nbd') |
| |
| |
| def apply_( |
| path, id_=None, |
| config=None, |
| approve_key=True, |
| install=True, |
| prep_install=False, |
| pub_key=None, |
| priv_key=None, |
| mount_point=None |
| ): |
| ''' |
| Seed a location (disk image, directory, or block device) with the |
| minion config, approve the minion's key, and/or install salt-minion. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'minion' seed.apply path id [config=config_data] \\ |
| [gen_key=(true|false)] [approve_key=(true|false)] \\ |
| [install=(true|false)] |
| |
| path |
| Full path to the directory, device, or disk image on the target |
| minion's file system. |
| |
| id |
| Minion id with which to seed the path. |
| |
| config |
| Minion configuration options. By default, the 'master' option is set to |
| the target host's 'master'. |
| |
| approve_key |
| Request a pre-approval of the generated minion key. Requires |
| that the salt-master be configured to either auto-accept all keys or |
| expect a signing request from the target host. Default: true. |
| |
| install |
| Install salt-minion, if absent. Default: true. |
| |
| prep_install |
| Prepare the bootstrap script, but don't run it. Default: false |
| ''' |
| stats = __salt__['file.stats'](path, follow_symlinks=True) |
| if not stats: |
| return '{0} does not exist'.format(path) |
| ftype = stats['type'] |
| path = stats['target'] |
| log.debug('Mounting {0} at {1}'.format(ftype, path)) |
| try: |
| os.makedirs(path) |
| except OSError: |
| # The directory already exists |
| pass |
| |
| mpt = _mount(path, ftype, mount_point) |
| |
| if not mpt: |
| return '{0} could not be mounted'.format(path) |
| |
| tmp = os.path.join(mpt, 'tmp') |
| log.debug('Attempting to create directory {0}'.format(tmp)) |
| try: |
| os.makedirs(tmp) |
| except OSError: |
| if not os.path.isdir(tmp): |
| raise |
| cfg_files = mkconfig(config, tmp=tmp, id_=id_, approve_key=approve_key, |
| pub_key=pub_key, priv_key=priv_key) |
| |
| if _check_install(mpt): |
| # salt-minion is already installed, just move the config and keys |
| # into place |
| log.info('salt-minion pre-installed on image, ' |
| 'configuring as {0}'.format(id_)) |
| minion_config = salt.config.minion_config(cfg_files['config']) |
| pki_dir = minion_config['pki_dir'] |
| if not os.path.isdir(os.path.join(mpt, pki_dir.lstrip('/'))): |
| __salt__['file.makedirs']( |
| os.path.join(mpt, pki_dir.lstrip('/'), '') |
| ) |
| os.rename(cfg_files['privkey'], os.path.join( |
| mpt, pki_dir.lstrip('/'), 'minion.pem')) |
| os.rename(cfg_files['pubkey'], os.path.join( |
| mpt, pki_dir.lstrip('/'), 'minion.pub')) |
| os.rename(cfg_files['config'], os.path.join(mpt, 'etc/salt/minion')) |
| res = True |
| elif install: |
| log.info('Attempting to install salt-minion to {0}'.format(mpt)) |
| res = _install(mpt) |
| elif prep_install: |
| log.error('The prep_install option is no longer supported. Please use ' |
| 'the bootstrap script installed with Salt, located at {0}.' |
| .format(salt.syspaths.BOOTSTRAP)) |
| res = False |
| else: |
| log.warning('No useful action performed on {0}'.format(mpt)) |
| res = False |
| |
| _umount(mpt, ftype) |
| return res |
| |
| |
| def mkconfig(config=None, |
| tmp=None, |
| id_=None, |
| approve_key=True, |
| pub_key=None, |
| priv_key=None): |
| ''' |
| Generate keys and config and put them in a tmp directory. |
| |
| pub_key |
| absolute path or file content of an optional preseeded salt key |
| |
| priv_key |
| absolute path or file content of an optional preseeded salt key |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'minion' seed.mkconfig [config=config_data] [tmp=tmp_dir] \\ |
| [id_=minion_id] [approve_key=(true|false)] |
| ''' |
| if tmp is None: |
| tmp = tempfile.mkdtemp() |
| if config is None: |
| config = {} |
| if 'master' not in config and __opts__['master'] != 'salt': |
| config['master'] = __opts__['master'] |
| if id_: |
| config['id'] = id_ |
| |
| # Write the new minion's config to a tmp file |
| tmp_config = os.path.join(tmp, 'minion') |
| with salt.utils.fopen(tmp_config, 'w+') as fp_: |
| fp_.write(salt.utils.cloud.salt_config_to_yaml(config)) |
| |
| # Generate keys for the minion |
| pubkeyfn = os.path.join(tmp, 'minion.pub') |
| privkeyfn = os.path.join(tmp, 'minion.pem') |
| preseeded = pub_key and priv_key |
| if preseeded: |
| log.debug('Writing minion.pub to {0}'.format(pubkeyfn)) |
| log.debug('Writing minion.pem to {0}'.format(privkeyfn)) |
| with salt.utils.fopen(pubkeyfn, 'w') as fic: |
| fic.write(_file_or_content(pub_key)) |
| with salt.utils.fopen(privkeyfn, 'w') as fic: |
| fic.write(_file_or_content(priv_key)) |
| os.chmod(pubkeyfn, 0o600) |
| os.chmod(privkeyfn, 0o600) |
| else: |
| salt.crypt.gen_keys(tmp, 'minion', 2048) |
| if approve_key and not preseeded: |
| with salt.utils.fopen(pubkeyfn) as fp_: |
| pubkey = fp_.read() |
| __salt__['pillar.ext']({'virtkey': [id_, pubkey]}) |
| |
| return {'config': tmp_config, 'pubkey': pubkeyfn, 'privkey': privkeyfn} |
| |
| |
| def _install(mpt): |
| ''' |
| Determine whether salt-minion is installed and, if not, |
| install it. |
| Return True if install is successful or already installed. |
| ''' |
| _check_resolv(mpt) |
| boot_, tmppath = (prep_bootstrap(mpt) |
| or salt.syspaths.BOOTSTRAP) |
| # Exec the chroot command |
| arg = 'stable {0}'.format('.'.join(salt.version.__version__.split('.')[:2])) |
| cmd = 'if type salt-minion; then exit 0; ' |
| cmd += 'else sh {0} -c /tmp {1}; fi'.format( |
| os.path.join(tmppath, 'bootstrap-salt.sh'), arg) |
| return not __salt__['cmd.run_chroot'](mpt, cmd, python_shell=True)['retcode'] |
| |
| |
| def _check_resolv(mpt): |
| ''' |
| Check that the resolv.conf is present and populated |
| ''' |
| resolv = os.path.join(mpt, 'etc/resolv.conf') |
| replace = False |
| if os.path.islink(resolv): |
| resolv = os.path.realpath(resolv) |
| if not os.path.isdir(os.path.dirname(resolv)): |
| os.makedirs(os.path.dirname(resolv)) |
| if not os.path.isfile(resolv): |
| replace = True |
| if not replace: |
| with salt.utils.fopen(resolv, 'rb') as fp_: |
| conts = fp_.read() |
| if 'nameserver' not in conts: |
| replace = True |
| if replace: |
| shutil.copy('/etc/resolv.conf', resolv) |
| |
| |
| def _check_install(root): |
| sh_ = '/bin/sh' |
| if os.path.isfile(os.path.join(root, 'bin/bash')): |
| sh_ = '/bin/bash' |
| |
| cmd = ('if ! type salt-minion; then exit 1; fi') |
| cmd = 'chroot \'{0}\' {1} -c \'{2}\''.format( |
| root, |
| sh_, |
| cmd) |
| |
| return not __salt__['cmd.retcode'](cmd, |
| output_loglevel='quiet', |
| python_shell=True) |