New Saltstack Salt formula
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.

seedng.py 8.8KB

8 년 전
8 년 전
8 년 전
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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_(path, id_=None, config=None, approve_key=True, install=True,
  75. prep_install=False, pub_key=None, priv_key=None, mount_point=None):
  76. '''
  77. Seed a location (disk image, directory, or block device) with the
  78. minion config, approve the minion's key, and/or install salt-minion.
  79. CLI Example:
  80. .. code-block:: bash
  81. salt 'minion' seed.apply path id [config=config_data] \\
  82. [gen_key=(true|false)] [approve_key=(true|false)] \\
  83. [install=(true|false)]
  84. path
  85. Full path to the directory, device, or disk image on the target
  86. minion's file system.
  87. id
  88. Minion id with which to seed the path.
  89. config
  90. Minion configuration options. By default, the 'master' option is set to
  91. the target host's 'master'.
  92. approve_key
  93. Request a pre-approval of the generated minion key. Requires
  94. that the salt-master be configured to either auto-accept all keys or
  95. expect a signing request from the target host. Default: true.
  96. install
  97. Install salt-minion, if absent. Default: true.
  98. prep_install
  99. Prepare the bootstrap script, but don't run it. Default: false
  100. '''
  101. stats = __salt__['file.stats'](path, follow_symlinks=True)
  102. if not stats:
  103. return '{0} does not exist'.format(path)
  104. ftype = stats['type']
  105. path = stats['target']
  106. log.debug('Mounting {0} at {1}'.format(ftype, path))
  107. try:
  108. os.makedirs(path)
  109. except OSError:
  110. # The directory already exists
  111. pass
  112. mpt = _mount(path, ftype, mount_point)
  113. if not mpt:
  114. return '{0} could not be mounted'.format(path)
  115. tmp = os.path.join(mpt, 'tmp')
  116. log.debug('Attempting to create directory {0}'.format(tmp))
  117. try:
  118. os.makedirs(tmp)
  119. except OSError:
  120. if not os.path.isdir(tmp):
  121. raise
  122. cfg_files = mkconfig(config, tmp=tmp, id_=id_, approve_key=approve_key,
  123. pub_key=pub_key, priv_key=priv_key)
  124. if _check_install(mpt):
  125. # salt-minion is already installed, just move the config and keys
  126. # into place
  127. log.info('salt-minion pre-installed on image, '
  128. 'configuring as {0}'.format(id_))
  129. minion_config = salt.config.minion_config(cfg_files['config'])
  130. pki_dir = minion_config['pki_dir']
  131. if not os.path.isdir(os.path.join(mpt, pki_dir.lstrip('/'))):
  132. __salt__['file.makedirs'](
  133. os.path.join(mpt, pki_dir.lstrip('/'), '')
  134. )
  135. os.rename(cfg_files['privkey'], os.path.join(
  136. mpt, pki_dir.lstrip('/'), 'minion.pem'))
  137. os.rename(cfg_files['pubkey'], os.path.join(
  138. mpt, pki_dir.lstrip('/'), 'minion.pub'))
  139. os.rename(cfg_files['config'], os.path.join(mpt, 'etc/salt/minion'))
  140. res = True
  141. elif install:
  142. log.info('Attempting to install salt-minion to {0}'.format(mpt))
  143. res = _install(mpt)
  144. elif prep_install:
  145. log.error('The prep_install option is no longer supported. Please use '
  146. 'the bootstrap script installed with Salt, located at {0}.'
  147. .format(salt.syspaths.BOOTSTRAP))
  148. res = False
  149. else:
  150. log.warning('No useful action performed on {0}'.format(mpt))
  151. res = False
  152. _umount(mpt, ftype)
  153. return res
  154. def mkconfig(config=None,
  155. tmp=None,
  156. id_=None,
  157. approve_key=True,
  158. pub_key=None,
  159. priv_key=None):
  160. '''
  161. Generate keys and config and put them in a tmp directory.
  162. pub_key
  163. absolute path or file content of an optional preseeded salt key
  164. priv_key
  165. absolute path or file content of an optional preseeded salt key
  166. CLI Example:
  167. .. code-block:: bash
  168. salt 'minion' seed.mkconfig [config=config_data] [tmp=tmp_dir] \\
  169. [id_=minion_id] [approve_key=(true|false)]
  170. '''
  171. if tmp is None:
  172. tmp = tempfile.mkdtemp()
  173. if config is None:
  174. config = {}
  175. if 'master' not in config and __opts__['master'] != 'salt':
  176. config['master'] = __opts__['master']
  177. if id_:
  178. config['id'] = id_
  179. # Write the new minion's config to a tmp file
  180. tmp_config = os.path.join(tmp, 'minion')
  181. with salt.utils.fopen(tmp_config, 'w+') as fp_:
  182. fp_.write(salt.utils.cloud.salt_config_to_yaml(config))
  183. # Generate keys for the minion
  184. pubkeyfn = os.path.join(tmp, 'minion.pub')
  185. privkeyfn = os.path.join(tmp, 'minion.pem')
  186. preseeded = pub_key and priv_key
  187. if preseeded:
  188. log.debug('Writing minion.pub to {0}'.format(pubkeyfn))
  189. log.debug('Writing minion.pem to {0}'.format(privkeyfn))
  190. with salt.utils.fopen(pubkeyfn, 'w') as fic:
  191. fic.write(_file_or_content(pub_key))
  192. with salt.utils.fopen(privkeyfn, 'w') as fic:
  193. fic.write(_file_or_content(priv_key))
  194. os.chmod(pubkeyfn, 0o600)
  195. os.chmod(privkeyfn, 0o600)
  196. else:
  197. salt.crypt.gen_keys(tmp, 'minion', 2048)
  198. if approve_key and not preseeded:
  199. with salt.utils.fopen(pubkeyfn) as fp_:
  200. pubkey = fp_.read()
  201. __salt__['pillar.ext']({'virtkey': [id_, pubkey]})
  202. return {'config': tmp_config, 'pubkey': pubkeyfn, 'privkey': privkeyfn}
  203. def _install(mpt):
  204. '''
  205. Determine whether salt-minion is installed and, if not,
  206. install it.
  207. Return True if install is successful or already installed.
  208. '''
  209. _check_resolv(mpt)
  210. boot_, tmppath = (prep_bootstrap(mpt)
  211. or salt.syspaths.BOOTSTRAP)
  212. # Exec the chroot command
  213. arg = 'stable {0}'.format('.'.join(salt.version.__version__.split('.')[:2]))
  214. cmd = 'if type salt-minion; then exit 0; '
  215. cmd += 'else sh {0} -c /tmp {1}; fi'.format(
  216. os.path.join(tmppath, 'bootstrap-salt.sh'), arg)
  217. return not __salt__['cmd.run_chroot'](mpt, cmd, python_shell=True)['retcode']
  218. def _check_resolv(mpt):
  219. '''
  220. Check that the resolv.conf is present and populated
  221. '''
  222. resolv = os.path.join(mpt, 'etc/resolv.conf')
  223. replace = False
  224. if os.path.islink(resolv):
  225. resolv = os.path.realpath(resolv)
  226. if not os.path.isdir(os.path.dirname(resolv)):
  227. os.makedirs(os.path.dirname(resolv))
  228. if not os.path.isfile(resolv):
  229. replace = True
  230. if not replace:
  231. with salt.utils.fopen(resolv, 'rb') as fp_:
  232. conts = fp_.read()
  233. if 'nameserver' not in conts:
  234. replace = True
  235. if replace:
  236. shutil.copy('/etc/resolv.conf', resolv)
  237. def _check_install(root):
  238. sh_ = '/bin/sh'
  239. if os.path.isfile(os.path.join(root, 'bin/bash')):
  240. sh_ = '/bin/bash'
  241. cmd = ('if ! type salt-minion; then exit 1; fi')
  242. cmd = 'chroot \'{0}\' {1} -c \'{2}\''.format(
  243. root,
  244. sh_,
  245. cmd)
  246. return not __salt__['cmd.retcode'](cmd,
  247. output_loglevel='quiet',
  248. python_shell=True)