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

template_processor.py 152 KiB

10 months ago
10 months ago
10 months ago
10 months ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083
  1. # vim: fileencoding=utf-8
  2. #
  3. from pprint import pprint
  4. from ..utils.package import (
  5. PackageAtomParser,
  6. PackageCreator,
  7. Package,
  8. PackageNotFound,
  9. PackageAtomName,
  10. Version,
  11. NonePackage,
  12. )
  13. from ..utils.files import (
  14. join_paths,
  15. write_file,
  16. read_file_lines,
  17. FilesError,
  18. check_directory_link,
  19. read_link,
  20. Process,
  21. get_target_from_link,
  22. get_directory_contents,
  23. )
  24. from .template_engine import (
  25. TemplateEngine,
  26. Variables,
  27. ConditionFailed,
  28. ParametersProcessor,
  29. DIR, FILE,
  30. ParametersContainer,
  31. )
  32. from calculate.variables.datavars import (
  33. StringType,
  34. ListType,
  35. NamespaceNode,
  36. VariableNode,
  37. TableType,
  38. VariableNotFoundError
  39. )
  40. from typing import (
  41. Union,
  42. Dict,
  43. List,
  44. Tuple,
  45. Iterator,
  46. Optional,
  47. Callable
  48. )
  49. from calculate.variables.loader import Datavars
  50. from .format.base_format import Format, FormatError
  51. from ..utils.io_module import IOModule
  52. from collections import OrderedDict, abc
  53. from contextlib import contextmanager
  54. from ..utils.mount import Mounts
  55. import hashlib
  56. import fnmatch
  57. import shutil
  58. import errno
  59. import stat
  60. import glob
  61. import copy
  62. import os
  63. # Наверное временно.
  64. CALCULATE_VERSION = Version('4.0')
  65. class TemplateExecutorError(Exception):
  66. pass
  67. class TemplateTypeConflict(Exception):
  68. pass
  69. class TemplateCollisionError(Exception):
  70. pass
  71. class CalculateConfigFile:
  72. '''Класс для работы с файлом /var/lib/calculate/config.'''
  73. def __init__(self, cl_config_path: str = '/var/lib/calculate/config',
  74. cl_chroot_path: str = '/'):
  75. self.chroot_path: str = cl_chroot_path
  76. self.cl_config_path: str = cl_config_path
  77. self._config_dictionary: OrderedDict = self._get_cl_config_dictionary()
  78. self._unsaved_changes: bool = False
  79. def __contains__(self, file_path: str) -> bool:
  80. file_path = self._remove_chroot(file_path)
  81. return file_path in self._config_dictionary
  82. def _get_cl_config_dictionary(self) -> OrderedDict:
  83. '''Метод для загрузки словаря файла /var/lib/calculate/config.'''
  84. config_dictionary = OrderedDict()
  85. if os.path.exists(self.cl_config_path):
  86. if os.path.isdir(self.cl_config_path):
  87. raise TemplateExecutorError(
  88. "directory instead calculate config file in: {}".
  89. format(self.cl_config_path))
  90. else:
  91. write_file(self.cl_config_path).close()
  92. return config_dictionary
  93. try:
  94. config_file_lines = read_file_lines(self.cl_config_path)
  95. except FilesError as error:
  96. raise TemplateExecutorError(
  97. "cannot read calculate config file in: {0}. Reason: {1}".
  98. format(self.cl_config_path, str(error)))
  99. # TODO Продумать проверку корректности найденного файла.
  100. for file_line in config_file_lines:
  101. filename, md5_sum = file_line.split(' ')
  102. config_dictionary.update({filename: md5_sum})
  103. self._unsaved_changes = False
  104. return config_dictionary
  105. def set_files_md5(self, file_path: str, file_md5: str) -> None:
  106. '''Метод для установки в config соответствия файла некоторой
  107. контрольной сумме.'''
  108. file_path = self._remove_chroot(file_path)
  109. self._config_dictionary[file_path] = file_md5
  110. self._unsaved_changes = True
  111. def remove_file(self, file_path: str) -> None:
  112. '''Метод для удаления файла из config.'''
  113. file_path = self._remove_chroot(file_path)
  114. if file_path in self._config_dictionary:
  115. self._config_dictionary.pop(file_path)
  116. self._unsaved_changes = True
  117. def compare_md5(self, file_path: str, file_md5: str) -> None:
  118. '''Метод для сравнения хэш-суммы из config и некоторой заданной.'''
  119. file_path = self._remove_chroot(file_path)
  120. if file_path in self._config_dictionary:
  121. return self._config_dictionary[file_path] == file_md5
  122. else:
  123. return False
  124. def save_changes(self) -> None:
  125. '''Метод для записи изменений, внессенных в файл config.'''
  126. if not self._unsaved_changes:
  127. return
  128. config_file = write_file(self.cl_config_path)
  129. for file_name, file_md5 in self._config_dictionary.items():
  130. config_file.write('{} {}\n'.format(file_name, file_md5))
  131. config_file.close()
  132. self._unsaved_changes = False
  133. def _remove_chroot(self, file_path: str) -> str:
  134. '''Метод для удаления корневого пути из указанного пути.'''
  135. if self.chroot_path != '/' and file_path.startswith(self.chroot_path):
  136. file_path = file_path[len(self.chroot_path):]
  137. return file_path
  138. class TemplateWrapper:
  139. '''Класс связывающий шаблон с целевым файлом и определяющий параметры
  140. наложения шаблона, обусловленные состоянием целевого файла.'''
  141. type_checks: Dict[int,
  142. Callable[[str], bool]] = {DIR: os.path.isdir,
  143. FILE: os.path.isfile}
  144. _protected_is_set: bool = False
  145. _protected_set: set = set()
  146. _unprotected_set: set = set()
  147. def __new__(cls, *args, **kwargs):
  148. if not cls._protected_is_set:
  149. # Устанавливаем значения PROTECTED, если не заданы.
  150. if 'chroot_path' in kwargs:
  151. chroot_path = kwargs['chroot_path']
  152. else:
  153. chroot_path = '/'
  154. cls._set_protected(chroot_path)
  155. return super().__new__(cls)
  156. def __init__(
  157. self, target_file_path: str,
  158. parameters: ParametersContainer,
  159. template_type: int,
  160. template_path: str,
  161. template_text: str = '',
  162. target_package: Optional[Package] = None,
  163. chroot_path: str = '/',
  164. config_archive_path: str = '/var/lib/calculate/config-archive',
  165. dbpkg: bool = True,
  166. pkg_autosave: bool = False):
  167. self.target_path: str = target_file_path
  168. self.template_path: str = template_path
  169. self.chroot_path: str = chroot_path
  170. self.config_archive_path: str = config_archive_path
  171. self.target_package_name: Union[PackageAtomName, None] = None
  172. self.package_atom_parser: PackageAtomParser = PackageAtomParser(
  173. chroot_path=self.chroot_path)
  174. self._pkg_autosave: bool = pkg_autosave
  175. # Вспомогательный флаг, включается, если по целевому пути лежит файл,
  176. # для которого не определился никакой пакет.
  177. self.target_without_package: bool = False
  178. self.parameters: ParametersContainer = parameters
  179. self.output_path: str = self.target_path
  180. self.input_path: Union[str, None] = None
  181. self.template_type: int = template_type
  182. self.template_text: str = template_text
  183. self.contents_matching: bool = True
  184. self.ca_is_missed: bool = False
  185. # Флаг, указывающий, что нужно удалить файл из target_path перед
  186. # применением шаблона.
  187. self.remove_original: bool = False
  188. # Флаг, указывающий, что целевой путь был изменен.
  189. self.target_path_is_changed: bool = False
  190. # Флаг, указывающий, что файл по целевому пути является ссылкой.
  191. self.target_is_link: bool = False
  192. # Пакет, к которому относится файл.
  193. self.target_package: Package = target_package
  194. # Флаг, разрешающий работу с CONTENTS. Если False, то выключает
  195. # protected для всех файлов блокирует все операции с CONTENTS и ._cfg.
  196. self.dbpkg: bool = dbpkg
  197. # Флаг, указывающий, что файл является PROTECTED.
  198. self.protected: bool = False
  199. # Временный флаг для определения того, является ли шаблон userspace.
  200. self.is_userspace: bool = False
  201. self.format_class: Union[Format, None] = None
  202. if self.parameters.run or self.parameters.exec:
  203. # Если есть параметр run или exec, то кроме текста шаблона ничего
  204. # не нужно.
  205. return
  206. if self.parameters.append in {'join', 'before', 'after', 'replace'}:
  207. # Получаем класс соответствующего формата файла.
  208. if self.parameters.format:
  209. self.format_class = ParametersProcessor.\
  210. available_formats[self.parameters.format]
  211. elif self.template_type is FILE:
  212. # TODO Здесь будет детектор форматов. Когда-нибудь.
  213. raise TemplateExecutorError("'format' parameter is not set"
  214. " file template.")
  215. # Если по этому пути что-то есть -- проверяем тип этого.
  216. if os.path.exists(target_file_path):
  217. for file_type, checker in self.type_checks.items():
  218. if checker(target_file_path):
  219. self.target_type = file_type
  220. break
  221. self.target_is_link = os.path.islink(target_file_path)
  222. # Если установлен параметр mirror и есть параметр source,
  223. # содержащий несуществующий путь -- удаляем целевой файл.
  224. if (self.parameters.source is True and self.parameters.mirror and
  225. not self.format_class.EXECUTABLE):
  226. self.remove_original = True
  227. else:
  228. self.target_type = None
  229. if self.format_class is not None and self.format_class.EXECUTABLE:
  230. # Если формат исполняемый -- проверяем, существует ли директория,
  231. # из которой будет выполняться шаблон.
  232. if self.target_type is None:
  233. # Если не существует -- создаем ее.
  234. if not os.path.exists(os.path.dirname(self.target_path)):
  235. os.makedirs(os.path.dirname(self.target_path))
  236. # Если есть параметр package, определяем по нему пакет.
  237. if self.parameters.package:
  238. self.target_package_name = self.parameters.package
  239. if (self.target_package is None or
  240. self.target_package.package_name !=
  241. self.target_package_name):
  242. self.target_package = Package(self.parameters.package,
  243. chroot_path=self.chroot_path,
  244. autosave=self._pkg_autosave)
  245. return
  246. self._check_type_conflicts()
  247. self._check_package_collision()
  248. self._check_user_changes()
  249. # if self.target_type is not None and self.contents_matching:
  250. # # Удаляем целевой файл, если append = 'replace'
  251. # if (self.parameters.append and
  252. # self.parameters.append == "replace"):
  253. # self.remove_original = True
  254. def _check_type_conflicts(self) -> None:
  255. '''Метод для проверки конфликтов типов.'''
  256. if self.parameters.append == 'link':
  257. if self.parameters.force:
  258. if os.path.exists(self.parameters.source):
  259. self.remove_original = True
  260. elif self.target_is_link:
  261. if self.template_type != self.target_type:
  262. raise TemplateTypeConflict(
  263. "the target is a link to {} while the template"
  264. "is {} and has append = 'link'".
  265. format('directory' if self.template_type ==
  266. DIR else 'file',
  267. 'file' if self.template_type ==
  268. DIR else 'directory'))
  269. else:
  270. self.remove_original = True
  271. elif self.target_type == DIR:
  272. raise TemplateTypeConflict("the target is a directory while "
  273. "the template has append = 'link'")
  274. elif self.target_type == FILE:
  275. raise TemplateTypeConflict("the target is a file while the"
  276. " template has append = 'link'")
  277. elif self.template_type == DIR:
  278. if self.target_type == FILE:
  279. if self.parameters.force:
  280. self.remove_original = True
  281. else:
  282. raise TemplateTypeConflict("the target is a file while the"
  283. " template is a directory")
  284. elif self.target_is_link:
  285. if self.parameters.force:
  286. self.remove_original = True
  287. elif not self.parameters.append == "remove":
  288. try:
  289. link_source = check_directory_link(
  290. self.target_path,
  291. chroot_path=self.chroot_path)
  292. self.target_path = link_source
  293. self.target_path_is_changed = True
  294. except FilesError as error:
  295. raise TemplateExecutorError("files error: {}".
  296. format(str(error)))
  297. elif self.template_type == FILE:
  298. if self.parameters.force:
  299. if self.target_type == DIR:
  300. self.remove_original = True
  301. elif self.target_is_link and self.target_type == FILE:
  302. try:
  303. link_source = read_link(self.target_path)
  304. self.target_path = get_target_from_link(
  305. self.target_path,
  306. link_source,
  307. chroot_path=self.chroot_path)
  308. self.target_path_is_changed = True
  309. except FilesError as error:
  310. raise TemplateExecutorError("files error: {}".
  311. format(str(error)))
  312. elif self.target_is_link:
  313. if not self.parameters.append == "remove":
  314. if self.target_type == DIR:
  315. raise TemplateTypeConflict("the target file is a link"
  316. " to a directory while the"
  317. " template is a file")
  318. else:
  319. raise TemplateTypeConflict("the target file is a link"
  320. " to a file while the"
  321. " template is a file")
  322. elif self.target_type == DIR:
  323. raise TemplateTypeConflict("the target file is a directory"
  324. " while the template is a file")
  325. def _check_package_collision(self) -> None:
  326. '''Метод для проверки на предмет коллизии, то есть конфликта пакета
  327. шаблона и целевого файла.'''
  328. if self.parameters.package:
  329. parameter_package = self.parameters.package
  330. else:
  331. parameter_package = None
  332. if self.target_type is not None:
  333. try:
  334. file_package = self.package_atom_parser.get_file_package(
  335. self.target_path)
  336. except PackageNotFound:
  337. file_package = None
  338. self.target_without_package = True
  339. else:
  340. file_package = None
  341. # Если для шаблона и целевого файла никаким образом не удается
  342. # определить пакет и есть параметр append -- шаблон пропускаем.
  343. if parameter_package is None and file_package is None:
  344. if (self.parameters.append and self.parameters.append != 'skip'
  345. and not self.parameters.handler):
  346. raise TemplateCollisionError(
  347. "'package' parameter is not defined for"
  348. " template with 'append' parameter")
  349. else:
  350. return
  351. elif parameter_package is None:
  352. self.target_package_name = file_package
  353. elif file_package is None:
  354. if self.parameters.handler:
  355. raise TemplateCollisionError((
  356. "The template is a handler while target"
  357. " file belongs {} package").format(
  358. file_package.atom
  359. ))
  360. self.target_package_name = parameter_package
  361. elif file_package != parameter_package and self.template_type != DIR:
  362. target_name = self._compare_packages(parameter_package,
  363. file_package)
  364. if (target_name is not None and self.target_package is not None
  365. and self.target_package.package_name == target_name):
  366. target_name = self.target_package.package_name
  367. if target_name is None or target_name.slot_specified:
  368. raise TemplateCollisionError((
  369. "The template package is {0} while target"
  370. " file package is {1}").format(
  371. parameter_package.atom,
  372. file_package.atom
  373. ))
  374. if (self.target_package is None or
  375. self.target_package_name != self.target_package.package_name):
  376. self.target_package = Package(self.target_package_name,
  377. chroot_path=self.chroot_path,
  378. autosave=True)
  379. # Теперь перемещаем файл в пакет со старшей версией.
  380. self.source_package = Package(file_package,
  381. chroot_path=self.chroot_path,
  382. autosave=self._pkg_autosave)
  383. removed = self.source_package.remove_file(self.target_path)
  384. for file_path, file_info in removed.items():
  385. if file_info['type'] == 'dir':
  386. self.target_package.add_dir(file_path)
  387. elif file_info['type'] == 'obj':
  388. self.target_package.add_obj(file_path,
  389. file_md5=file_info['md5'],
  390. mtime=file_info['mtime'])
  391. elif file_info['type'] == 'sym':
  392. self.target_package.add_sym(
  393. file_path,
  394. target_path=file_info['target'],
  395. mtime=file_info['mtime'])
  396. else:
  397. self.target_package_name = parameter_package
  398. if (self.target_package is None or
  399. self.target_package_name != self.target_package.package_name):
  400. self.target_package = Package(self.target_package_name,
  401. chroot_path=self.chroot_path,
  402. autosave=self._pkg_autosave)
  403. def _compare_packages(self, lpackage: PackageAtomName,
  404. rpackage: PackageAtomName
  405. ) -> Union[None, PackageAtomName]:
  406. '''Метод, сравнивающий пакеты по их именам, возвращает старший, если
  407. пакеты с одинаковыми именами, но разными версиями, или None, если их
  408. имена и категории не равны.'''
  409. if lpackage.category != rpackage.category:
  410. return None
  411. if lpackage.name != rpackage.name:
  412. return None
  413. if lpackage.version > rpackage.version:
  414. return lpackage
  415. else:
  416. return rpackage
  417. def _check_user_changes(self) -> None:
  418. '''Метод для проверки наличия пользовательских изменений в
  419. конфигурационных файлах.'''
  420. # Эта проверка только для файлов.
  421. if self.template_type != FILE:
  422. return
  423. # Проверим, является ли файл защищенным.
  424. # Сначала проверяем по переменной CONFIG_PROTECT.
  425. if self.dbpkg and not self.parameters.handler:
  426. for protected_path in self._protected_set:
  427. if self.target_path.startswith(protected_path):
  428. self.protected = True
  429. break
  430. # Затем по переменной CONFIG_PROTECT_MASK.
  431. for unprotected_path in self._unprotected_set:
  432. if self.target_path.startswith(unprotected_path):
  433. self.protected = False
  434. break
  435. else:
  436. self.protected = False
  437. # Собираем список имеющихся ._cfg файлов.
  438. cfg_pattern = os.path.join(os.path.dirname(self.target_path),
  439. "._cfg????_{}".format(
  440. os.path.basename(self.target_path)))
  441. self.cfg_list = glob.glob(cfg_pattern)
  442. self.cfg_list.sort()
  443. # Путь к архивной версии файла.
  444. self.archive_path = self._get_archive_path(self.target_path)
  445. self.contents_matching = (self.parameters.autoupdate)
  446. if not self.protected:
  447. self.contents_matching = True
  448. elif self.parameters.unbound:
  449. # Если присутствует unbound, то просто модифицируем файл и
  450. # удаляем его из CONTENTS.
  451. self.contents_matching = True
  452. elif self.target_type is None:
  453. # Если целевой файл отсутствует.
  454. if (self.target_path in self.target_package and
  455. not self.parameters.autoupdate):
  456. # Проверка -- был ли файл удален.
  457. self.contents_matching = False
  458. pass
  459. else:
  460. self.contents_matching = True
  461. elif self.target_without_package:
  462. # Если файл по целевому пути не относится к какому-либо пакету.
  463. # self.contents_matching = False
  464. pass
  465. elif self.target_type is DIR and self.parameters.force:
  466. self.contents_matching = True
  467. elif not self.contents_matching:
  468. # Если файл есть и он относится к текущему пакету.
  469. # Если по каким-то причинам уже нужно считать, что хэш-суммы
  470. # совпадают -- в дальнейшей проверке нет необходимости.
  471. # target_md5 = self.target_package.get_md5(self.target_path)
  472. self.contents_matching = self.target_package.check_contents_data(
  473. self.target_path,
  474. symlink=self.target_is_link)
  475. # Если по целевому пути файл не относящийся к какому-либо пакету и
  476. # присутствует параметр autoupdate -- удаляем этот файл.
  477. if (self.target_without_package and
  478. (self.parameters.autoupdate or self.parameters.force)):
  479. self.remove_original = True
  480. # Определяем пути входных и выходных файлов.
  481. if self.contents_matching:
  482. # Приоритет отдаем пути из параметра source.
  483. if self.parameters.source:
  484. self.input_path = self.parameters.source
  485. elif self.cfg_list and not self.parameters.unbound:
  486. if os.path.exists(self.archive_path):
  487. self.input_path = self.archive_path
  488. else:
  489. self.input_path = self.target_path
  490. self.ca_is_missed = True
  491. else:
  492. self.input_path = self.target_path
  493. self.output_path = self.target_path
  494. else:
  495. # Приоритет отдаем пути из параметра source.
  496. if self.parameters.source:
  497. self.input_path = self.parameters.source
  498. else:
  499. if os.path.exists(self.archive_path):
  500. self.input_path = self.archive_path
  501. else:
  502. self.input_path = self.target_path
  503. self.ca_is_missed = True
  504. self.output_path = self._get_cfg_path(self.target_path)
  505. def _get_archive_path(self, file_path: str) -> str:
  506. '''Метод для получения пути к архивной версии указанного файла.'''
  507. if self.chroot_path != "/" and file_path.startswith(self.chroot_path):
  508. file_path = file_path[len(self.chroot_path):]
  509. return join_paths(self.config_archive_path, file_path)
  510. def _get_cfg_path(self, file_path: str) -> str:
  511. '''Метод для получения пути для создания нового ._cfg????_ файла.'''
  512. if self.cfg_list:
  513. last_cfg_name = os.path.basename(self.cfg_list[-1])
  514. slice_value = len('._cfg')
  515. cfg_number = int(last_cfg_name[slice_value: slice_value + 4]) + 1
  516. cfg_number = str(cfg_number)
  517. else:
  518. cfg_number = '0'
  519. if len(cfg_number) < 4:
  520. cfg_number = '0' * (4 - len(cfg_number)) + cfg_number
  521. new_cfg_name = "._cfg{}_{}".format(cfg_number,
  522. os.path.basename(file_path))
  523. new_cfg_path = os.path.join(os.path.dirname(file_path), new_cfg_name)
  524. return new_cfg_path
  525. def remove_from_contents(self) -> None:
  526. '''Метод для удаления целевого файла из CONTENTS.'''
  527. if self.target_package is None:
  528. return
  529. if self.template_type == DIR:
  530. self.target_package.remove_dir(self.target_path)
  531. elif self.template_type == FILE:
  532. self.target_package.remove_obj(self.target_path)
  533. def clear_dir_contents(self) -> None:
  534. '''Метод для удаления из CONTENTS всего содержимого директории после
  535. применения append = "clear".'''
  536. if self.template_type == DIR and self.target_package is not None:
  537. self.target_package.clear_dir(self.target_path)
  538. def add_to_contents(self, file_md5: Optional[str] = None) -> None:
  539. '''Метод для добавления целевого файла в CONTENTS.'''
  540. if self.target_package is None:
  541. return
  542. # В подавляющем большинстве случаев берем хэш-сумму из выходного файла,
  543. # но если по какой-то причине выходного файла нет -- пытаемся
  544. # по целевому. Такое поведение маловероятно, но, наверное, стоит
  545. # учесть возможность такой ситуации.
  546. if os.path.exists(self.output_path):
  547. source_path = self.output_path
  548. else:
  549. source_path = self.target_path
  550. if self.parameters.append == 'link':
  551. self.target_package.add_sym(source_path,
  552. self.parameters.source)
  553. elif self.template_type == DIR:
  554. self.target_package.add_dir(source_path)
  555. elif self.template_type == FILE:
  556. self.target_package.add_obj(source_path, file_md5=file_md5)
  557. def update_contents_from_list(self, changed_list: dict) -> None:
  558. '''Метод для изменения CONTENTS по списку измененных файлов.'''
  559. print("UPDATE CONTENTS FROM LIST")
  560. if self.target_package is None:
  561. return
  562. for file_path, mode in changed_list.items():
  563. if mode in {"M", "N"}:
  564. if os.path.islink(file_path):
  565. self.target_package.add_sym(file_path)
  566. elif os.path.isdir(file_path):
  567. self.target_package.add_dir(file_path)
  568. elif os.path.isfile(file_path):
  569. self.target_package.add_obj(file_path)
  570. elif mode == "D":
  571. if os.path.islink(file_path) or os.path.isfile(file_path):
  572. self.target_package.remove_obj(file_path)
  573. elif os.path.isdir(file_path):
  574. self.target_package.add_dir(file_path)
  575. @classmethod
  576. def _set_protected(cls, chroot_path: str) -> None:
  577. '''Метод для получения множества защищенных директорий.'''
  578. cls._protected_set = set()
  579. cls._unprotected_set = set()
  580. cls._protected_set.add(join_paths(chroot_path, '/etc'))
  581. config_protect_env = os.environ.get('CONFIG_PROTECT', False)
  582. if config_protect_env:
  583. for protected_path in config_protect_env.split():
  584. protected_path = join_paths(chroot_path,
  585. protected_path.strip())
  586. cls._protected_set.add(protected_path)
  587. config_protect_mask_env = os.environ.get('CONFIG_PROTECT_MASK', False)
  588. if config_protect_mask_env:
  589. for unprotected_path in config_protect_mask_env.split():
  590. unprotected_path = join_paths(chroot_path,
  591. unprotected_path.strip())
  592. cls._unprotected_set.add(unprotected_path)
  593. cls._protected_is_set = True
  594. def save_changes(self) -> None:
  595. '''Метод для сохранения изменений внесенных в CONTENTS.'''
  596. if self.target_package:
  597. self.target_package.remove_empty_directories()
  598. self.target_package.write_contents_file()
  599. class TemplateExecutor:
  600. '''Класс исполнительного модуля.'''
  601. def __init__(self,
  602. datavars_module: Union[Datavars, Variables] = Variables(),
  603. chroot_path: str = '/',
  604. cl_config_archive: str = '/var/lib/calculate/config-archive',
  605. cl_config_path: str = '/var/lib/calculate/config',
  606. execute_archive_path: str = '/var/lib/calculate/.execute/',
  607. dbpkg: bool = True,
  608. pkg_autosave: bool = False):
  609. # TODO добавить список измененных файлов.
  610. self.datavars_module: Union[Datavars, Variables] = datavars_module
  611. self.chroot_path: str = chroot_path
  612. # Объект для проверки файловых систем. Пока не инициализируем.
  613. self.mounts: Union[Mounts, None] = None
  614. # Директория для хранения полученных при обработке exec скриптов.
  615. self.execute_archive_path: str = execute_archive_path
  616. self.execute_files: OrderedDict = OrderedDict()
  617. self.dbpkg: bool = dbpkg
  618. self._pkg_autosave: bool = pkg_autosave
  619. # Словарь с измененными файлами и статусами их изменений.
  620. self.changed_files: dict = {}
  621. # Список целевых путей измененных файлов. Нужен для корректного
  622. # формирования calculate-заголовка.
  623. self.processed_targets: list = []
  624. # TODO разобраться с этим.
  625. # Значения параметров по умолчанию, пока не используются.
  626. self.directory_default_parameters: dict =\
  627. ParametersProcessor.directory_default_parameters
  628. self.file_default_parameters: dict =\
  629. ParametersProcessor.file_default_parameters
  630. # Отображение имен действий для директорий на методы их реализующие.
  631. self.directory_appends: dict = {
  632. 'join': self._append_join_directory,
  633. 'remove': self._append_remove_directory,
  634. 'skip': self._append_skip_directory,
  635. 'clear': self._append_clear_directory,
  636. 'link': self._append_link_directory,
  637. 'replace': self._append_replace_directory}
  638. # Отображение имен действий для файлов на методы их реализующие.
  639. self.file_appends: dict = {'join': self._append_join_file,
  640. 'after': self._append_after_file,
  641. 'before': self._append_before_file,
  642. 'replace': self._append_replace_file,
  643. 'remove': self._append_remove_file,
  644. 'skip': self._append_skip_file,
  645. 'clear': self._append_clear_file,
  646. 'link': self._append_link_file}
  647. self.formats_classes: set = ParametersProcessor.available_formats
  648. self.calculate_config_file: CalculateConfigFile = CalculateConfigFile(
  649. cl_config_path=cl_config_path,
  650. cl_chroot_path=chroot_path)
  651. self.cl_config_archive_path: str = cl_config_archive
  652. Format.CALCULATE_VERSION = CALCULATE_VERSION
  653. @property
  654. def available_appends(self) -> set:
  655. '''Метод для получения множества возможных значений append.'''
  656. appends_set = set(self.directory_appends.keys()).union(
  657. set(self.file_appends.keys()))
  658. return appends_set
  659. def execute_template(self, target_path: str,
  660. parameters: ParametersContainer, template_type: int,
  661. template_path: str, template_text: str = '',
  662. save_changes: bool = True,
  663. target_package: Optional[Package] = None) -> dict:
  664. '''Метод для запуска выполнения шаблонов.'''
  665. # Словарь с данными о результате работы исполнительного метода.
  666. self.executor_output = {'target_path': None,
  667. 'stdout': None,
  668. 'stderr': None,
  669. 'warnings': []}
  670. if parameters.append == 'skip':
  671. return self.executor_output
  672. try:
  673. template_object = TemplateWrapper(
  674. target_path, parameters,
  675. template_type,
  676. template_path,
  677. template_text=template_text,
  678. target_package=target_package,
  679. chroot_path=self.chroot_path,
  680. config_archive_path=self.cl_config_archive_path,
  681. dbpkg=self.dbpkg,
  682. pkg_autosave=self._pkg_autosave)
  683. except TemplateTypeConflict as error:
  684. raise TemplateExecutorError("type conflict: {}".format(str(error)))
  685. except TemplateCollisionError as error:
  686. raise TemplateExecutorError("collision: {}".format(str(error)))
  687. # Удаляем оригинал, если это необходимо из-за наличия force или по
  688. # другим причинам.
  689. if template_object.remove_original:
  690. if template_object.target_type == DIR:
  691. self._remove_directory(template_object.target_path)
  692. else:
  693. self._remove_file(template_object.target_path)
  694. if self.dbpkg:
  695. template_object.remove_from_contents()
  696. template_object.target_type = None
  697. # Если был включен mirror, то после удаления файла завершаем
  698. # выполнение шаблона.
  699. if template_object.parameters.mirror:
  700. # if save_changes:
  701. # template_object.save_changes()
  702. return self.executor_output
  703. template_object.target_type = None
  704. if template_object.parameters.run:
  705. # Если есть параметр run -- запускаем текст шаблона.
  706. self._run_template(template_object)
  707. elif template_object.parameters.exec:
  708. # Если есть параметр exec -- запускаем текст шаблона после
  709. # обработки всех шаблонов.
  710. self._exec_template(template_object)
  711. elif template_object.parameters.append:
  712. if template_object.template_type == DIR:
  713. self.directory_appends[template_object.parameters.append](
  714. template_object)
  715. elif template_object.template_type == FILE:
  716. self.file_appends[template_object.parameters.append](
  717. template_object)
  718. # Сохраняем изменения в CONTENTS внесенные согласно шаблону.
  719. # if save_changes:
  720. # template_object.save_changes()
  721. # Возвращаем целевой путь, если он был изменен, или
  722. # None если не был изменен.
  723. if template_object.target_path_is_changed:
  724. self.executor_output['target_path'] =\
  725. template_object.target_path
  726. if template_object.ca_is_missed:
  727. self.executor_output['warnings'].append(
  728. "archive file is missed,"
  729. " target file was used instead")
  730. return self.executor_output
  731. def save_changes(self) -> None:
  732. '''Метод для сохранения чего-нибудь после выполнения всех шаблонов.'''
  733. # Пока сохраняем только получившееся содержимое config-файла.
  734. self.calculate_config_file.save_changes()
  735. def _append_join_directory(self, template_object: TemplateWrapper) -> None:
  736. '''Метод описывающий действия для append = "join", если шаблон --
  737. директория. Создает директорию, если ее нет.'''
  738. if template_object.target_type is None:
  739. self._create_directory(template_object)
  740. # Корректируем список измененных файлов.
  741. if template_object.target_path in self.changed_files:
  742. self.changed_files[template_object.target_path] = 'M'
  743. else:
  744. self.changed_files[template_object.target_path] = 'N'
  745. else:
  746. if template_object.parameters.chmod:
  747. self._chmod_directory(template_object.target_path,
  748. template_object.parameters.chmod)
  749. if template_object.parameters.chown:
  750. self._chown_directory(template_object.target_path,
  751. template_object.parameters.chown)
  752. if self.dbpkg:
  753. template_object.add_to_contents()
  754. def _append_remove_directory(self, template_object: TemplateWrapper
  755. ) -> None:
  756. '''Метод описывающий действия для append = "remove", если шаблон --
  757. директория. Удаляет директорию со всем содержимым, если она есть.'''
  758. if template_object.target_type is not None:
  759. changed = [template_object.target_path]
  760. changed.extend(get_directory_contents(template_object.target_path))
  761. self._remove_directory(template_object.target_path)
  762. # Добавляем все содержимое директории в список измененных файлов.
  763. for file_path in changed:
  764. if file_path in self.changed_files:
  765. if self.changed_files[file_path] == 'N':
  766. self.changed_files.pop(file_path)
  767. else:
  768. self.changed_files[file_path] = 'D'
  769. else:
  770. self.changed_files[file_path] = 'D'
  771. if self.dbpkg:
  772. template_object.remove_from_contents()
  773. def _append_skip_directory(self,
  774. template_object: TemplateWrapper) -> None:
  775. pass
  776. def _append_clear_directory(self,
  777. template_object: TemplateWrapper) -> None:
  778. '''Метод описывающий действия для append = "clear", если шаблон --
  779. директория. Удаляет все содержимое директории, если она есть.'''
  780. if template_object.target_type is not None:
  781. dir_contents = get_directory_contents(template_object.target_path)
  782. self._clear_directory(template_object.target_path)
  783. # Добавляем все содержимое директории в список изменных файлов.
  784. for file_path in dir_contents:
  785. if file_path in self.changed_files:
  786. if self.changed_files[file_path] == 'N':
  787. self.changed_files.pop(file_path)
  788. else:
  789. self.changed_files[file_path] = 'D'
  790. else:
  791. self.changed_files[file_path] = 'D'
  792. # Меняем права и владельца очищенной директории, если это
  793. # необходимо.
  794. if template_object.parameters.chmod:
  795. self._chmod_directory(template_object.target_path,
  796. template_object.parameters.chmod)
  797. if template_object.parameters.chown:
  798. self._chown_directory(template_object.target_path,
  799. template_object.parameters.chown)
  800. if self.dbpkg:
  801. template_object.clear_dir_contents()
  802. def _append_link_directory(self, template_object: TemplateWrapper
  803. ) -> None:
  804. '''Метод описывающий действия для append = "link", если шаблон --
  805. директория. Создает ссылку на директорию, если она есть.'''
  806. self._link_directory(template_object.parameters.source,
  807. template_object.target_path)
  808. # Корректируем список измененных файлов.
  809. if template_object.target_path in self.changed_files:
  810. self.changed_files[template_object.target_path] = 'M'
  811. else:
  812. self.changed_files[template_object.target_path] = 'N'
  813. # Меняем права и владельца файла, на который указывает ссылка.
  814. if template_object.parameters.chmod:
  815. self._chmod_directory(template_object.parameters.source,
  816. template_object.parameters.chmod)
  817. if template_object.parameters.chown:
  818. self._chown_directory(template_object.parameters.source,
  819. template_object.parameters.chown)
  820. if self.dbpkg:
  821. template_object.add_to_contents()
  822. def _append_replace_directory(self, template_object: TemplateWrapper
  823. ) -> None:
  824. '''Метод описывающий действия для append = "replace", если шаблон --
  825. директория. Очищает директорию или создает, если ее нет.'''
  826. if template_object.target_type is None:
  827. self._create_directory(template_object)
  828. if self.dbpkg:
  829. template_object.add_to_contents()
  830. # Корректируем список измененных файлов.
  831. if template_object.target_path in self.changed_files:
  832. self.changed_files[template_object.target_path] = 'M'
  833. else:
  834. self.changed_files[template_object.target_path] = 'N'
  835. else:
  836. dir_contents = get_directory_contents(template_object.target_path)
  837. self._clear_directory(template_object.target_path)
  838. # Добавляем все содержимое директории в список измененных файлов.
  839. for file_path in dir_contents:
  840. if file_path in self.changed_files:
  841. if self.changed_files[file_path] == 'N':
  842. self.changed_files.pop(file_path)
  843. else:
  844. self.changed_files[file_path] = 'D'
  845. else:
  846. self.changed_files[file_path] = 'D'
  847. if self.dbpkg:
  848. template_object.clear_dir_contents()
  849. def _append_join_file(self, template_object: TemplateWrapper,
  850. join_before: bool = False, replace: bool = False
  851. ) -> None:
  852. '''Метод описывающий действия при append = "join", если шаблон -- файл.
  853. Объединяет шаблон с целевым файлом.'''
  854. output_path = template_object.output_path
  855. input_file_md5 = None
  856. output_file_md5 = None
  857. template_format = template_object.format_class
  858. # Задаемся значениями chmod и chown в зависимости от наличия или
  859. # отсутствия файла, принадлежности его пакету и наличия значений
  860. # параметров по умолчанию.
  861. chmod = self._get_chmod(template_object)
  862. chown = self._get_chown(template_object)
  863. if template_format.EXECUTABLE or template_object.contents_matching:
  864. # Действия, если целевой файл не имеет пользовательских изменений
  865. # или если он исполнительный.
  866. # Парсим текст шаблона используя его формат.
  867. if (not template_object.template_text.strip()
  868. and template_object.parameters.source
  869. and template_object.parameters.append == "replace"
  870. and template_object.parameters.format == "raw"):
  871. # Если шаблон пуст, параметром source задан входной файл,
  872. # и при этом формат шаблона raw и append = 'replace'
  873. # значит этот шаблон предназначен для копирования.
  874. if template_object.target_type is not None:
  875. self._remove_file(template_object.target_path)
  876. output_file_md5 = self._copy_from_source(template_object,
  877. chown=chown,
  878. chmod=chmod)
  879. elif not template_object.format_class.EXECUTABLE:
  880. parsed_template = template_format(
  881. template_object.template_text,
  882. template_object.template_path,
  883. ignore_comments=True,
  884. chroot_path=self.chroot_path)
  885. # Действия для шаблонов не являющихся исполнительными.
  886. output_paths = [output_path]
  887. # Если целевой файл защищен, а шаблон не userspace.
  888. if (template_object.protected
  889. and not template_object.is_userspace):
  890. # Тогда также обновляем архив.
  891. output_paths.append(template_object.archive_path)
  892. input_text = self._get_input_text(template_object,
  893. replace=replace)
  894. if (replace and template_object.target_type is not None and
  895. os.path.exists(output_path)):
  896. self._clear_file(output_path)
  897. # Если шаблон не исполнительный разбираем входной текст.
  898. parsed_input = template_format(
  899. input_text,
  900. template_object.template_path,
  901. add_header=True,
  902. join_before=join_before,
  903. already_changed=(template_object.target_path
  904. in self.processed_targets),
  905. parameters=template_object.parameters)
  906. parsed_input.join_template(parsed_template)
  907. # Результат наложения шаблона.
  908. output_text = parsed_input.document_text
  909. # Удаляем форматный объект входного файла.
  910. # del(parsed_input)
  911. output_file_md5 = hashlib.md5(output_text.encode()).hexdigest()
  912. if input_text:
  913. input_file_md5 = hashlib.md5(
  914. input_text.encode()).hexdigest()
  915. for save_path in output_paths:
  916. if not os.path.exists(os.path.dirname(save_path)):
  917. self._create_directory(
  918. template_object,
  919. path_to_create=os.path.dirname(save_path))
  920. with open(save_path, 'w') as output_file:
  921. output_file.write(output_text)
  922. # Меняем права доступа и владельца всех сохраняемых файлов,
  923. # если это необходимо.
  924. if chown:
  925. self._chown_file(save_path, chown)
  926. if chmod:
  927. self._chmod_file(save_path, chmod)
  928. elif template_object.format_class.EXECUTABLE:
  929. parsed_template = template_format(
  930. template_object.template_text,
  931. template_object.template_path,
  932. parameters=template_object.parameters,
  933. datavars=self.datavars_module)
  934. changed_files = parsed_template.execute_format(
  935. template_object.target_path,
  936. chroot_path=self.chroot_path)
  937. # Удаляем форматный объект входного файла.
  938. # del(parsed_template)
  939. # Если исполняемый формат выдал список измененных файлов для
  940. # изменения CONTENTS и при этом задан пакет -- обновляем
  941. # CONTENTS.
  942. for file_path, status in changed_files.items():
  943. if status == 'M':
  944. if file_path in self.changed_files:
  945. if self.changed_files[file_path] == 'D':
  946. self.changed_files[file_path] == 'N'
  947. else:
  948. self.changed_files[file_path] = 'M'
  949. elif status == 'D':
  950. if file_path in self.changed_files:
  951. if self.changed_files[file_path] == 'M':
  952. self.changed_files[file_path] == 'D'
  953. if self.changed_files[file_path] == 'N':
  954. self.changed_files.pop(file_path)
  955. else:
  956. self.changed_files[file_path] = 'D'
  957. # Если в ходе работы формат выкидывал предупреждения --
  958. # добавляем их в список предупреждений.
  959. if parsed_template.warnings:
  960. self.executor_output['warnings'].extend(
  961. parsed_template.warnings)
  962. if (self.dbpkg and changed_files and
  963. template_object.target_package and
  964. template_object.format_class.FORMAT != 'contents'):
  965. template_object.update_contents_from_list(changed_files)
  966. return
  967. if input_file_md5 != output_file_md5:
  968. if template_object.target_type is None:
  969. self.changed_files[
  970. template_object.target_path] = 'N'
  971. else:
  972. self.changed_files[
  973. template_object.target_path] = 'M'
  974. if self.dbpkg:
  975. self._remove_cfg_files(template_object)
  976. if template_object.parameters.unbound:
  977. template_object.remove_from_contents()
  978. else:
  979. template_object.add_to_contents(
  980. file_md5=output_file_md5)
  981. else:
  982. input_text = self._get_input_text(template_object,
  983. replace=replace)
  984. parsed_input = template_format(
  985. input_text,
  986. template_object.template_path,
  987. add_header=True,
  988. join_before=join_before,
  989. already_changed=False,
  990. parameters=template_object.parameters)
  991. parsed_template = template_format(template_object.template_text,
  992. template_object.template_path,
  993. ignore_comments=True)
  994. parsed_input.join_template(parsed_template)
  995. # Результат наложения шаблона.
  996. output_text = parsed_input.document_text
  997. # Удаляем форматный объект входного файла.
  998. del(parsed_input)
  999. output_file_md5 = hashlib.md5(output_text.encode()).hexdigest()
  1000. if not self.calculate_config_file.compare_md5(
  1001. template_object.target_path,
  1002. output_file_md5):
  1003. if not os.path.exists(os.path.dirname(output_path)):
  1004. self._create_directory(
  1005. template_object,
  1006. path_to_create=os.path.dirname(output_path))
  1007. with open(output_path, 'w') as output_file:
  1008. output_file.write(output_text)
  1009. # Меняем права доступа и владельца ._cfg????_ файлов, если
  1010. # это необходимо.
  1011. if chown:
  1012. self._chown_file(output_path, chown)
  1013. if chmod:
  1014. self._chmod_file(output_path, chmod)
  1015. # Обновляем CL.
  1016. self.calculate_config_file.set_files_md5(
  1017. template_object.target_path,
  1018. output_file_md5)
  1019. self.changed_files[template_object.target_path] = 'C'
  1020. # Обновляем CONTENTS.
  1021. template_object.add_to_contents(file_md5=output_file_md5)
  1022. else:
  1023. # Действия если CL совпало. Hичего не делаем.
  1024. pass
  1025. def _get_input_text(self, template_object: TemplateWrapper,
  1026. replace: bool = False) -> str:
  1027. '''Метод для получения текста вх