PageRenderTime 57ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/salt/states/file.py

https://github.com/vitaliyf/salt
Python | 3882 lines | 3766 code | 28 blank | 88 comment | 58 complexity | 425da95a095315035529e76a74c2f07d MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. # -*- coding: utf-8 -*-
  2. '''
  3. Operations on regular files, special files, directories, and symlinks
  4. =====================================================================
  5. Salt States can aggressively manipulate files on a system. There are a number
  6. of ways in which files can be managed.
  7. Regular files can be enforced with the ``managed`` function. This function
  8. downloads files from the salt master and places them on the target system.
  9. The downloaded files can be rendered as a jinja, mako, or wempy template,
  10. adding a dynamic component to file management. An example of ``file.managed``
  11. which makes use of the jinja templating system would look like this:
  12. .. code-block:: yaml
  13. /etc/http/conf/http.conf:
  14. file.managed:
  15. - source: salt://apache/http.conf
  16. - user: root
  17. - group: root
  18. - mode: 644
  19. - template: jinja
  20. - defaults:
  21. custom_var: "default value"
  22. other_var: 123
  23. {% if grains['os'] == 'Ubuntu' %}
  24. - context:
  25. custom_var: "override"
  26. {% endif %}
  27. .. note::
  28. When using both the ``defaults`` and ``context`` arguments, note the extra
  29. indentation (four spaces instead of the normal two). This is due to an
  30. idiosyncrasy of how PyYAML loads nested dictionaries, and is explained in
  31. greater detail :ref:`here <nested-dict-indentation>`.
  32. If using a template, any user-defined template variables in the file defined in
  33. ``source`` must be passed in using the ``defaults`` and/or ``context``
  34. arguments. The general best practice is to place default values in
  35. ``defaults``, with conditional overrides going into ``context``, as seen above.
  36. The template will receive a variable ``custom_var``, which would be accessed in
  37. the template using ``{{ custom_var }}``. If the operating system is Ubuntu, the
  38. value of the variable ``custom_var`` would be *override*, otherwise it is the
  39. default *default value*
  40. The ``source`` parameter can be specified as a list. If this is done, then the
  41. first file to be matched will be the one that is used. This allows you to have
  42. a default file on which to fall back if the desired file does not exist on the
  43. salt fileserver. Here's an example:
  44. .. code-block:: yaml
  45. /etc/foo.conf:
  46. file.managed:
  47. - source:
  48. - salt://foo.conf.{{ grains['fqdn'] }}
  49. - salt://foo.conf.fallback
  50. - user: foo
  51. - group: users
  52. - mode: 644
  53. - backup: minion
  54. .. note::
  55. Salt supports backing up managed files via the backup option. For more
  56. details on this functionality please review the
  57. :doc:`backup_mode documentation </ref/states/backup_mode>`.
  58. The ``source`` parameter can also specify a file in another Salt environment.
  59. In this example ``foo.conf`` in the ``dev`` environment will be used instead.
  60. .. code-block:: yaml
  61. /etc/foo.conf:
  62. file.managed:
  63. - source:
  64. - salt://foo.conf?saltenv=dev
  65. - user: foo
  66. - group: users
  67. - mode: '0644'
  68. .. warning::
  69. When using a mode that includes a leading zero you must wrap the
  70. value in single quotes. If the value is not wrapped in quotes it
  71. will be read by YAML as an integer and evaluated as an octal.
  72. Special files can be managed via the ``mknod`` function. This function will
  73. create and enforce the permissions on a special file. The function supports the
  74. creation of character devices, block devices, and fifo pipes. The function will
  75. create the directory structure up to the special file if it is needed on the
  76. minion. The function will not overwrite or operate on (change major/minor
  77. numbers) existing special files with the exception of user, group, and
  78. permissions. In most cases the creation of some special files require root
  79. permisisons on the minion. This would require that the minion to be run as the
  80. root user. Here is an example of a character device:
  81. .. code-block:: yaml
  82. /var/named/chroot/dev/random:
  83. file.mknod:
  84. - ntype: c
  85. - major: 1
  86. - minor: 8
  87. - user: named
  88. - group: named
  89. - mode: 660
  90. Here is an example of a block device:
  91. .. code-block:: yaml
  92. /var/named/chroot/dev/loop0:
  93. file.mknod:
  94. - ntype: b
  95. - major: 7
  96. - minor: 0
  97. - user: named
  98. - group: named
  99. - mode: 660
  100. Here is an example of a fifo pipe:
  101. .. code-block:: yaml
  102. /var/named/chroot/var/log/logfifo:
  103. file.mknod:
  104. - ntype: p
  105. - user: named
  106. - group: named
  107. - mode: 660
  108. Directories can be managed via the ``directory`` function. This function can
  109. create and enforce the permissions on a directory. A directory statement will
  110. look like this:
  111. .. code-block:: yaml
  112. /srv/stuff/substuf:
  113. file.directory:
  114. - user: fred
  115. - group: users
  116. - mode: 755
  117. - makedirs: True
  118. If you need to enforce user and/or group ownership or permissions recursively
  119. on the directory's contents, you can do so by adding a ``recurse`` directive:
  120. .. code-block:: yaml
  121. /srv/stuff/substuf:
  122. file.directory:
  123. - user: fred
  124. - group: users
  125. - mode: 755
  126. - makedirs: True
  127. - recurse:
  128. - user
  129. - group
  130. - mode
  131. As a default, ``mode`` will resolve to ``dir_mode`` and ``file_mode``, to
  132. specify both directory and file permissions, use this form:
  133. .. code-block:: yaml
  134. /srv/stuff/substuf:
  135. file.directory:
  136. - user: fred
  137. - group: users
  138. - file_mode: 744
  139. - dir_mode: 755
  140. - makedirs: True
  141. - recurse:
  142. - user
  143. - group
  144. - mode
  145. Symlinks can be easily created; the symlink function is very simple and only
  146. takes a few arguments:
  147. .. code-block:: yaml
  148. /etc/grub.conf:
  149. file.symlink:
  150. - target: /boot/grub/grub.conf
  151. Recursive directory management can also be set via the ``recurse``
  152. function. Recursive directory management allows for a directory on the salt
  153. master to be recursively copied down to the minion. This is a great tool for
  154. deploying large code and configuration systems. A state using ``recurse``
  155. would look something like this:
  156. .. code-block:: yaml
  157. /opt/code/flask:
  158. file.recurse:
  159. - source: salt://code/flask
  160. - include_empty: True
  161. A more complex ``recurse`` example:
  162. .. code-block:: yaml
  163. {% set site_user = 'testuser' %}
  164. {% set site_name = 'test_site' %}
  165. {% set project_name = 'test_proj' %}
  166. {% set sites_dir = 'test_dir' %}
  167. django-project:
  168. file.recurse:
  169. - name: {{ sites_dir }}/{{ site_name }}/{{ project_name }}
  170. - user: {{ site_user }}
  171. - dir_mode: 2775
  172. - file_mode: '0644'
  173. - template: jinja
  174. - source: salt://project/templates_dir
  175. - include_empty: True
  176. '''
  177. # Import python libs
  178. import difflib
  179. import json
  180. import logging
  181. import os
  182. import pprint
  183. import shutil
  184. import traceback
  185. import yaml
  186. # Import salt libs
  187. import salt.utils
  188. import salt.utils.templates
  189. from salt.exceptions import CommandExecutionError
  190. from salt.utils.serializers import yaml as yaml_serializer
  191. from salt.utils.serializers import json as json_serializer
  192. from salt._compat import string_types, integer_types
  193. log = logging.getLogger(__name__)
  194. COMMENT_REGEX = r'^([[:space:]]*){0}[[:space:]]?'
  195. _ACCUMULATORS = {}
  196. _ACCUMULATORS_DEPS = {}
  197. def _check_user(user, group):
  198. '''
  199. Checks if the named user and group are present on the minion
  200. '''
  201. err = ''
  202. if user:
  203. uid = __salt__['file.user_to_uid'](user)
  204. if uid == '':
  205. err += 'User {0} is not available '.format(user)
  206. if group:
  207. gid = __salt__['file.group_to_gid'](group)
  208. if gid == '':
  209. err += 'Group {0} is not available'.format(group)
  210. return err
  211. def _gen_keep_files(name, require):
  212. '''
  213. Generate the list of files that need to be kept when a dir based function
  214. like directory or recurse has a clean.
  215. '''
  216. keep = set()
  217. keep.add(name)
  218. if isinstance(require, list):
  219. for comp in require:
  220. if 'file' in comp:
  221. keep.add(comp['file'])
  222. if os.path.isdir(comp['file']):
  223. for root, dirs, files in os.walk(comp['file']):
  224. for name in files:
  225. keep.add(os.path.join(root, name))
  226. for name in dirs:
  227. keep.add(os.path.join(root, name))
  228. return list(keep)
  229. def _check_file(name):
  230. ret = True
  231. msg = ''
  232. if not os.path.isabs(name):
  233. ret = False
  234. msg = 'Specified file {0} is not an absolute path'.format(name)
  235. elif not os.path.exists(name):
  236. ret = False
  237. msg = '{0}: file not found'.format(name)
  238. return ret, msg
  239. def _clean_dir(root, keep, exclude_pat):
  240. '''
  241. Clean out all of the files and directories in a directory (root) while
  242. preserving the files in a list (keep) and part of exclude_pat
  243. '''
  244. removed = set()
  245. real_keep = set()
  246. real_keep.add(root)
  247. if isinstance(keep, list):
  248. for fn_ in keep:
  249. if not os.path.isabs(fn_):
  250. continue
  251. real_keep.add(fn_)
  252. while True:
  253. fn_ = os.path.dirname(fn_)
  254. real_keep.add(fn_)
  255. if fn_ in ['/', ''.join([os.path.splitdrive(fn_)[0], '\\'])]:
  256. break
  257. for roots, dirs, files in os.walk(root):
  258. for name in dirs:
  259. nfn = os.path.join(roots, name)
  260. if os.path.islink(nfn):
  261. # the "directory" is in fact a symlink and cannot be
  262. # removed by shutil.rmtree
  263. files.append(nfn)
  264. continue
  265. if nfn not in real_keep:
  266. # -- check if this is a part of exclude_pat(only). No need to
  267. # check include_pat
  268. if not salt.utils.check_include_exclude(
  269. nfn[len(root) + 1:], None, exclude_pat):
  270. continue
  271. removed.add(nfn)
  272. if not __opts__['test']:
  273. shutil.rmtree(nfn)
  274. for name in files:
  275. nfn = os.path.join(roots, name)
  276. if nfn not in real_keep:
  277. # -- check if this is a part of exclude_pat(only). No need to
  278. # check include_pat
  279. if not salt.utils.check_include_exclude(
  280. nfn[len(root) + 1:], None, exclude_pat):
  281. continue
  282. removed.add(nfn)
  283. if not __opts__['test']:
  284. os.remove(nfn)
  285. return list(removed)
  286. def _error(ret, err_msg):
  287. ret['result'] = False
  288. ret['comment'] = err_msg
  289. return ret
  290. def _get_recurse_dest(prefix, fn_, source, env):
  291. '''
  292. Return the destination path to copy the file path, fn_(as returned by
  293. a call to __salt__['cp.cache_dir']), to.
  294. '''
  295. local_roots = []
  296. if __opts__['file_client'] == 'local':
  297. local_roots = __opts__['file_roots'][env]
  298. local_roots.sort(key=len, reverse=True)
  299. srcpath = source[7:] # the path after "salt://"
  300. # in solo mode(ie, file_client=='local'), fn_ is a path below
  301. # a file root; in remote mode, fn_ is a path below the cache_dir.
  302. for root in local_roots:
  303. rootlen = len(root)
  304. # if root is the longest prefix path of fn_
  305. if root == fn_[:rootlen]:
  306. cachedir = os.path.join(root, srcpath)
  307. break
  308. else:
  309. cachedir = os.path.join(
  310. __opts__['cachedir'], 'files', env, srcpath)
  311. return os.path.join(prefix, os.path.relpath(fn_, cachedir))
  312. def _check_directory(name,
  313. user,
  314. group,
  315. recurse,
  316. mode,
  317. clean,
  318. require,
  319. exclude_pat):
  320. '''
  321. Check what changes need to be made on a directory
  322. '''
  323. changes = {}
  324. if recurse:
  325. if not set(['user', 'group', 'mode']) >= set(recurse):
  326. return False, 'Types for "recurse" limited to "user", ' \
  327. '"group" and "mode"'
  328. if 'user' not in recurse:
  329. user = None
  330. if 'group' not in recurse:
  331. group = None
  332. if 'mode' not in recurse:
  333. mode = None
  334. for root, dirs, files in os.walk(name):
  335. for fname in files:
  336. fchange = {}
  337. path = os.path.join(root, fname)
  338. stats = __salt__['file.stats'](
  339. path, 'md5', follow_symlinks=False
  340. )
  341. if user is not None and user != stats.get('user'):
  342. fchange['user'] = user
  343. if group is not None and group != stats.get('group'):
  344. fchange['group'] = group
  345. if fchange:
  346. changes[path] = fchange
  347. for name_ in dirs:
  348. path = os.path.join(root, name_)
  349. fchange = _check_dir_meta(path, user, group, mode)
  350. if fchange:
  351. changes[path] = fchange
  352. else:
  353. fchange = _check_dir_meta(name, user, group, mode)
  354. if fchange:
  355. changes[name] = fchange
  356. if clean:
  357. keep = _gen_keep_files(name, require)
  358. for root, dirs, files in os.walk(name):
  359. for fname in files:
  360. fchange = {}
  361. path = os.path.join(root, fname)
  362. if path not in keep:
  363. if not salt.utils.check_include_exclude(
  364. path[len(name) + 1:], None, exclude_pat):
  365. continue
  366. fchange['removed'] = 'Removed due to clean'
  367. changes[path] = fchange
  368. for name_ in dirs:
  369. fchange = {}
  370. path = os.path.join(root, name_)
  371. if path not in keep:
  372. if not salt.utils.check_include_exclude(
  373. path[len(name) + 1:], None, exclude_pat):
  374. continue
  375. fchange['removed'] = 'Removed due to clean'
  376. changes[path] = fchange
  377. if not os.path.isdir(name):
  378. changes[name] = {'directory': 'new'}
  379. if changes:
  380. comments = ['The following files will be changed:\n']
  381. for fn_ in changes:
  382. key, val = changes[fn_].keys()[0], changes[fn_].values()[0]
  383. comments.append('{0}: {1} - {2}\n'.format(fn_, key, val))
  384. return None, ''.join(comments)
  385. return True, 'The directory {0} is in the correct state'.format(name)
  386. def _check_dir_meta(name,
  387. user,
  388. group,
  389. mode):
  390. '''
  391. Check the changes in directory metadata
  392. '''
  393. stats = __salt__['file.stats'](name, follow_symlinks=False)
  394. changes = {}
  395. if not stats:
  396. changes['directory'] = 'new'
  397. return changes
  398. if user is not None and user != stats['user']:
  399. changes['user'] = user
  400. if group is not None and group != stats['group']:
  401. changes['group'] = group
  402. # Normalize the dir mode
  403. smode = __salt__['config.manage_mode'](stats['mode'])
  404. mode = __salt__['config.manage_mode'](mode)
  405. if mode is not None and mode != smode:
  406. changes['mode'] = mode
  407. return changes
  408. def _check_touch(name, atime, mtime):
  409. '''
  410. Check to see if a file needs to be updated or created
  411. '''
  412. if not os.path.exists(name):
  413. return None, 'File {0} is set to be created'.format(name)
  414. stats = __salt__['file.stats'](name, follow_symlinks=False)
  415. if atime is not None:
  416. if str(atime) != str(stats['atime']):
  417. return None, 'Times set to be updated on file {0}'.format(name)
  418. if mtime is not None:
  419. if str(mtime) != str(stats['mtime']):
  420. return None, 'Times set to be updated on file {0}'.format(name)
  421. return True, 'File {0} exists and has the correct times'.format(name)
  422. def _get_symlink_ownership(path):
  423. return (
  424. __salt__['file.get_user'](path, follow_symlinks=False),
  425. __salt__['file.get_group'](path, follow_symlinks=False)
  426. )
  427. def _check_symlink_ownership(path, user, group):
  428. '''
  429. Check if the symlink ownership matches the specified user and group
  430. '''
  431. cur_user, cur_group = _get_symlink_ownership(path)
  432. return (cur_user == user) and (cur_group == group)
  433. def _set_symlink_ownership(path, user, group):
  434. '''
  435. Set the ownership of a symlink and return a boolean indicating
  436. success/failure
  437. '''
  438. try:
  439. __salt__['file.lchown'](path, user, group)
  440. except OSError:
  441. pass
  442. return _check_symlink_ownership(path, user, group)
  443. def _symlink_check(name, target, force, user, group):
  444. '''
  445. Check the symlink function
  446. '''
  447. if not os.path.exists(name) and not __salt__['file.is_link'](name):
  448. return None, 'Symlink {0} to {1} is set for creation'.format(
  449. name, target
  450. )
  451. if __salt__['file.is_link'](name):
  452. if __salt__['file.readlink'](name) != target:
  453. return None, 'Link {0} target is set to be changed to {1}'.format(
  454. name, target
  455. )
  456. else:
  457. result = True
  458. msg = 'The symlink {0} is present'.format(name)
  459. if not _check_symlink_ownership(name, user, group):
  460. result = None
  461. msg += (
  462. ', but the ownership of the symlink would be changed '
  463. 'from {2}:{3} to {0}:{1}'
  464. ).format(user, group, *_get_symlink_ownership(name))
  465. return result, msg
  466. else:
  467. if force:
  468. return None, ('The file or directory {0} is set for removal to '
  469. 'make way for a new symlink targeting {1}'
  470. .format(name, target))
  471. return False, ('File or directory exists where the symlink {0} '
  472. 'should be. Did you mean to use force?'.format(name))
  473. def _test_owner(kwargs, user=None):
  474. '''
  475. Convert owner to user, since other config management tools use owner,
  476. no need to punish people coming from other systems.
  477. PLEASE DO NOT DOCUMENT THIS! WE USE USER, NOT OWNER!!!!
  478. '''
  479. if user:
  480. return user
  481. if 'owner' in kwargs:
  482. log.warning(
  483. 'Use of argument owner found, "owner" is invalid, please '
  484. 'use "user"'
  485. )
  486. return kwargs['owner']
  487. return user
  488. def _unify_sources_and_hashes(source=None, source_hash=None,
  489. sources=None, source_hashes=None):
  490. '''
  491. Silly little function to give us a standard tuple list for sources and
  492. source_hashes
  493. '''
  494. if sources is None:
  495. sources = []
  496. if source_hashes is None:
  497. source_hashes = []
  498. if source and sources:
  499. return (False,
  500. "source and sources are mutually exclusive", [])
  501. if source_hash and source_hashes:
  502. return (False,
  503. "source_hash and source_hashes are mutually exclusive", [])
  504. if source:
  505. return (True, '', [(source, source_hash)])
  506. # Make a nice neat list of tuples exactly len(sources) long..
  507. return (True, '', map(None, sources, source_hashes[:len(sources)]))
  508. def _get_template_texts(source_list=None,
  509. template='jinja',
  510. defaults=None,
  511. context=None,
  512. **kwargs):
  513. '''
  514. Iterate a list of sources and process them as templates.
  515. Returns a list of 'chunks' containing the rendered templates.
  516. '''
  517. ret = {'name': '_get_template_texts',
  518. 'changes': {},
  519. 'result': True,
  520. 'comment': '',
  521. 'data': []}
  522. if source_list is None:
  523. return _error(ret,
  524. '_get_template_texts called with empty source_list')
  525. txtl = []
  526. for (source, source_hash) in source_list:
  527. tmpctx = defaults if defaults else {}
  528. if context:
  529. tmpctx.update(context)
  530. rndrd_templ_fn = __salt__['cp.get_template'](
  531. source,
  532. '',
  533. template=template,
  534. saltenv=__env__,
  535. context=tmpctx,
  536. **kwargs
  537. )
  538. msg = 'cp.get_template returned {0} (Called with: {1})'
  539. log.debug(msg.format(rndrd_templ_fn, source))
  540. if rndrd_templ_fn:
  541. tmplines = None
  542. with salt.utils.fopen(rndrd_templ_fn, 'rb') as fp_:
  543. tmplines = fp_.readlines()
  544. if not tmplines:
  545. msg = 'Failed to read rendered template file {0} ({1})'
  546. log.debug(msg.format(rndrd_templ_fn, source))
  547. ret['name'] = source
  548. return _error(ret, msg.format(rndrd_templ_fn, source))
  549. txtl.append(''.join(tmplines))
  550. else:
  551. msg = 'Failed to load template file {0}'.format(source)
  552. log.debug(msg)
  553. ret['name'] = source
  554. return _error(ret, msg)
  555. ret['data'] = txtl
  556. return ret
  557. def symlink(
  558. name,
  559. target,
  560. force=False,
  561. backupname=None,
  562. makedirs=False,
  563. user=None,
  564. group=None,
  565. mode=None,
  566. **kwargs):
  567. '''
  568. Create a symlink
  569. If the file already exists and is a symlink pointing to any location other
  570. than the specified target, the symlink will be replaced. If the symlink is
  571. a regular file or directory then the state will return False. If the
  572. regular file or directory is desired to be replaced with a symlink pass
  573. force: True, if it is to be renamed, pass a backupname.
  574. name
  575. The location of the symlink to create
  576. target
  577. The location that the symlink points to
  578. force
  579. If the name of the symlink exists and is not a symlink and
  580. force is set to False, the state will fail. If force is set to
  581. True, the file or directory in the way of the symlink file
  582. will be deleted to make room for the symlink, unless
  583. backupname is set, when it will be renamed
  584. backupname
  585. If the name of the symlink exists and is not a symlink, it will be
  586. renamed to the backupname. If the backupname already
  587. exists and force is False, the state will fail. Otherwise, the
  588. backupname will be removed first.
  589. makedirs
  590. If the location of the symlink does not already have a parent directory
  591. then the state will fail, setting makedirs to True will allow Salt to
  592. create the parent directory
  593. user
  594. The user to own the file, this defaults to the user salt is running as
  595. on the minion
  596. group
  597. The group ownership set for the file, this defaults to the group salt
  598. is running as on the minion. On Windows, this is ignored
  599. mode
  600. The permissions to set on this file, aka 644, 0775, 4664. Not supported
  601. on Windows
  602. '''
  603. # Make sure that leading zeros stripped by YAML loader are added back
  604. mode = __salt__['config.manage_mode'](mode)
  605. user = _test_owner(kwargs, user=user)
  606. ret = {'name': name,
  607. 'changes': {},
  608. 'result': True,
  609. 'comment': ''}
  610. if user is None:
  611. user = __opts__['user']
  612. if salt.utils.is_windows():
  613. if group is not None:
  614. log.warning(
  615. 'The group argument for {0} has been ignored as this '
  616. 'is a Windows system.'.format(name)
  617. )
  618. group = user
  619. if group is None:
  620. group = __salt__['file.gid_to_group'](
  621. __salt__['user.info'](user).get('gid', 0)
  622. )
  623. preflight_errors = []
  624. uid = __salt__['file.user_to_uid'](user)
  625. gid = __salt__['file.group_to_gid'](group)
  626. if uid == '':
  627. preflight_errors.append('User {0} does not exist'.format(user))
  628. if gid == '':
  629. preflight_errors.append('Group {0} does not exist'.format(group))
  630. if not os.path.isabs(name):
  631. preflight_errors.append(
  632. 'Specified file {0} is not an absolute path'.format(name)
  633. )
  634. if preflight_errors:
  635. msg = '. '.join(preflight_errors)
  636. if len(preflight_errors) > 1:
  637. msg += '.'
  638. return _error(ret, msg)
  639. if __opts__['test']:
  640. ret['result'], ret['comment'] = _symlink_check(name, target, force,
  641. user, group)
  642. return ret
  643. if not os.path.isdir(os.path.dirname(name)):
  644. if makedirs:
  645. __salt__['file.makedirs'](
  646. name,
  647. user=user,
  648. group=group,
  649. mode=mode)
  650. else:
  651. return _error(
  652. ret,
  653. 'Directory {0} for symlink is not present'.format(
  654. os.path.dirname(name)
  655. )
  656. )
  657. if __salt__['file.is_link'](name):
  658. # The link exists, verify that it matches the target
  659. if __salt__['file.readlink'](name) != target:
  660. # The target is wrong, delete the link
  661. os.remove(name)
  662. else:
  663. if _check_symlink_ownership(name, user, group):
  664. # The link looks good!
  665. ret['comment'] = ('Symlink {0} is present and owned by '
  666. '{1}:{2}'.format(name, user, group))
  667. else:
  668. if _set_symlink_ownership(name, user, group):
  669. ret['comment'] = ('Set ownership of symlink {0} to '
  670. '{1}:{2}'.format(name, user, group))
  671. ret['changes']['ownership'] = '{0}:{1}'.format(user, group)
  672. else:
  673. ret['result'] = False
  674. ret['comment'] += (
  675. 'Failed to set ownership of symlink {0} to '
  676. '{1}:{2}'.format(name, user, group)
  677. )
  678. return ret
  679. elif os.path.isfile(name) or os.path.isdir(name):
  680. # It is not a link, but a file or dir
  681. if backupname is not None:
  682. # Make a backup first
  683. if os.path.lexists(backupname):
  684. if not force:
  685. return _error(ret, ((
  686. 'File exists where the backup target {0} should go'
  687. ).format(backupname)))
  688. elif os.path.isfile(backupname):
  689. os.remove(backupname)
  690. elif os.path.isdir(backupname):
  691. shutil.rmtree(backupname)
  692. else:
  693. return _error(ret, ((
  694. 'Something exists where the backup target {0}'
  695. 'should go'
  696. ).format(backupname)))
  697. os.rename(name, backupname)
  698. elif force:
  699. # Remove whatever is in the way
  700. if os.path.isfile(name):
  701. os.remove(name)
  702. ret['changes']['forced'] = 'Symlink was forcibly replaced'
  703. else:
  704. shutil.rmtree(name)
  705. else:
  706. # Otherwise throw an error
  707. if os.path.isfile(name):
  708. return _error(ret,
  709. ('File exists where the symlink {0} should be'
  710. .format(name)))
  711. else:
  712. return _error(ret, ((
  713. 'Directory exists where the symlink {0} should be'
  714. ).format(name)))
  715. if not os.path.exists(name):
  716. # The link is not present, make it
  717. try:
  718. __salt__['file.symlink'](target, name)
  719. except OSError as exc:
  720. ret['result'] = False
  721. ret['comment'] = ('Unable to create new symlink {0} -> '
  722. '{1}: {2}'.format(name, target, exc))
  723. return ret
  724. else:
  725. ret['comment'] = ('Created new symlink {0} -> '
  726. '{1}'.format(name, target))
  727. ret['changes']['new'] = name
  728. if not _check_symlink_ownership(name, user, group):
  729. if not _set_symlink_ownership(name, user, group):
  730. ret['result'] = False
  731. ret['comment'] += (', but was unable to set ownership to '
  732. '{0}:{1}'.format(user, group))
  733. return ret
  734. def absent(name):
  735. '''
  736. Make sure that the named file or directory is absent. If it exists, it will
  737. be deleted. This will work to reverse any of the functions in the file
  738. state module.
  739. name
  740. The path which should be deleted
  741. '''
  742. ret = {'name': name,
  743. 'changes': {},
  744. 'result': True,
  745. 'comment': ''}
  746. if not os.path.isabs(name):
  747. return _error(
  748. ret, 'Specified file {0} is not an absolute path'.format(name)
  749. )
  750. if name == '/':
  751. return _error(ret, 'Refusing to make "/" absent')
  752. if os.path.isfile(name) or os.path.islink(name):
  753. if __opts__['test']:
  754. ret['result'] = None
  755. ret['comment'] = 'File {0} is set for removal'.format(name)
  756. return ret
  757. try:
  758. __salt__['file.remove'](name)
  759. ret['comment'] = 'Removed file {0}'.format(name)
  760. ret['changes']['removed'] = name
  761. return ret
  762. except CommandExecutionError as exc:
  763. return _error(ret, '{0}'.format(exc))
  764. elif os.path.isdir(name):
  765. if __opts__['test']:
  766. ret['result'] = None
  767. ret['comment'] = 'Directory {0} is set for removal'.format(name)
  768. return ret
  769. try:
  770. shutil.rmtree(name)
  771. ret['comment'] = 'Removed directory {0}'.format(name)
  772. ret['changes']['removed'] = name
  773. return ret
  774. except (OSError, IOError):
  775. return _error(ret, 'Failed to remove directory {0}'.format(name))
  776. ret['comment'] = 'File {0} is not present'.format(name)
  777. return ret
  778. def exists(name):
  779. '''
  780. Verify that the named file or directory is present or exists.
  781. Ensures pre-requisites outside of Salt's purview
  782. (e.g., keytabs, private keys, etc.) have been previously satisfied before
  783. deployment.
  784. name
  785. Absolute path which must exist
  786. '''
  787. ret = {'name': name,
  788. 'changes': {},
  789. 'result': True,
  790. 'comment': ''}
  791. if not os.path.exists(name):
  792. return _error(ret, ('Specified path {0} does not exist').format(name))
  793. ret['comment'] = 'Path {0} exists'.format(name)
  794. return ret
  795. def missing(name):
  796. '''
  797. Verify that the named file or directory is missing, this returns True only
  798. if the named file is missing but does not remove the file if it is present.
  799. name
  800. Absolute path which must NOT exist
  801. '''
  802. ret = {'name': name,
  803. 'changes': {},
  804. 'result': True,
  805. 'comment': ''}
  806. if os.path.exists(name):
  807. return _error(ret, ('Specified path {0} exists').format(name))
  808. ret['comment'] = 'Path {0} is missing'.format(name)
  809. return ret
  810. def managed(name,
  811. source=None,
  812. source_hash='',
  813. user=None,
  814. group=None,
  815. mode=None,
  816. template=None,
  817. makedirs=False,
  818. dir_mode=None,
  819. context=None,
  820. replace=True,
  821. defaults=None,
  822. env=None,
  823. backup='',
  824. show_diff=True,
  825. create=True,
  826. contents=None,
  827. contents_pillar=None,
  828. contents_grains=None,
  829. contents_newline=True,
  830. follow_symlinks=True,
  831. check_cmd=None,
  832. **kwargs):
  833. '''
  834. Manage a given file, this function allows for a file to be downloaded from
  835. the salt master and potentially run through a templating system.
  836. name
  837. The location of the file to manage
  838. source
  839. The source file to download to the minion, this source file can be
  840. hosted on either the salt master server, or on an HTTP or FTP server.
  841. Both HTTPS and HTTP are supported as well as downloading directly
  842. from Amazon S3 compatible URLs with both pre-configured and automatic
  843. IAM credentials. (see s3.get state documentation)
  844. File retrieval from Openstack Swift object storage is supported via
  845. swift://container/object_path URLs, see swift.get documentation.
  846. For files hosted on the salt file server, if the file is located on
  847. the master in the directory named spam, and is called eggs, the source
  848. string is salt://spam/eggs. If source is left blank or None
  849. (use ~ in YAML), the file will be created as an empty file and
  850. the content will not be managed
  851. If the file is hosted on a HTTP or FTP server then the source_hash
  852. argument is also required
  853. source_hash
  854. This can be one of the following:
  855. 1. a source hash string
  856. 2. the URI of a file that contains source hash strings
  857. The function accepts the first encountered long unbroken alphanumeric
  858. string of correct length as a valid hash, in order from most secure to
  859. least secure::
  860. Type Length
  861. ====== ======
  862. sha512 128
  863. sha384 96
  864. sha256 64
  865. sha224 56
  866. sha1 40
  867. md5 32
  868. The file can contain several checksums for several files. Each line
  869. must contain both the file name and the hash. If no file name is
  870. matched, the first hash encountered will be used, otherwise the most
  871. secure hash with the correct source file name will be used.
  872. Debian file type ``*.dsc`` is supported.
  873. Examples::
  874. /etc/rc.conf ef6e82e4006dee563d98ada2a2a80a27
  875. sha254c8525aee419eb649f0233be91c151178b30f0dff8ebbdcc8de71b1d5c8bcc06a /etc/resolv.conf
  876. ead48423703509d37c4a90e6a0d53e143b6fc268
  877. Known issues:
  878. If the remote server URL has the hash file as an apparent
  879. sub-directory of the source file, the module will discover that it
  880. has already cached a directory where a file should be cached. For
  881. example:
  882. .. code-block:: yaml
  883. tomdroid-src-0.7.3.tar.gz:
  884. file.managed:
  885. - name: /tmp/tomdroid-src-0.7.3.tar.gz
  886. - source: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz
  887. - source_hash: https://launchpad.net/tomdroid/beta/0.7.3/+download/tomdroid-src-0.7.3.tar.gz/+md5
  888. user
  889. The user to own the file, this defaults to the user salt is running as
  890. on the minion
  891. group
  892. The group ownership set for the file, this defaults to the group salt
  893. is running as on the minion On Windows, this is ignored
  894. mode
  895. The permissions to set on this file, aka 644, 0775, 4664. Not supported
  896. on Windows
  897. template
  898. If this setting is applied then the named templating engine will be
  899. used to render the downloaded file, currently jinja, mako, and wempy
  900. are supported
  901. makedirs
  902. If the file is located in a path without a parent directory, then
  903. the state will fail. If makedirs is set to True, then the parent
  904. directories will be created to facilitate the creation of the named
  905. file.
  906. dir_mode
  907. If directories are to be created, passing this option specifies the
  908. permissions for those directories. If this is not set, directories
  909. will be assigned permissions from the 'mode' argument.
  910. replace
  911. If this file should be replaced. If false, this command will
  912. not overwrite file contents but will enforce permissions if the file
  913. exists already. Default is True.
  914. context
  915. Overrides default context variables passed to the template.
  916. defaults
  917. Default context passed to the template.
  918. backup
  919. Overrides the default backup mode for this specific file.
  920. show_diff
  921. If set to False, the diff will not be shown.
  922. create
  923. Default is True, if create is set to False then the file will only be
  924. managed if the file already exists on the system.
  925. contents
  926. Default is None. If specified, will use the given string as the
  927. contents of the file. Should not be used in conjunction with a source
  928. file of any kind. Ignores hashes and does not use a templating engine.
  929. contents_pillar
  930. .. versionadded:: 0.17.0
  931. Operates like ``contents``, but draws from a value stored in pillar,
  932. using the pillar path syntax used in :mod:`pillar.get
  933. <salt.modules.pillar.get>`. This is useful when the pillar value
  934. contains newlines, as referencing a pillar variable using a jinja/mako
  935. template can result in YAML formatting issues due to the newlines
  936. causing indentation mismatches.
  937. For example, the following could be used to deploy an SSH private key:
  938. .. code-block:: yaml
  939. /home/deployer/.ssh/id_rsa:
  940. file.managed:
  941. - user: deployer
  942. - group: deployer
  943. - mode: 600
  944. - contents_pillar: userdata:deployer:id_rsa
  945. This would populate ``/home/deployer/.ssh/id_rsa`` with the contents of
  946. ``pillar['userdata']['deployer']['id_rsa']``. An example of this pillar
  947. setup would be like so:
  948. .. code-block:: yaml
  949. userdata:
  950. deployer:
  951. id_rsa: |
  952. -----BEGIN RSA PRIVATE KEY-----
  953. MIIEowIBAAKCAQEAoQiwO3JhBquPAalQF9qP1lLZNXVjYMIswrMe2HcWUVBgh+vY
  954. U7sCwx/dH6+VvNwmCoqmNnP+8gTPKGl1vgAObJAnMT623dMXjVKwnEagZPRJIxDy
  955. B/HaAre9euNiY3LvIzBTWRSeMfT+rWvIKVBpvwlgGrfgz70m0pqxu+UyFbAGLin+
  956. GpxzZAMaFpZw4sSbIlRuissXZj/sHpQb8p9M5IeO4Z3rjkCP1cxI
  957. -----END RSA PRIVATE KEY-----
  958. .. note::
  959. The private key above is shortened to keep the example brief, but
  960. shows how to do multiline string in YAML. The key is followed by a
  961. pipe character, and the mutliline string is indented two more
  962. spaces.
  963. contents_grains
  964. .. versionadded:: Helium
  965. Same as contents_pillar, but with grains
  966. contents_newline
  967. .. versionadded:: Helium
  968. When using contents, contents_pillar, or contents_grains, a newline is
  969. inserted into the data. When loading some data this newline is better
  970. left off. Setting contents_newline to False will omit this newline.
  971. follow_symlinks : True
  972. .. versionadded:: Helium
  973. If the desired path is a symlink follow it and make changes to the
  974. file to which the symlink points.
  975. check_cmd
  976. .. versionadded:: Helium
  977. Do run the state only if the check_cmd succeeds
  978. '''
  979. # Make sure that leading zeros stripped by YAML loader are added back
  980. mode = __salt__['config.manage_mode'](mode)
  981. # If no source is specified, set replace to False, as there is nothing
  982. # to replace the file with.
  983. src_defined = source or contents or contents_pillar or contents_grains
  984. if not src_defined and replace:
  985. replace = False
  986. log.warning(
  987. 'Neither \'source\' nor \'contents\' nor \'contents_pillar\' nor \'contents_grains\' '
  988. 'was defined, yet \'replace\' was set to \'True\'. As there is '
  989. 'no source to replace the file with, \'replace\' has been set '
  990. 'to \'False\' to avoid reading the file unnecessarily'.format(name)
  991. )
  992. user = _test_owner(kwargs, user=user)
  993. if salt.utils.is_windows():
  994. if group is not None:
  995. log.warning(
  996. 'The group argument for {0} has been ignored as this '
  997. 'is a Windows system.'.format(name)
  998. )
  999. group = user
  1000. ret = {'changes': {},
  1001. 'comment': '',
  1002. 'name': name,
  1003. 'result': True}
  1004. if not create:
  1005. if not os.path.isfile(name):
  1006. # Don't create a file that is not already present
  1007. ret['comment'] = ('File {0} is not present and is not set for '
  1008. 'creation').format(name)
  1009. return ret
  1010. u_check = _check_user(user, group)
  1011. if u_check:
  1012. # The specified user or group do not exist
  1013. return _error(ret, u_check)
  1014. if not os.path.isabs(name):
  1015. return _error(
  1016. ret, 'Specified file {0} is not an absolute path'.format(name))
  1017. if isinstance(env, salt._compat.string_types):
  1018. msg = (
  1019. 'Passing a salt environment should be done using \'saltenv\' not '
  1020. '\'env\'. This warning will go away in Salt Boron and this '
  1021. 'will be the default and expected behaviour. Please update your '
  1022. 'state files.'
  1023. )
  1024. salt.utils.warn_until('Boron', msg)
  1025. ret.setdefault('warnings', []).append(msg)
  1026. # No need to set __env__ = env since that's done in the state machinery
  1027. if os.path.isdir(name):
  1028. ret['comment'] = 'Specified target {0} is a directory'.format(name)
  1029. ret['result'] = False
  1030. return ret
  1031. if context is None:
  1032. context = {}
  1033. elif not isinstance(context, dict):
  1034. return _error(
  1035. ret, 'Context must be formed as a dict')
  1036. if len(filter(None, [contents, contents_pillar, contents_grains])) > 1:
  1037. return _error(
  1038. ret, 'Only one of contents, contents_pillar, and contents_grains is permitted')
  1039. # If contents_pillar was used, get the pillar data
  1040. if contents_pillar:
  1041. contents = __salt__['pillar.get'](contents_pillar)
  1042. if contents_grains:
  1043. contents = __salt__['grains.get'](contents_grains)
  1044. if contents_newline:
  1045. # Make sure file ends in newline
  1046. if contents and not contents.endswith('\n'):
  1047. contents += '\n'
  1048. if not replace and os.path.exists(name):
  1049. # Check and set the permissions if necessary
  1050. ret, _ = __salt__['file.check_perms'](name, ret, user, group, mode, follow_symlinks)
  1051. if __opts__['test']:
  1052. ret['comment'] = 'File {0} not updated'.format(name)
  1053. elif not ret['changes'] and ret['result']:
  1054. ret['comment'] = ('File {0} exists with proper permissions. '
  1055. 'No changes made.'.format(name))
  1056. return ret
  1057. if name in _ACCUMULATORS:
  1058. if not context:
  1059. context = {}
  1060. context['accumulator'] = _ACCUMULATORS[name]
  1061. try:
  1062. if __opts__['test']:
  1063. ret['result'], ret['comment'] = __salt__['file.check_managed'](
  1064. name,
  1065. source,
  1066. source_hash,
  1067. user,
  1068. group,
  1069. mode,
  1070. template,
  1071. context,
  1072. defaults,
  1073. __env__,
  1074. contents,
  1075. **kwargs
  1076. )
  1077. return ret
  1078. # If the source is a list then find which file exists
  1079. source, source_hash = __salt__['file.source_list'](
  1080. source,
  1081. source_hash,
  1082. __env__
  1083. )
  1084. except CommandExecutionError as exc:
  1085. ret['result'] = False
  1086. ret['comment'] = 'Unable to manage file: {0}'.format(exc)
  1087. return ret
  1088. # Gather the source file from the server
  1089. try:
  1090. sfn, source_sum, comment_ = __salt__['file.get_managed'](
  1091. name,
  1092. template,
  1093. source,
  1094. source_hash,
  1095. user,
  1096. group,
  1097. mode,
  1098. __env__,
  1099. context,
  1100. defaults,
  1101. **kwargs
  1102. )
  1103. except Exception as exc:
  1104. ret['changes'] = {}
  1105. log.debug(traceback.format_exc())
  1106. return _error(ret, 'Unable to manage file: {0}'.format(exc))
  1107. if check_cmd:
  1108. tmp_filename = salt.utils.mkstemp()
  1109. # if exists copy existing file to tmp to compare
  1110. if __salt__['file.file_exists'](name):
  1111. try:
  1112. __salt__['file.copy'](name, tmp_filename)
  1113. except Exception as exc:
  1114. return _error(ret, 'Unable to copy file {0} to {1}: {2}'.format(name, tmp_filename, exc))
  1115. try:
  1116. ret = __salt__['file.manage_file'](
  1117. tmp_filename,
  1118. sfn,
  1119. ret,
  1120. source,
  1121. source_sum,
  1122. user,
  1123. group,
  1124. mode,
  1125. __env__,
  1126. backup,
  1127. makedirs,
  1128. template,
  1129. show_diff,
  1130. contents,
  1131. dir_mode,
  1132. follow_symlinks)
  1133. except Exception as exc:
  1134. ret['changes'] = {}
  1135. log.debug(traceback.format_exc())
  1136. return _error(ret, 'Unable to check_cmd file: {0}'.format(exc))
  1137. # file being updated to verify using check_cmd
  1138. if ret['changes']:
  1139. # Reset ret
  1140. ret = {'changes': {},
  1141. 'comment': '',
  1142. 'name': name,
  1143. 'result': True}
  1144. cret = mod_run_check_cmd(
  1145. check_cmd, tmp_filename
  1146. )
  1147. if isinstance(cret, dict):
  1148. ret.update(cret)
  1149. return ret
  1150. else:
  1151. ret = {'changes': {},
  1152. 'comment': '',
  1153. 'name': name,
  1154. 'result': True}
  1155. if comment_ and contents is None:
  1156. return _error(ret, comment_)
  1157. else:
  1158. try:
  1159. return __salt__['file.manage_file'](
  1160. name,
  1161. sfn,
  1162. ret,
  1163. source,
  1164. source_sum,
  1165. user,
  1166. group,
  1167. mode,
  1168. __env__,
  1169. backup,
  1170. makedirs,
  1171. template,
  1172. show_diff,
  1173. contents,
  1174. dir_mode,
  1175. follow_symlinks)
  1176. except Exception as exc:
  1177. ret['changes'] = {}
  1178. log.debug(traceback.format_exc())
  1179. return _error(ret, 'Unable to manage file: {0}'.format(exc))
  1180. def directory(name,
  1181. user=None,
  1182. group=None,
  1183. recurse=None,
  1184. dir_mode=None,
  1185. file_mode=None,
  1186. makedirs=False,
  1187. clean=False,
  1188. require=None,
  1189. exclude_pat=None,
  1190. follow_symlinks=False,
  1191. **kwargs):
  1192. '''
  1193. Ensure that a named directory is present and has the right perms
  1194. name
  1195. The location to create or manage a directory
  1196. user
  1197. The user to own the directory; this defaults to the user salt is
  1198. running as on the minion
  1199. group
  1200. The group ownership set for the directory; this defaults to the group
  1201. salt is running as on the minion. On Windows, this is ignored
  1202. recurse
  1203. Enforce user/group ownership and mode of directory recursively. Accepts
  1204. a list of strings representing what you would like to recurse.
  1205. Example:
  1206. .. code-block:: yaml
  1207. /var/log/httpd:
  1208. file.directory:
  1209. - user: root
  1210. - group: root
  1211. - dir_mode: 755
  1212. - file_mode: 644
  1213. - recurse:
  1214. - user
  1215. - group
  1216. - mode
  1217. dir_mode / mode
  1218. The permissions mode to set any directories created. Not supported on
  1219. Windows
  1220. file_mode
  1221. The permissions mode to set any files created if 'mode' is ran in
  1222. 'recurse'. This defaults to dir_mode. Not supported on Windows
  1223. makedirs
  1224. If the directory is located in a path without a parent directory, then
  1225. the state will fail. If makedirs is set to True, then the parent
  1226. directories will be created to facilitate the creation of the named
  1227. file.
  1228. clean
  1229. Make sure that only files that are set up by salt and required by this
  1230. function are kept. If this option is set then everything in this
  1231. directory will be deleted unless it is required.
  1232. require
  1233. Require other resources such as packages or files
  1234. exclude_pat
  1235. When 'clean' is set to True, exclude this pattern from removal list
  1236. and preserve in the destination.
  1237. follow_symlinks : False
  1238. If the desired path is a symlink (or ``recurse`` is defined and a
  1239. symlink is encountered while recursing), follow it and check the
  1240. permissions of the directory/file to which the symlink points.
  1241. .. versionadded:: 2014.1.4
  1242. '''
  1243. # Remove trailing slash, if present
  1244. if name[-1] == '/':
  1245. name = name[:-1]
  1246. user = _test_owner(kwargs, user=user)
  1247. if salt.utils.is_windows():
  1248. if group is not None:
  1249. log.warning(
  1250. 'The group argument for {0} has been ignored as this is '
  1251. 'a Windows system.'.format(name)
  1252. )
  1253. group = user
  1254. if 'mode' in kwargs and not dir_mode:
  1255. dir_mode = kwargs.get('mode', [])
  1256. if not file_mode:
  1257. file_mode = dir_mode
  1258. # Make sure that leading zeros stripped by YAML loader are added back
  1259. dir_mode = __salt__['config.manage_mode'](dir_mode)
  1260. file_mode = __salt__['config.manage_mode'](file_mode)
  1261. ret = {'name': name,
  1262. 'changes': {},
  1263. 'result': True,
  1264. 'comment': ''}
  1265. u_check = _check_user(user, group)
  1266. if u_check:
  1267. # The specified user or group do not exist
  1268. return _error(ret, u_check)
  1269. if not os.path.isabs(name):
  1270. return

Large files files are truncated, but you can click here to view the full file