1
0

fileclient.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Classes that manage file clients
  4. '''
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. # Import python libs
  7. import contextlib
  8. import errno
  9. import logging
  10. import os
  11. import string
  12. import shutil
  13. import ftplib
  14. from tornado.httputil import parse_response_start_line, HTTPHeaders, HTTPInputError
  15. import salt.utils.atomicfile
  16. # Import salt libs
  17. from salt.exceptions import (
  18. CommandExecutionError, MinionError
  19. )
  20. import salt.client
  21. import salt.loader
  22. import salt.payload
  23. import salt.transport.client
  24. import salt.fileserver
  25. import salt.utils.data
  26. import salt.utils.files
  27. import salt.utils.gzip_util
  28. import salt.utils.hashutils
  29. import salt.utils.http
  30. import salt.utils.path
  31. import salt.utils.platform
  32. import salt.utils.stringutils
  33. import salt.utils.templates
  34. import salt.utils.url
  35. import salt.utils.versions
  36. from salt.utils.openstack.swift import SaltSwift
  37. # pylint: disable=no-name-in-module,import-error
  38. from salt.ext import six
  39. import salt.ext.six.moves.BaseHTTPServer as BaseHTTPServer
  40. from salt.ext.six.moves.urllib.error import HTTPError, URLError
  41. from salt.ext.six.moves.urllib.parse import urlparse, urlunparse
  42. # pylint: enable=no-name-in-module,import-error
  43. log = logging.getLogger(__name__)
  44. MAX_FILENAME_LENGTH = 255
  45. def get_file_client(opts, pillar=False):
  46. '''
  47. Read in the ``file_client`` option and return the correct type of file
  48. server
  49. '''
  50. client = opts.get('file_client', 'remote')
  51. if pillar and client == 'local':
  52. client = 'pillar'
  53. return {
  54. 'remote': RemoteClient,
  55. 'local': FSClient,
  56. 'pillar': PillarClient,
  57. }.get(client, RemoteClient)(opts)
  58. def decode_dict_keys_to_str(src):
  59. '''
  60. Convert top level keys from bytes to strings if possible.
  61. This is necessary because Python 3 makes a distinction
  62. between these types.
  63. '''
  64. if not six.PY3 or not isinstance(src, dict):
  65. return src
  66. output = {}
  67. for key, val in six.iteritems(src):
  68. if isinstance(key, bytes):
  69. try:
  70. key = key.decode()
  71. except UnicodeError:
  72. pass
  73. output[key] = val
  74. return output
  75. class Client(object):
  76. '''
  77. Base class for Salt file interactions
  78. '''
  79. def __init__(self, opts):
  80. self.opts = opts
  81. self.utils = salt.loader.utils(self.opts)
  82. self.serial = salt.payload.Serial(self.opts)
  83. # Add __setstate__ and __getstate__ so that the object may be
  84. # deep copied. It normally can't be deep copied because its
  85. # constructor requires an 'opts' parameter.
  86. # The TCP transport needs to be able to deep copy this class
  87. # due to 'salt.utils.context.ContextDict.clone'.
  88. def __setstate__(self, state):
  89. # This will polymorphically call __init__
  90. # in the derived class.
  91. self.__init__(state['opts'])
  92. def __getstate__(self):
  93. return {'opts': self.opts}
  94. def _check_proto(self, path):
  95. '''
  96. Make sure that this path is intended for the salt master and trim it
  97. '''
  98. if not path.startswith('salt://'):
  99. raise MinionError('Unsupported path: {0}'.format(path))
  100. file_path, saltenv = salt.utils.url.parse(path)
  101. return file_path
  102. def _file_local_list(self, dest):
  103. '''
  104. Helper util to return a list of files in a directory
  105. '''
  106. if os.path.isdir(dest):
  107. destdir = dest
  108. else:
  109. destdir = os.path.dirname(dest)
  110. filelist = set()
  111. for root, dirs, files in salt.utils.path.os_walk(destdir, followlinks=True):
  112. for name in files:
  113. path = os.path.join(root, name)
  114. filelist.add(path)
  115. return filelist
  116. @contextlib.contextmanager
  117. def _cache_loc(self, path, saltenv='base', cachedir=None):
  118. '''
  119. Return the local location to cache the file, cache dirs will be made
  120. '''
  121. cachedir = self.get_cachedir(cachedir)
  122. dest = salt.utils.path.join(cachedir,
  123. 'files',
  124. saltenv,
  125. path)
  126. destdir = os.path.dirname(dest)
  127. with salt.utils.files.set_umask(0o077):
  128. # remove destdir if it is a regular file to avoid an OSError when
  129. # running os.makedirs below
  130. if os.path.isfile(destdir):
  131. os.remove(destdir)
  132. # ensure destdir exists
  133. try:
  134. os.makedirs(destdir)
  135. except OSError as exc:
  136. if exc.errno != errno.EEXIST: # ignore if it was there already
  137. raise
  138. yield dest
  139. def get_cachedir(self, cachedir=None):
  140. if cachedir is None:
  141. cachedir = self.opts['cachedir']
  142. elif not os.path.isabs(cachedir):
  143. cachedir = os.path.join(self.opts['cachedir'], cachedir)
  144. return cachedir
  145. def get_file(self,
  146. path,
  147. dest='',
  148. makedirs=False,
  149. saltenv='base',
  150. gzip=None,
  151. cachedir=None):
  152. '''
  153. Copies a file from the local files or master depending on
  154. implementation
  155. '''
  156. raise NotImplementedError
  157. def file_list_emptydirs(self, saltenv='base', prefix=''):
  158. '''
  159. List the empty dirs
  160. '''
  161. raise NotImplementedError
  162. def cache_file(self, path, saltenv='base', cachedir=None, source_hash=None):
  163. '''
  164. Pull a file down from the file server and store it in the minion
  165. file cache
  166. '''
  167. return self.get_url(
  168. path, '', True, saltenv, cachedir=cachedir, source_hash=source_hash)
  169. def cache_files(self, paths, saltenv='base', cachedir=None):
  170. '''
  171. Download a list of files stored on the master and put them in the
  172. minion file cache
  173. '''
  174. ret = []
  175. if isinstance(paths, six.string_types):
  176. paths = paths.split(',')
  177. for path in paths:
  178. ret.append(self.cache_file(path, saltenv, cachedir=cachedir))
  179. return ret
  180. def cache_master(self, saltenv='base', cachedir=None):
  181. '''
  182. Download and cache all files on a master in a specified environment
  183. '''
  184. ret = []
  185. for path in self.file_list(saltenv):
  186. ret.append(
  187. self.cache_file(
  188. salt.utils.url.create(path), saltenv, cachedir=cachedir)
  189. )
  190. return ret
  191. def cache_dir(self, path, saltenv='base', include_empty=False,
  192. include_pat=None, exclude_pat=None, cachedir=None):
  193. '''
  194. Download all of the files in a subdir of the master
  195. '''
  196. ret = []
  197. path = self._check_proto(salt.utils.data.decode(path))
  198. # We want to make sure files start with this *directory*, use
  199. # '/' explicitly because the master (that's generating the
  200. # list of files) only runs on POSIX
  201. if not path.endswith('/'):
  202. path = path + '/'
  203. log.info(
  204. 'Caching directory \'%s\' for environment \'%s\'', path, saltenv
  205. )
  206. # go through the list of all files finding ones that are in
  207. # the target directory and caching them
  208. for fn_ in self.file_list(saltenv):
  209. fn_ = salt.utils.data.decode(fn_)
  210. if fn_.strip() and fn_.startswith(path):
  211. if salt.utils.stringutils.check_include_exclude(
  212. fn_, include_pat, exclude_pat):
  213. fn_ = self.cache_file(
  214. salt.utils.url.create(fn_), saltenv, cachedir=cachedir)
  215. if fn_:
  216. ret.append(fn_)
  217. if include_empty:
  218. # Break up the path into a list containing the bottom-level
  219. # directory (the one being recursively copied) and the directories
  220. # preceding it
  221. # separated = string.rsplit(path, '/', 1)
  222. # if len(separated) != 2:
  223. # # No slashes in path. (So all files in saltenv will be copied)
  224. # prefix = ''
  225. # else:
  226. # prefix = separated[0]
  227. cachedir = self.get_cachedir(cachedir)
  228. dest = salt.utils.path.join(cachedir, 'files', saltenv)
  229. for fn_ in self.file_list_emptydirs(saltenv):
  230. fn_ = salt.utils.data.decode(fn_)
  231. if fn_.startswith(path):
  232. minion_dir = '{0}/{1}'.format(dest, fn_)
  233. if not os.path.isdir(minion_dir):
  234. os.makedirs(minion_dir)
  235. ret.append(minion_dir)
  236. return ret
  237. def cache_local_file(self, path, **kwargs):
  238. '''
  239. Cache a local file on the minion in the localfiles cache
  240. '''
  241. dest = os.path.join(self.opts['cachedir'], 'localfiles',
  242. path.lstrip('/'))
  243. destdir = os.path.dirname(dest)
  244. if not os.path.isdir(destdir):
  245. os.makedirs(destdir)
  246. shutil.copyfile(path, dest)
  247. return dest
  248. def file_local_list(self, saltenv='base'):
  249. '''
  250. List files in the local minion files and localfiles caches
  251. '''
  252. filesdest = os.path.join(self.opts['cachedir'], 'files', saltenv)
  253. localfilesdest = os.path.join(self.opts['cachedir'], 'localfiles')
  254. fdest = self._file_local_list(filesdest)
  255. ldest = self._file_local_list(localfilesdest)
  256. return sorted(fdest.union(ldest))
  257. def file_list(self, saltenv='base', prefix=''):
  258. '''
  259. This function must be overwritten
  260. '''
  261. return []
  262. def dir_list(self, saltenv='base', prefix=''):
  263. '''
  264. This function must be overwritten
  265. '''
  266. return []
  267. def symlink_list(self, saltenv='base', prefix=''):
  268. '''
  269. This function must be overwritten
  270. '''
  271. return {}
  272. def is_cached(self, path, saltenv='base', cachedir=None):
  273. '''
  274. Returns the full path to a file if it is cached locally on the minion
  275. otherwise returns a blank string
  276. '''
  277. if path.startswith('salt://'):
  278. path, senv = salt.utils.url.parse(path)
  279. if senv:
  280. saltenv = senv
  281. escaped = True if salt.utils.url.is_escaped(path) else False
  282. # also strip escape character '|'
  283. localsfilesdest = os.path.join(
  284. self.opts['cachedir'], 'localfiles', path.lstrip('|/'))
  285. filesdest = os.path.join(
  286. self.opts['cachedir'], 'files', saltenv, path.lstrip('|/'))
  287. extrndest = self._extrn_path(path, saltenv, cachedir=cachedir)
  288. if os.path.exists(filesdest):
  289. return salt.utils.url.escape(filesdest) if escaped else filesdest
  290. elif os.path.exists(localsfilesdest):
  291. return salt.utils.url.escape(localsfilesdest) \
  292. if escaped \
  293. else localsfilesdest
  294. elif os.path.exists(extrndest):
  295. return extrndest
  296. return ''
  297. def list_states(self, saltenv):
  298. '''
  299. Return a list of all available sls modules on the master for a given
  300. environment
  301. '''
  302. states = set()
  303. for path in self.file_list(saltenv):
  304. if salt.utils.platform.is_windows():
  305. path = path.replace('\\', '/')
  306. if path.endswith('.sls'):
  307. # is an sls module!
  308. if path.endswith('/init.sls'):
  309. states.add(path.replace('/', '.')[:-9])
  310. else:
  311. states.add(path.replace('/', '.')[:-4])
  312. return sorted(states)
  313. def get_state(self, sls, saltenv, cachedir=None):
  314. '''
  315. Get a state file from the master and store it in the local minion
  316. cache; return the location of the file
  317. '''
  318. if '.' in sls:
  319. sls = sls.replace('.', '/')
  320. sls_url = salt.utils.url.create(sls + '.sls')
  321. init_url = salt.utils.url.create(sls + '/init.sls')
  322. for path in [sls_url, init_url]:
  323. dest = self.cache_file(path, saltenv, cachedir=cachedir)
  324. if dest:
  325. return {'source': path, 'dest': dest}
  326. return {}
  327. def get_dir(self, path, dest='', saltenv='base', gzip=None,
  328. cachedir=None):
  329. '''
  330. Get a directory recursively from the salt-master
  331. '''
  332. ret = []
  333. # Strip trailing slash
  334. path = self._check_proto(path).rstrip('/')
  335. # Break up the path into a list containing the bottom-level directory
  336. # (the one being recursively copied) and the directories preceding it
  337. separated = path.rsplit('/', 1)
  338. if len(separated) != 2:
  339. # No slashes in path. (This means all files in saltenv will be
  340. # copied)
  341. prefix = ''
  342. else:
  343. prefix = separated[0]
  344. # Copy files from master
  345. for fn_ in self.file_list(saltenv, prefix=path):
  346. # Prevent files in "salt://foobar/" (or salt://foo.sh) from
  347. # matching a path of "salt://foo"
  348. try:
  349. if fn_[len(path)] != '/':
  350. continue
  351. except IndexError:
  352. continue
  353. # Remove the leading directories from path to derive
  354. # the relative path on the minion.
  355. minion_relpath = fn_[len(prefix):].lstrip('/')
  356. ret.append(
  357. self.get_file(
  358. salt.utils.url.create(fn_),
  359. '{0}/{1}'.format(dest, minion_relpath),
  360. True, saltenv, gzip
  361. )
  362. )
  363. # Replicate empty dirs from master
  364. try:
  365. for fn_ in self.file_list_emptydirs(saltenv, prefix=path):
  366. # Prevent an empty dir "salt://foobar/" from matching a path of
  367. # "salt://foo"
  368. try:
  369. if fn_[len(path)] != '/':
  370. continue
  371. except IndexError:
  372. continue
  373. # Remove the leading directories from path to derive
  374. # the relative path on the minion.
  375. minion_relpath = fn_[len(prefix):].lstrip('/')
  376. minion_mkdir = '{0}/{1}'.format(dest, minion_relpath)
  377. if not os.path.isdir(minion_mkdir):
  378. os.makedirs(minion_mkdir)
  379. ret.append(minion_mkdir)
  380. except TypeError:
  381. pass
  382. ret.sort()
  383. return ret
  384. def get_url(self, url, dest, makedirs=False, saltenv='base',
  385. no_cache=False, cachedir=None, source_hash=None):
  386. '''
  387. Get a single file from a URL.
  388. '''
  389. url_data = urlparse(url)
  390. url_scheme = url_data.scheme
  391. url_path = os.path.join(
  392. url_data.netloc, url_data.path).rstrip(os.sep)
  393. # If dest is a directory, rewrite dest with filename
  394. if dest is not None \
  395. and (os.path.isdir(dest) or dest.endswith(('/', '\\'))):
  396. if url_data.query or len(url_data.path) > 1 and not url_data.path.endswith('/'):
  397. strpath = url.split('/')[-1]
  398. else:
  399. strpath = 'index.html'
  400. if salt.utils.platform.is_windows():
  401. strpath = salt.utils.path.sanitize_win_path(strpath)
  402. dest = os.path.join(dest, strpath)
  403. if url_scheme and url_scheme.lower() in string.ascii_lowercase:
  404. url_path = ':'.join((url_scheme, url_path))
  405. url_scheme = 'file'
  406. if url_scheme in ('file', ''):
  407. # Local filesystem
  408. if not os.path.isabs(url_path):
  409. raise CommandExecutionError(
  410. 'Path \'{0}\' is not absolute'.format(url_path)
  411. )
  412. if dest is None:
  413. with salt.utils.files.fopen(url_path, 'rb') as fp_:
  414. data = fp_.read()
  415. return data
  416. return url_path
  417. if url_scheme == 'salt':
  418. result = self.get_file(url, dest, makedirs, saltenv, cachedir=cachedir)
  419. if result and dest is None:
  420. with salt.utils.files.fopen(result, 'rb') as fp_:
  421. data = fp_.read()
  422. return data
  423. return result
  424. if dest:
  425. destdir = os.path.dirname(dest)
  426. if not os.path.isdir(destdir):
  427. if makedirs:
  428. os.makedirs(destdir)
  429. else:
  430. return ''
  431. elif not no_cache:
  432. dest = self._extrn_path(url, saltenv, cachedir=cachedir)
  433. if source_hash is not None:
  434. try:
  435. source_hash = source_hash.split('=')[-1]
  436. form = salt.utils.files.HASHES_REVMAP[len(source_hash)]
  437. if salt.utils.hashutils.get_hash(dest, form) == source_hash:
  438. log.debug(
  439. 'Cached copy of %s (%s) matches source_hash %s, '
  440. 'skipping download', url, dest, source_hash
  441. )
  442. return dest
  443. except (AttributeError, KeyError, IOError, OSError):
  444. pass
  445. destdir = os.path.dirname(dest)
  446. if not os.path.isdir(destdir):
  447. os.makedirs(destdir)
  448. if url_data.scheme == 's3':
  449. try:
  450. def s3_opt(key, default=None):
  451. '''
  452. Get value of s3.<key> from Minion config or from Pillar
  453. '''
  454. if 's3.' + key in self.opts:
  455. return self.opts['s3.' + key]
  456. try:
  457. return self.opts['pillar']['s3'][key]
  458. except (KeyError, TypeError):
  459. return default
  460. self.utils['s3.query'](method='GET',
  461. bucket=url_data.netloc,
  462. path=url_data.path[1:],
  463. return_bin=False,
  464. local_file=dest,
  465. action=None,
  466. key=s3_opt('key'),
  467. keyid=s3_opt('keyid'),
  468. service_url=s3_opt('service_url'),
  469. verify_ssl=s3_opt('verify_ssl', True),
  470. location=s3_opt('location'),
  471. path_style=s3_opt('path_style', False),
  472. https_enable=s3_opt('https_enable', True))
  473. return dest
  474. except Exception as exc:
  475. raise MinionError(
  476. 'Could not fetch from {0}. Exception: {1}'.format(url, exc)
  477. )
  478. if url_data.scheme == 'ftp':
  479. try:
  480. ftp = ftplib.FTP()
  481. ftp.connect(url_data.hostname, url_data.port)
  482. ftp.login(url_data.username, url_data.password)
  483. remote_file_path = url_data.path.lstrip('/')
  484. with salt.utils.files.fopen(dest, 'wb') as fp_:
  485. ftp.retrbinary('RETR {0}'.format(remote_file_path), fp_.write)
  486. ftp.quit()
  487. return dest
  488. except Exception as exc:
  489. raise MinionError('Could not retrieve {0} from FTP server. Exception: {1}'.format(url, exc))
  490. if url_data.scheme == 'swift':
  491. try:
  492. def swift_opt(key, default):
  493. '''
  494. Get value of <key> from Minion config or from Pillar
  495. '''
  496. if key in self.opts:
  497. return self.opts[key]
  498. try:
  499. return self.opts['pillar'][key]
  500. except (KeyError, TypeError):
  501. return default
  502. swift_conn = SaltSwift(swift_opt('keystone.user', None),
  503. swift_opt('keystone.tenant', None),
  504. swift_opt('keystone.auth_url', None),
  505. swift_opt('keystone.password', None))
  506. swift_conn.get_object(url_data.netloc,
  507. url_data.path[1:],
  508. dest)
  509. return dest
  510. except Exception:
  511. raise MinionError('Could not fetch from {0}'.format(url))
  512. get_kwargs = {}
  513. if url_data.username is not None \
  514. and url_data.scheme in ('http', 'https'):
  515. netloc = url_data.netloc
  516. at_sign_pos = netloc.rfind('@')
  517. if at_sign_pos != -1:
  518. netloc = netloc[at_sign_pos + 1:]
  519. fixed_url = urlunparse(
  520. (url_data.scheme, netloc, url_data.path,
  521. url_data.params, url_data.query, url_data.fragment))
  522. get_kwargs['auth'] = (url_data.username, url_data.password)
  523. else:
  524. fixed_url = url
  525. destfp = None
  526. try:
  527. # Tornado calls streaming_callback on redirect response bodies.
  528. # But we need streaming to support fetching large files (> RAM
  529. # avail). Here we are working around this by disabling recording
  530. # the body for redirections. The issue is fixed in Tornado 4.3.0
  531. # so on_header callback could be removed when we'll deprecate
  532. # Tornado<4.3.0. See #27093 and #30431 for details.
  533. # Use list here to make it writable inside the on_header callback.
  534. # Simple bool doesn't work here: on_header creates a new local
  535. # variable instead. This could be avoided in Py3 with 'nonlocal'
  536. # statement. There is no Py2 alternative for this.
  537. #
  538. # write_body[0] is used by the on_chunk callback to tell it whether
  539. # or not we need to write the body of the request to disk. For
  540. # 30x redirects we set this to False because we don't want to
  541. # write the contents to disk, as we will need to wait until we
  542. # get to the redirected URL.
  543. #
  544. # write_body[1] will contain a tornado.httputil.HTTPHeaders
  545. # instance that we will use to parse each header line. We
  546. # initialize this to False, and after we parse the status line we
  547. # will replace it with the HTTPHeaders instance. If/when we have
  548. # found the encoding used in the request, we set this value to
  549. # False to signify that we are done parsing.
  550. #
  551. # write_body[2] is where the encoding will be stored
  552. write_body = [None, False, None]
  553. def on_header(hdr):
  554. if write_body[1] is not False and write_body[2] is None:
  555. if not hdr.strip() and 'Content-Type' not in write_body[1]:
  556. # If write_body[0] is True, then we are not following a
  557. # redirect (initial response was a 200 OK). So there is
  558. # no need to reset write_body[0].
  559. if write_body[0] is not True:
  560. # We are following a redirect, so we need to reset
  561. # write_body[0] so that we properly follow it.
  562. write_body[0] = None
  563. # We don't need the HTTPHeaders object anymore
  564. write_body[1] = False
  565. return
  566. # Try to find out what content type encoding is used if
  567. # this is a text file
  568. write_body[1].parse_line(hdr) # pylint: disable=no-member
  569. if 'Content-Type' in write_body[1]:
  570. content_type = write_body[1].get('Content-Type') # pylint: disable=no-member
  571. if not content_type.startswith('text'):
  572. write_body[1] = write_body[2] = False
  573. else:
  574. encoding = 'utf-8'
  575. fields = content_type.split(';')
  576. for field in fields:
  577. if 'encoding' in field:
  578. encoding = field.split('encoding=')[-1]
  579. write_body[2] = encoding
  580. # We have found our encoding. Stop processing headers.
  581. write_body[1] = False
  582. # If write_body[0] is False, this means that this
  583. # header is a 30x redirect, so we need to reset
  584. # write_body[0] to None so that we parse the HTTP
  585. # status code from the redirect target. Additionally,
  586. # we need to reset write_body[2] so that we inspect the
  587. # headers for the Content-Type of the URL we're
  588. # following.
  589. if write_body[0] is write_body[1] is False:
  590. write_body[0] = write_body[2] = None
  591. # Check the status line of the HTTP request
  592. if write_body[0] is None:
  593. try:
  594. hdr = parse_response_start_line(hdr)
  595. except HTTPInputError:
  596. # Not the first line, do nothing
  597. return
  598. write_body[0] = hdr.code not in [301, 302, 303, 307]
  599. write_body[1] = HTTPHeaders()
  600. if no_cache:
  601. result = []
  602. def on_chunk(chunk):
  603. if write_body[0]:
  604. if write_body[2]:
  605. chunk = chunk.decode(write_body[2])
  606. result.append(chunk)
  607. else:
  608. dest_tmp = u"{0}.part".format(dest)
  609. # We need an open filehandle to use in the on_chunk callback,
  610. # that's why we're not using a with clause here.
  611. destfp = salt.utils.files.fopen(dest_tmp, 'wb') # pylint: disable=resource-leakage
  612. def on_chunk(chunk):
  613. if write_body[0]:
  614. destfp.write(chunk)
  615. query = salt.utils.http.query(
  616. fixed_url,
  617. stream=True,
  618. streaming_callback=on_chunk,
  619. header_callback=on_header,
  620. username=url_data.username,
  621. password=url_data.password,
  622. opts=self.opts,
  623. **get_kwargs
  624. )
  625. if 'handle' not in query:
  626. raise MinionError('Error: {0} reading {1}'.format(query['error'], url))
  627. if no_cache:
  628. if write_body[2]:
  629. return ''.join(result)
  630. return b''.join(result)
  631. else:
  632. destfp.close()
  633. destfp = None
  634. salt.utils.files.rename(dest_tmp, dest)
  635. return dest
  636. except HTTPError as exc:
  637. raise MinionError('HTTP error {0} reading {1}: {3}'.format(
  638. exc.code,
  639. url,
  640. *BaseHTTPServer.BaseHTTPRequestHandler.responses[exc.code]))
  641. except URLError as exc:
  642. raise MinionError('Error reading {0}: {1}'.format(url, exc.reason))
  643. finally:
  644. if destfp is not None:
  645. destfp.close()
  646. def get_template(
  647. self,
  648. url,
  649. dest,
  650. template='jinja',
  651. makedirs=False,
  652. saltenv='base',
  653. cachedir=None,
  654. **kwargs):
  655. '''
  656. Cache a file then process it as a template
  657. '''
  658. if 'env' in kwargs:
  659. # "env" is not supported; Use "saltenv".
  660. kwargs.pop('env')
  661. kwargs['saltenv'] = saltenv
  662. url_data = urlparse(url)
  663. sfn = self.cache_file(url, saltenv, cachedir=cachedir)
  664. if not sfn or not os.path.exists(sfn):
  665. return ''
  666. if template in salt.utils.templates.TEMPLATE_REGISTRY:
  667. data = salt.utils.templates.TEMPLATE_REGISTRY[template](
  668. sfn,
  669. **kwargs
  670. )
  671. else:
  672. log.error(
  673. 'Attempted to render template with unavailable engine %s',
  674. template
  675. )
  676. return ''
  677. if not data['result']:
  678. # Failed to render the template
  679. log.error('Failed to render template with error: %s', data['data'])
  680. return ''
  681. if not dest:
  682. # No destination passed, set the dest as an extrn_files cache
  683. dest = self._extrn_path(url, saltenv, cachedir=cachedir)
  684. # If Salt generated the dest name, create any required dirs
  685. makedirs = True
  686. destdir = os.path.dirname(dest)
  687. if not os.path.isdir(destdir):
  688. if makedirs:
  689. os.makedirs(destdir)
  690. else:
  691. salt.utils.files.safe_rm(data['data'])
  692. return ''
  693. shutil.move(data['data'], dest)
  694. return dest
  695. def _extrn_path(self, url, saltenv, cachedir=None):
  696. '''
  697. Return the extrn_filepath for a given url
  698. '''
  699. url_data = urlparse(url)
  700. if salt.utils.platform.is_windows():
  701. netloc = salt.utils.path.sanitize_win_path(url_data.netloc)
  702. else:
  703. netloc = url_data.netloc
  704. # Strip user:pass from URLs
  705. netloc = netloc.split('@')[-1]
  706. if cachedir is None:
  707. cachedir = self.opts['cachedir']
  708. elif not os.path.isabs(cachedir):
  709. cachedir = os.path.join(self.opts['cachedir'], cachedir)
  710. if url_data.query:
  711. file_name = '-'.join([url_data.path, url_data.query])
  712. else:
  713. file_name = url_data.path
  714. if len(file_name) > MAX_FILENAME_LENGTH:
  715. file_name = salt.utils.hashutils.sha256_digest(file_name)
  716. return salt.utils.path.join(
  717. cachedir,
  718. 'extrn_files',
  719. saltenv,
  720. netloc,
  721. file_name
  722. )
  723. class PillarClient(Client):
  724. '''
  725. Used by pillar to handle fileclient requests
  726. '''
  727. def _find_file(self, path, saltenv='base'):
  728. '''
  729. Locate the file path
  730. '''
  731. fnd = {'path': '',
  732. 'rel': ''}
  733. if salt.utils.url.is_escaped(path):
  734. # The path arguments are escaped
  735. path = salt.utils.url.unescape(path)
  736. for root in self.opts['pillar_roots'].get(saltenv, []):
  737. full = os.path.join(root, path)
  738. if os.path.isfile(full):
  739. fnd['path'] = full
  740. fnd['rel'] = path
  741. return fnd
  742. return fnd
  743. def get_file(self,
  744. path,
  745. dest='',
  746. makedirs=False,
  747. saltenv='base',
  748. gzip=None,
  749. cachedir=None):
  750. '''
  751. Copies a file from the local files directory into :param:`dest`
  752. gzip compression settings are ignored for local files
  753. '''
  754. path = self._check_proto(path)
  755. fnd = self._find_file(path, saltenv)
  756. fnd_path = fnd.get('path')
  757. if not fnd_path:
  758. return ''
  759. return fnd_path
  760. def file_list(self, saltenv='base', prefix=''):
  761. '''
  762. Return a list of files in the given environment
  763. with optional relative prefix path to limit directory traversal
  764. '''
  765. ret = []
  766. prefix = prefix.strip('/')
  767. for path in self.opts['pillar_roots'].get(saltenv, []):
  768. for root, dirs, files in salt.utils.path.os_walk(
  769. os.path.join(path, prefix), followlinks=True
  770. ):
  771. # Don't walk any directories that match file_ignore_regex or glob
  772. dirs[:] = [d for d in dirs if not salt.fileserver.is_file_ignored(self.opts, d)]
  773. for fname in files:
  774. relpath = os.path.relpath(os.path.join(root, fname), path)
  775. ret.append(salt.utils.data.decode(relpath))
  776. return ret
  777. def file_list_emptydirs(self, saltenv='base', prefix=''):
  778. '''
  779. List the empty dirs in the pillar_roots
  780. with optional relative prefix path to limit directory traversal
  781. '''
  782. ret = []
  783. prefix = prefix.strip('/')
  784. for path in self.opts['pillar_roots'].get(saltenv, []):
  785. for root, dirs, files in salt.utils.path.os_walk(
  786. os.path.join(path, prefix), followlinks=True
  787. ):
  788. # Don't walk any directories that match file_ignore_regex or glob
  789. dirs[:] = [d for d in dirs if not salt.fileserver.is_file_ignored(self.opts, d)]
  790. if not dirs and not files:
  791. ret.append(salt.utils.data.decode(os.path.relpath(root, path)))
  792. return ret
  793. def dir_list(self, saltenv='base', prefix=''):
  794. '''
  795. List the dirs in the pillar_roots
  796. with optional relative prefix path to limit directory traversal
  797. '''
  798. ret = []
  799. prefix = prefix.strip('/')
  800. for path in self.opts['pillar_roots'].get(saltenv, []):
  801. for root, dirs, files in salt.utils.path.os_walk(
  802. os.path.join(path, prefix), followlinks=True
  803. ):
  804. ret.append(salt.utils.data.decode(os.path.relpath(root, path)))
  805. return ret
  806. def __get_file_path(self, path, saltenv='base'):
  807. '''
  808. Return either a file path or the result of a remote find_file call.
  809. '''
  810. try:
  811. path = self._check_proto(path)
  812. except MinionError as err:
  813. # Local file path
  814. if not os.path.isfile(path):
  815. log.warning(
  816. 'specified file %s is not present to generate hash: %s',
  817. path, err
  818. )
  819. return None
  820. else:
  821. return path
  822. return self._find_file(path, saltenv)
  823. def hash_file(self, path, saltenv='base'):
  824. '''
  825. Return the hash of a file, to get the hash of a file in the pillar_roots
  826. prepend the path with salt://<file on server> otherwise, prepend the
  827. file with / for a local file.
  828. '''
  829. ret = {}
  830. fnd = self.__get_file_path(path, saltenv)
  831. if fnd is None:
  832. return ret
  833. try:
  834. # Remote file path (self._find_file() invoked)
  835. fnd_path = fnd['path']
  836. except TypeError:
  837. # Local file path
  838. fnd_path = fnd
  839. hash_type = self.opts.get('hash_type', 'md5')
  840. ret['hsum'] = salt.utils.hashutils.get_hash(fnd_path, form=hash_type)
  841. ret['hash_type'] = hash_type
  842. return ret
  843. def hash_and_stat_file(self, path, saltenv='base'):
  844. '''
  845. Return the hash of a file, to get the hash of a file in the pillar_roots
  846. prepend the path with salt://<file on server> otherwise, prepend the
  847. file with / for a local file.
  848. Additionally, return the stat result of the file, or None if no stat
  849. results were found.
  850. '''
  851. ret = {}
  852. fnd = self.__get_file_path(path, saltenv)
  853. if fnd is None:
  854. return ret, None
  855. try:
  856. # Remote file path (self._find_file() invoked)
  857. fnd_path = fnd['path']
  858. fnd_stat = fnd.get('stat')
  859. except TypeError:
  860. # Local file path
  861. fnd_path = fnd
  862. try:
  863. fnd_stat = list(os.stat(fnd_path))
  864. except Exception:
  865. fnd_stat = None
  866. hash_type = self.opts.get('hash_type', 'md5')
  867. ret['hsum'] = salt.utils.hashutils.get_hash(fnd_path, form=hash_type)
  868. ret['hash_type'] = hash_type
  869. return ret, fnd_stat
  870. def list_env(self, saltenv='base'):
  871. '''
  872. Return a list of the files in the file server's specified environment
  873. '''
  874. return self.file_list(saltenv)
  875. def master_opts(self):
  876. '''
  877. Return the master opts data
  878. '''
  879. return self.opts
  880. def envs(self):
  881. '''
  882. Return the available environments
  883. '''
  884. ret = []
  885. for saltenv in self.opts['pillar_roots']:
  886. ret.append(saltenv)
  887. return ret
  888. def master_tops(self):
  889. '''
  890. Originally returned information via the external_nodes subsystem.
  891. External_nodes was deprecated and removed in
  892. 2014.1.6 in favor of master_tops (which had been around since pre-0.17).
  893. salt-call --local state.show_top
  894. ends up here, but master_tops has not been extended to support
  895. show_top in a completely local environment yet. It's worth noting
  896. that originally this fn started with
  897. if 'external_nodes' not in opts: return {}
  898. So since external_nodes is gone now, we are just returning the
  899. empty dict.
  900. '''
  901. return {}
  902. class RemoteClient(Client):
  903. '''
  904. Interact with the salt master file server.
  905. '''
  906. def __init__(self, opts):
  907. Client.__init__(self, opts)
  908. self._closing = False
  909. self.channel = salt.transport.client.ReqChannel.factory(self.opts)
  910. if hasattr(self.channel, 'auth'):
  911. self.auth = self.channel.auth
  912. else:
  913. self.auth = ''
  914. def _refresh_channel(self):
  915. '''
  916. Reset the channel, in the event of an interruption
  917. '''
  918. self.channel = salt.transport.client.ReqChannel.factory(self.opts)
  919. return self.channel
  920. def __del__(self):
  921. self.destroy()
  922. def destroy(self):
  923. if self._closing:
  924. return
  925. self._closing = True
  926. channel = None
  927. try:
  928. channel = self.channel
  929. except AttributeError:
  930. pass
  931. if channel is not None:
  932. channel.close()
  933. def get_file(self,
  934. path,
  935. dest='',
  936. makedirs=False,
  937. saltenv='base',
  938. gzip=None,
  939. cachedir=None):
  940. '''
  941. Get a single file from the salt-master
  942. path must be a salt server location, aka, salt://path/to/file, if
  943. dest is omitted, then the downloaded file will be placed in the minion
  944. cache
  945. '''
  946. path, senv = salt.utils.url.split_env(path)
  947. if senv:
  948. saltenv = senv
  949. if not salt.utils.platform.is_windows():
  950. hash_server, stat_server = self.hash_and_stat_file(path, saltenv)
  951. try:
  952. mode_server = stat_server[0]
  953. except (IndexError, TypeError):
  954. mode_server = None
  955. else:
  956. hash_server = self.hash_file(path, saltenv)
  957. mode_server = None
  958. # Check if file exists on server, before creating files and
  959. # directories
  960. if hash_server == '':
  961. log.debug(
  962. 'Could not find file \'%s\' in saltenv \'%s\'',
  963. path, saltenv
  964. )
  965. return False
  966. # If dest is a directory, rewrite dest with filename
  967. if dest is not None \
  968. and (os.path.isdir(dest) or dest.endswith(('/', '\\'))):
  969. dest = os.path.join(dest, os.path.basename(path))
  970. log.debug(
  971. 'In saltenv \'%s\', \'%s\' is a directory. Changing dest to '
  972. '\'%s\'', saltenv, os.path.dirname(dest), dest
  973. )
  974. # Hash compare local copy with master and skip download
  975. # if no difference found.
  976. dest2check = dest
  977. if not dest2check:
  978. rel_path = self._check_proto(path)
  979. log.debug(
  980. 'In saltenv \'%s\', looking at rel_path \'%s\' to resolve '
  981. '\'%s\'', saltenv, rel_path, path
  982. )
  983. with self._cache_loc(
  984. rel_path, saltenv, cachedir=cachedir) as cache_dest:
  985. dest2check = cache_dest
  986. log.debug(
  987. 'In saltenv \'%s\', ** considering ** path \'%s\' to resolve '
  988. '\'%s\'', saltenv, dest2check, path
  989. )
  990. if dest2check and os.path.isfile(dest2check):
  991. if not salt.utils.platform.is_windows():
  992. hash_local, stat_local = \
  993. self.hash_and_stat_file(dest2check, saltenv)
  994. try:
  995. mode_local = stat_local[0]
  996. except (IndexError, TypeError):
  997. mode_local = None
  998. else:
  999. hash_local = self.hash_file(dest2check, saltenv)
  1000. mode_local = None
  1001. if hash_local == hash_server:
  1002. return dest2check
  1003. log.debug(
  1004. 'Fetching file from saltenv \'%s\', ** attempting ** \'%s\'',
  1005. saltenv, path
  1006. )
  1007. d_tries = 0
  1008. transport_tries = 0
  1009. path = self._check_proto(path)
  1010. load = {'path': path,
  1011. 'saltenv': saltenv,
  1012. 'cmd': '_serve_file'}
  1013. if gzip:
  1014. gzip = int(gzip)
  1015. load['gzip'] = gzip
  1016. fn_ = None
  1017. if dest:
  1018. destdir = os.path.dirname(dest)
  1019. if not os.path.isdir(destdir):
  1020. if makedirs:
  1021. try:
  1022. os.makedirs(destdir)
  1023. except OSError as exc:
  1024. if exc.errno != errno.EEXIST: # ignore if it was there already
  1025. raise
  1026. else:
  1027. return False
  1028. # We need an open filehandle here, that's why we're not using a
  1029. # with clause:
  1030. fn_ = salt.utils.files.fopen(dest, 'wb+') # pylint: disable=resource-leakage
  1031. else:
  1032. log.debug('No dest file found')
  1033. while True:
  1034. if not fn_:
  1035. load['loc'] = 0
  1036. else:
  1037. load['loc'] = fn_.tell()
  1038. data = self.channel.send(load, raw=True)
  1039. if six.PY3:
  1040. # Sometimes the source is local (eg when using
  1041. # 'salt.fileserver.FSChan'), in which case the keys are
  1042. # already strings. Sometimes the source is remote, in which
  1043. # case the keys are bytes due to raw mode. Standardize on
  1044. # strings for the top-level keys to simplify things.
  1045. data = decode_dict_keys_to_str(data)
  1046. try:
  1047. if not data['data']:
  1048. if not fn_ and data['dest']:
  1049. # This is a 0 byte file on the master
  1050. with self._cache_loc(
  1051. data['dest'],
  1052. saltenv,
  1053. cachedir=cachedir) as cache_dest:
  1054. dest = cache_dest
  1055. with salt.utils.files.fopen(cache_dest, 'wb+') as ofile:
  1056. ofile.write(data['data'])
  1057. if 'hsum' in data and d_tries < 3:
  1058. # Master has prompted a file verification, if the
  1059. # verification fails, re-download the file. Try 3 times
  1060. d_tries += 1
  1061. hsum = salt.utils.hashutils.get_hash(dest, salt.utils.stringutils.to_str(data.get('hash_type', b'md5')))
  1062. if hsum != data['hsum']:
  1063. log.warning(
  1064. 'Bad download of file %s, attempt %d of 3',
  1065. path, d_tries
  1066. )
  1067. continue
  1068. break
  1069. if not fn_:
  1070. with self._cache_loc(
  1071. data['dest'],
  1072. saltenv,
  1073. cachedir=cachedir) as cache_dest:
  1074. dest = cache_dest
  1075. # If a directory was formerly cached at this path, then
  1076. # remove it to avoid a traceback trying to write the file
  1077. if os.path.isdir(dest):
  1078. salt.utils.files.rm_rf(dest)
  1079. fn_ = salt.utils.atomicfile.atomic_open(dest, 'wb+')
  1080. if data.get('gzip', None):
  1081. data = salt.utils.gzip_util.uncompress(data['data'])
  1082. else:
  1083. data = data['data']
  1084. if six.PY3 and isinstance(data, str):
  1085. data = data.encode()
  1086. fn_.write(data)
  1087. except (TypeError, KeyError) as exc:
  1088. try:
  1089. data_type = type(data).__name__
  1090. except AttributeError:
  1091. # Shouldn't happen, but don't let this cause a traceback.
  1092. data_type = six.text_type(type(data))
  1093. transport_tries += 1
  1094. log.warning(
  1095. 'Data transport is broken, got: %s, type: %s, '
  1096. 'exception: %s, attempt %d of 3',
  1097. data, data_type, exc, transport_tries
  1098. )
  1099. self._refresh_channel()
  1100. if transport_tries > 3:
  1101. log.error(
  1102. 'Data transport is broken, got: %s, type: %s, '
  1103. 'exception: %s, retry attempts exhausted',
  1104. data, data_type, exc
  1105. )
  1106. break
  1107. if fn_:
  1108. fn_.close()
  1109. log.info(
  1110. 'Fetching file from saltenv \'%s\', ** done ** \'%s\'',
  1111. saltenv, path
  1112. )
  1113. else:
  1114. log.debug(
  1115. 'In saltenv \'%s\', we are ** missing ** the file \'%s\'',
  1116. saltenv, path
  1117. )
  1118. return dest
  1119. def file_list(self, saltenv='base', prefix=''):
  1120. '''
  1121. List the files on the master
  1122. '''
  1123. load = {'saltenv': saltenv,
  1124. 'prefix': prefix,
  1125. 'cmd': '_file_list'}
  1126. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1127. else self.channel.send(load)
  1128. def file_list_emptydirs(self, saltenv='base', prefix=''):
  1129. '''
  1130. List the empty dirs on the master
  1131. '''
  1132. load = {'saltenv': saltenv,
  1133. 'prefix': prefix,
  1134. 'cmd': '_file_list_emptydirs'}
  1135. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1136. else self.channel.send(load)
  1137. def dir_list(self, saltenv='base', prefix=''):
  1138. '''
  1139. List the dirs on the master
  1140. '''
  1141. load = {'saltenv': saltenv,
  1142. 'prefix': prefix,
  1143. 'cmd': '_dir_list'}
  1144. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1145. else self.channel.send(load)
  1146. def symlink_list(self, saltenv='base', prefix=''):
  1147. '''
  1148. List symlinked files and dirs on the master
  1149. '''
  1150. load = {'saltenv': saltenv,
  1151. 'prefix': prefix,
  1152. 'cmd': '_symlink_list'}
  1153. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1154. else self.channel.send(load)
  1155. def __hash_and_stat_file(self, path, saltenv='base'):
  1156. '''
  1157. Common code for hashing and stating files
  1158. '''
  1159. try:
  1160. path = self._check_proto(path)
  1161. except MinionError as err:
  1162. if not os.path.isfile(path):
  1163. log.warning(
  1164. 'specified file %s is not present to generate hash: %s',
  1165. path, err
  1166. )
  1167. return {}, None
  1168. else:
  1169. ret = {}
  1170. hash_type = self.opts.get('hash_type', 'md5')
  1171. ret['hsum'] = salt.utils.hashutils.get_hash(path, form=hash_type)
  1172. ret['hash_type'] = hash_type
  1173. return ret
  1174. load = {'path': path,
  1175. 'saltenv': saltenv,
  1176. 'cmd': '_file_hash'}
  1177. return self.channel.send(load)
  1178. def hash_file(self, path, saltenv='base'):
  1179. '''
  1180. Return the hash of a file, to get the hash of a file on the salt
  1181. master file server prepend the path with salt://<file on server>
  1182. otherwise, prepend the file with / for a local file.
  1183. '''
  1184. return self.__hash_and_stat_file(path, saltenv)
  1185. def hash_and_stat_file(self, path, saltenv='base'):
  1186. '''
  1187. The same as hash_file, but also return the file's mode, or None if no
  1188. mode data is present.
  1189. '''
  1190. hash_result = self.hash_file(path, saltenv)
  1191. try:
  1192. path = self._check_proto(path)
  1193. except MinionError as err:
  1194. if not os.path.isfile(path):
  1195. return hash_result, None
  1196. else:
  1197. try:
  1198. return hash_result, list(os.stat(path))
  1199. except Exception:
  1200. return hash_result, None
  1201. load = {'path': path,
  1202. 'saltenv': saltenv,
  1203. 'cmd': '_file_find'}
  1204. fnd = self.channel.send(load)
  1205. try:
  1206. stat_result = fnd.get('stat')
  1207. except AttributeError:
  1208. stat_result = None
  1209. return hash_result, stat_result
  1210. def list_env(self, saltenv='base'):
  1211. '''
  1212. Return a list of the files in the file server's specified environment
  1213. '''
  1214. load = {'saltenv': saltenv,
  1215. 'cmd': '_file_list'}
  1216. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1217. else self.channel.send(load)
  1218. def envs(self):
  1219. '''
  1220. Return a list of available environments
  1221. '''
  1222. load = {'cmd': '_file_envs'}
  1223. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1224. else self.channel.send(load)
  1225. def master_opts(self):
  1226. '''
  1227. Return the master opts data
  1228. '''
  1229. load = {'cmd': '_master_opts'}
  1230. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1231. else self.channel.send(load)
  1232. def master_tops(self):
  1233. '''
  1234. Return the metadata derived from the master_tops system
  1235. '''
  1236. log.debug(
  1237. 'The _ext_nodes master function has been renamed to _master_tops. '
  1238. 'To ensure compatibility when using older Salt masters we will '
  1239. 'continue to invoke the function as _ext_nodes until the '
  1240. 'Magnesium release.'
  1241. )
  1242. # TODO: Change back to _master_tops
  1243. # for Magnesium release
  1244. load = {'cmd': '_ext_nodes',
  1245. 'id': self.opts['id'],
  1246. 'opts': self.opts}
  1247. if self.auth:
  1248. load['tok'] = self.auth.gen_token(b'salt')
  1249. return salt.utils.data.decode(self.channel.send(load)) if six.PY2 \
  1250. else self.channel.send(load)
  1251. class FSClient(RemoteClient):
  1252. '''
  1253. A local client that uses the RemoteClient but substitutes the channel for
  1254. the FSChan object
  1255. '''
  1256. def __init__(self, opts): # pylint: disable=W0231
  1257. Client.__init__(self, opts) # pylint: disable=W0233
  1258. self._closing = False
  1259. self.channel = salt.fileserver.FSChan(opts)
  1260. self.auth = DumbAuth()
  1261. # Provide backward compatibility for anyone directly using LocalClient (but no
  1262. # one should be doing this).
  1263. LocalClient = FSClient
  1264. class DumbAuth(object):
  1265. '''
  1266. The dumbauth class is used to stub out auth calls fired from the FSClient
  1267. subsystem
  1268. '''
  1269. def gen_token(self, clear_tok):
  1270. return clear_tok