|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- # -*- 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
- cmd = 'if type salt-minion; then exit 0; '
- cmd += 'else sh {0} -c /tmp; fi'.format(os.path.join(tmppath, 'bootstrap-salt.sh'))
- 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)
|