New version of salt-formula from Saltstack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
8.9KB

  1. # -*- coding: utf-8 -*-
  2. '''
  3. Virtual machine image management tools
  4. '''
  5. from __future__ import absolute_import
  6. # Import python libs
  7. import os
  8. import shutil
  9. import logging
  10. import tempfile
  11. # Import salt libs
  12. import salt.crypt
  13. import salt.utils
  14. import salt.utils.cloud
  15. import salt.config
  16. import salt.syspaths
  17. import uuid
  18. # Set up logging
  19. log = logging.getLogger(__name__)
  20. # Don't shadow built-in's.
  21. __func_alias__ = {
  22. 'apply_': 'apply'
  23. }
  24. def _file_or_content(file_):
  25. if os.path.exists(file_):
  26. with salt.utils.fopen(file_) as fic:
  27. return fic.read()
  28. return file_
  29. def prep_bootstrap(mpt):
  30. '''
  31. Update and get the random script to a random place
  32. CLI Example:
  33. .. code-block:: bash
  34. salt '*' seed.prep_bootstrap /tmp
  35. '''
  36. # Verify that the boostrap script is downloaded
  37. bs_ = __salt__['config.gather_bootstrap_script']()
  38. fpd_ = os.path.join(mpt, 'tmp', "{0}".format(
  39. uuid.uuid4()))
  40. if not os.path.exists(fpd_):
  41. os.makedirs(fpd_)
  42. os.chmod(fpd_, 0o700)
  43. fp_ = os.path.join(fpd_, os.path.basename(bs_))
  44. # Copy script into tmp
  45. shutil.copy(bs_, fp_)
  46. tmppath = fpd_.replace(mpt, '')
  47. return fp_, tmppath
  48. def _mount(path, ftype, root=None):
  49. mpt = None
  50. if ftype == 'block':
  51. mpt = tempfile.mkdtemp()
  52. if not __salt__['mount.mount'](mpt, path):
  53. os.rmdir(mpt)
  54. return None
  55. elif ftype == 'dir':
  56. return path
  57. elif ftype == 'file':
  58. if 'guestfs.mount' in __salt__:
  59. util = 'guestfs'
  60. elif 'qemu_nbd.init' in __salt__:
  61. util = 'qemu_nbd'
  62. else:
  63. return None
  64. mpt = __salt__['mount.mount'](path, device=root, util=util)
  65. if not mpt:
  66. return None
  67. return mpt
  68. def _umount(mpt, ftype):
  69. if ftype == 'block':
  70. __salt__['mount.umount'](mpt)
  71. os.rmdir(mpt)
  72. elif ftype == 'file':
  73. __salt__['mount.umount'](mpt, util='qemu_nbd')
  74. def apply_(
  75. path, id_=None,
  76. config=None,
  77. approve_key=True,
  78. install=True,
  79. prep_install=False,
  80. pub_key=None,
  81. priv_key=None,
  82. mount_point=None
  83. ):
  84. '''
  85. Seed a location (disk image, directory, or block device) with the
  86. minion config, approve the minion's key, and/or install salt-minion.
  87. CLI Example:
  88. .. code-block:: bash
  89. salt 'minion' seed.apply path id [config=config_data] \\
  90. [gen_key=(true|false)] [approve_key=(true|false)] \\
  91. [install=(true|false)]
  92. path
  93. Full path to the directory, device, or disk image on the target
  94. minion's file system.
  95. id
  96. Minion id with which to seed the path.
  97. config
  98. Minion configuration options. By default, the 'master' option is set to
  99. the target host's 'master'.
  100. approve_key
  101. Request a pre-approval of the generated minion key. Requires
  102. that the salt-master be configured to either auto-accept all keys or
  103. expect a signing request from the target host. Default: true.
  104. install
  105. Install salt-minion, if absent. Default: true.
  106. prep_install
  107. Prepare the bootstrap script, but don't run it. Default: false
  108. '''
  109. stats = __salt__['file.stats'](path, follow_symlinks=True)
  110. if not stats:
  111. return '{0} does not exist'.format(path)
  112. ftype = stats['type']
  113. path = stats['target']
  114. log.debug('Mounting {0} at {1}'.format(ftype, path))
  115. try:
  116. os.makedirs(path)
  117. except OSError:
  118. # The directory already exists
  119. pass
  120. mpt = _mount(path, ftype, mount_point)
  121. if not mpt:
  122. return '{0} could not be mounted'.format(path)
  123. tmp = os.path.join(mpt, 'tmp')
  124. log.debug('Attempting to create directory {0}'.format(tmp))
  125. try:
  126. os.makedirs(tmp)
  127. except OSError:
  128. if not os.path.isdir(tmp):
  129. raise
  130. cfg_files = mkconfig(config, tmp=tmp, id_=id_, approve_key=approve_key,
  131. pub_key=pub_key, priv_key=priv_key)
  132. if _check_install(mpt):
  133. # salt-minion is already installed, just move the config and keys
  134. # into place
  135. log.info('salt-minion pre-installed on image, '
  136. 'configuring as {0}'.format(id_))
  137. minion_config = salt.config.minion_config(cfg_files['config'])
  138. pki_dir = minion_config['pki_dir']
  139. if not os.path.isdir(os.path.join(mpt, pki_dir.lstrip('/'))):
  140. __salt__['file.makedirs'](
  141. os.path.join(mpt, pki_dir.lstrip('/'), '')
  142. )
  143. os.rename(cfg_files['privkey'], os.path.join(
  144. mpt, pki_dir.lstrip('/'), 'minion.pem'))
  145. os.rename(cfg_files['pubkey'], os.path.join(
  146. mpt, pki_dir.lstrip('/'), 'minion.pub'))
  147. os.rename(cfg_files['config'], os.path.join(mpt, 'etc/salt/minion'))
  148. res = True
  149. elif install:
  150. log.info('Attempting to install salt-minion to {0}'.format(mpt))
  151. res = _install(mpt)
  152. elif prep_install:
  153. log.error('The prep_install option is no longer supported. Please use '
  154. 'the bootstrap script installed with Salt, located at {0}.'
  155. .format(salt.syspaths.BOOTSTRAP))
  156. res = False
  157. else:
  158. log.warning('No useful action performed on {0}'.format(mpt))
  159. res = False
  160. _umount(mpt, ftype)
  161. return res
  162. def mkconfig(config=None,
  163. tmp=None,
  164. id_=None,
  165. approve_key=True,
  166. pub_key=None,
  167. priv_key=None):
  168. '''
  169. Generate keys and config and put them in a tmp directory.
  170. pub_key
  171. absolute path or file content of an optional preseeded salt key
  172. priv_key
  173. absolute path or file content of an optional preseeded salt key
  174. CLI Example:
  175. .. code-block:: bash
  176. salt 'minion' seed.mkconfig [config=config_data] [tmp=tmp_dir] \\
  177. [id_=minion_id] [approve_key=(true|false)]
  178. '''
  179. if tmp is None:
  180. tmp = tempfile.mkdtemp()
  181. if config is None:
  182. config = {}
  183. if 'master' not in config and __opts__['master'] != 'salt':
  184. config['master'] = __opts__['master']
  185. if id_:
  186. config['id'] = id_
  187. # Write the new minion's config to a tmp file
  188. tmp_config = os.path.join(tmp, 'minion')
  189. with salt.utils.fopen(tmp_config, 'w+') as fp_:
  190. fp_.write(salt.utils.cloud.salt_config_to_yaml(config))
  191. # Generate keys for the minion
  192. pubkeyfn = os.path.join(tmp, 'minion.pub')
  193. privkeyfn = os.path.join(tmp, 'minion.pem')
  194. preseeded = pub_key and priv_key
  195. if preseeded:
  196. log.debug('Writing minion.pub to {0}'.format(pubkeyfn))
  197. log.debug('Writing minion.pem to {0}'.format(privkeyfn))
  198. with salt.utils.fopen(pubkeyfn, 'w') as fic:
  199. fic.write(_file_or_content(pub_key))
  200. with salt.utils.fopen(privkeyfn, 'w') as fic:
  201. fic.write(_file_or_content(priv_key))
  202. os.chmod(pubkeyfn, 0o600)
  203. os.chmod(privkeyfn, 0o600)
  204. else:
  205. salt.crypt.gen_keys(tmp, 'minion', 2048)
  206. if approve_key and not preseeded:
  207. with salt.utils.fopen(pubkeyfn) as fp_:
  208. pubkey = fp_.read()
  209. __salt__['pillar.ext']({'virtkey': [id_, pubkey]})
  210. return {'config': tmp_config, 'pubkey': pubkeyfn, 'privkey': privkeyfn}
  211. def _install(mpt):
  212. '''
  213. Determine whether salt-minion is installed and, if not,
  214. install it.
  215. Return True if install is successful or already installed.
  216. '''
  217. _check_resolv(mpt)
  218. boot_, tmppath = (prep_bootstrap(mpt)
  219. or salt.syspaths.BOOTSTRAP)
  220. # Exec the chroot command
  221. arg = 'stable {0}'.format('.'.join(salt.version.__version__.split('.')[:2]))
  222. cmd = 'if type salt-minion; then exit 0; '
  223. cmd += 'else sh {0} -c /tmp {1}; fi'.format(
  224. os.path.join(tmppath, 'bootstrap-salt.sh'), arg)
  225. return not __salt__['cmd.run_chroot'](mpt, cmd, python_shell=True)['retcode']
  226. def _check_resolv(mpt):
  227. '''
  228. Check that the resolv.conf is present and populated
  229. '''
  230. resolv = os.path.join(mpt, 'etc/resolv.conf')
  231. replace = False
  232. if os.path.islink(resolv):
  233. resolv = os.path.realpath(resolv)
  234. if not os.path.isdir(os.path.dirname(resolv)):
  235. os.makedirs(os.path.dirname(resolv))
  236. if not os.path.isfile(resolv):
  237. replace = True
  238. if not replace:
  239. with salt.utils.fopen(resolv, 'rb') as fp_:
  240. conts = fp_.read()
  241. if 'nameserver' not in conts:
  242. replace = True
  243. if replace:
  244. shutil.copy('/etc/resolv.conf', resolv)
  245. def _check_install(root):
  246. sh_ = '/bin/sh'
  247. if os.path.isfile(os.path.join(root, 'bin/bash')):
  248. sh_ = '/bin/bash'
  249. cmd = ('if ! type salt-minion; then exit 1; fi')
  250. cmd = 'chroot \'{0}\' {1} -c \'{2}\''.format(
  251. root,
  252. sh_,
  253. cmd)
  254. return not __salt__['cmd.retcode'](cmd,
  255. output_loglevel='quiet',
  256. python_shell=True)