DevStack supporting tools in python
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.

dsconf.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # Copyright 2017 IBM
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. # Implementation of ini add / remove for devstack. We don't use the
  15. # python ConfigFile parser because that ends up rewriting the entire
  16. # file and doesn't ensure comments remain.
  17. import os.path
  18. import re
  19. import shutil
  20. import tempfile
  21. class IniFile(object):
  22. """Class for manipulating ini files in place."""
  23. def __init__(self, fname):
  24. self.fname = fname
  25. def has(self, section, name):
  26. """Returns True if section has a key that is name"""
  27. current_section = ""
  28. if not os.path.exists(self.fname):
  29. return False
  30. with open(self.fname, "r+") as reader:
  31. for line in reader.readlines():
  32. m = re.match("\[([^\[\]]+)\]", line)
  33. if m:
  34. current_section = m.group(1)
  35. if current_section == section:
  36. if re.match("%s\s*\=" % name, line):
  37. return True
  38. return False
  39. def add(self, section, name, value):
  40. """add a key / value to an ini file in a section.
  41. The new key value will be added at the beginning of the
  42. section, if no section is found a new section and key value
  43. will be added to the end of the file.
  44. """
  45. temp = tempfile.NamedTemporaryFile(mode='r')
  46. if os.path.exists(self.fname):
  47. shutil.copyfile(self.fname, temp.name)
  48. else:
  49. with open(temp.name, "w+"):
  50. pass
  51. found = False
  52. with open(self.fname, "w+") as writer:
  53. with open(temp.name) as reader:
  54. for line in reader.readlines():
  55. writer.write(line)
  56. m = re.match("\[([^\[\]]+)\]", line)
  57. if m and m.group(1) == section:
  58. found = True
  59. writer.write("%s = %s\n" % (name, value))
  60. if not found:
  61. writer.write("[%s]\n" % section)
  62. writer.write("%s = %s\n" % (name, value))
  63. def _at_existing_key(self, section, name, func, match="%s\s*\="):
  64. """Run a function at a found key.
  65. NOTE(sdague): if the file isn't found, we end up
  66. exploding. This seems like the right behavior in nearly all
  67. circumstances.
  68. """
  69. temp = tempfile.NamedTemporaryFile(mode='r')
  70. shutil.copyfile(self.fname, temp.name)
  71. current_section = ""
  72. with open(temp.name) as reader:
  73. with open(self.fname, "w+") as writer:
  74. for line in reader.readlines():
  75. m = re.match("\[([^\[\]]+)\]", line)
  76. if m:
  77. current_section = m.group(1)
  78. if current_section == section:
  79. if re.match(match % name, line):
  80. # run function with writer and found line
  81. func(writer, line)
  82. else:
  83. writer.write(line)
  84. else:
  85. writer.write(line)
  86. def remove(self, section, name):
  87. """remove a key / value from an ini file in a section."""
  88. def _do_remove(writer, line):
  89. pass
  90. self._at_existing_key(section, name, _do_remove)
  91. def comment(self, section, name):
  92. def _do_comment(writer, line):
  93. writer.write("# %s" % line)
  94. self._at_existing_key(section, name, _do_comment)
  95. def uncomment(self, section, name):
  96. def _do_uncomment(writer, line):
  97. writer.write(re.sub("^#\s*", "", line))
  98. self._at_existing_key(section, name, _do_uncomment,
  99. match="#\s*%s\s*\=")
  100. def set(self, section, name, value):
  101. def _do_set(writer, line):
  102. writer.write("%s = %s\n" % (name, value))
  103. if self.has(section, name):
  104. self._at_existing_key(section, name, _do_set)
  105. else:
  106. self.add(section, name, value)
  107. class LocalConf(object):
  108. """Class for manipulating local.conf files in place."""
  109. def __init__(self, fname):
  110. self.fname = fname
  111. def _conf(self, group, conf):
  112. current_section = ""
  113. for line in self._section(group, conf):
  114. m = re.match("\[([^\[\]]+)\]", line)
  115. if m:
  116. current_section = m.group(1)
  117. continue
  118. else:
  119. m2 = re.match(r"(\w+)\s*\=\s*(.+)", line)
  120. if m2:
  121. yield current_section, m2.group(1), m2.group(2)
  122. def groups(self):
  123. """Return a list of all groups in the local.conf"""
  124. groups = []
  125. with open(self.fname) as reader:
  126. for line in reader.readlines():
  127. m = re.match(r"\[\[([^\[\]]+)\|([^\[\]]+)\]\]", line)
  128. if m:
  129. group = (m.group(1), m.group(2))
  130. groups.append(group)
  131. return groups
  132. def _section(self, group, conf):
  133. """Yield all the lines out of a meta section."""
  134. in_section = False
  135. with open(self.fname) as reader:
  136. for line in reader.readlines():
  137. if re.match(r"\[\[%s\|%s\]\]" % (
  138. re.escape(group),
  139. re.escape(conf)),
  140. line):
  141. in_section = True
  142. continue
  143. # any other meta section means we aren't in the
  144. # section we want to be.
  145. elif re.match("\[\[.*\|.*\]\]", line):
  146. in_section = False
  147. continue
  148. if in_section:
  149. yield line
  150. def _has_local_section(self):
  151. for group in self.groups():
  152. if group == ("local", "localrc"):
  153. return True
  154. return False
  155. def extract(self, group, conf, target):
  156. ini_file = IniFile(target)
  157. for section, name, value in self._conf(group, conf):
  158. ini_file.set(section, name, value)
  159. def extract_localrc(self, target):
  160. with open(target, "a+") as f:
  161. for line in self._section("local", "localrc"):
  162. f.write(line)
  163. def _at_insert_point_local(self, name, func):
  164. """Run function when we are at the right insertion point in file.
  165. This lets us process an arbitrary file and insert content at
  166. the correct point. It has a few different state flags that we
  167. are looking for.
  168. Does this file have a local section at all? If not, we need to
  169. write one early in the file (this means we work with an empty
  170. file, as well as a file that has only post-config sections.
  171. Are we currently in a local section, if so, we need to write
  172. out content to the end, because items added to local always
  173. have to be added at the end.
  174. Did we write out the work that we expected? If so, just blast
  175. all lines to the end of the file.
  176. """
  177. temp = tempfile.NamedTemporaryFile(mode='r')
  178. shutil.copyfile(self.fname, temp.name)
  179. in_local = False
  180. has_local = self._has_local_section()
  181. done = False
  182. with open(self.fname, "w+") as writer:
  183. with open(temp.name) as reader:
  184. for line in reader.readlines():
  185. if done:
  186. writer.write(line)
  187. continue
  188. if re.match(re.escape("[[local|localrc]]"), line):
  189. in_local = True
  190. elif in_local and re.match(re.escape("[["), line):
  191. func(writer, None)
  192. done = True
  193. in_local = False
  194. elif not has_local and re.match(re.escape("[["), line):
  195. writer.write("[[local|localrc]]\n")
  196. func(writer, None)
  197. done = True
  198. in_local = False
  199. has_local = True
  200. # otherwise, just write what we found
  201. writer.write(line)
  202. if not done:
  203. func(writer, None)
  204. def set_local(self, line):
  205. if not os.path.exists(self.fname):
  206. with open(self.fname, "w+") as writer:
  207. writer.write("[[local|localrc]]\n")
  208. writer.write("%s\n" % line.rstrip())
  209. return
  210. def _do_set(writer, no_line):
  211. writer.write("%s\n" % line.rstrip())
  212. self._at_insert_point_local(line, _do_set)
  213. def _at_insert_point(self, group, conf, section, name, func):
  214. temp = tempfile.NamedTemporaryFile(mode='r')
  215. shutil.copyfile(self.fname, temp.name)
  216. in_meta = False
  217. in_section = False
  218. done = False
  219. with open(self.fname, "w+") as writer:
  220. with open(temp.name) as reader:
  221. for line in reader.readlines():
  222. if done:
  223. writer.write(line)
  224. continue
  225. if re.match(re.escape("[[%s|%s]]" % (group, conf)), line):
  226. in_meta = True
  227. writer.write(line)
  228. elif re.match("\[\[.*\|.*\]\]", line):
  229. # if we're not done yet, we
  230. if in_meta:
  231. if not in_section:
  232. # if we've not found the section yet,
  233. # write out section as well.
  234. writer.write("[%s]\n" % section)
  235. func(writer, None)
  236. done = True
  237. writer.write(line)
  238. in_meta = False
  239. in_section = False
  240. elif re.match(re.escape("[%s]" % section), line):
  241. # we found a relevant section
  242. writer.write(line)
  243. in_section = True
  244. elif re.match("\[[^\[\]]+\]", line):
  245. if in_meta and in_section:
  246. # We've ended our section, in our meta,
  247. # never found the key. Time to add it.
  248. func(writer, None)
  249. done = True
  250. in_section = False
  251. writer.write(line)
  252. elif (in_meta and in_section and
  253. re.match("\s*%s\s*\=" % re.escape(name), line)):
  254. # we found our match point
  255. func(writer, line)
  256. done = True
  257. else:
  258. # write out whatever we find
  259. writer.write(line)
  260. if not done:
  261. # we never found meta with a relevant section
  262. writer.write("[[%s|%s]]\n" % (group, conf))
  263. writer.write("[%s]\n" % (section))
  264. func(writer, None)
  265. def set(self, group, conf, section, name, value):
  266. if not os.path.exists(self.fname):
  267. with open(self.fname, "w+") as writer:
  268. writer.write("[[%s|%s]]\n" % (group, conf))
  269. writer.write("[%s]\n" % section)
  270. writer.write("%s = %s\n" % (name, value))
  271. return
  272. def _do_set(writer, line):
  273. writer.write("%s = %s\n" % (name, value))
  274. self._at_insert_point(group, conf, section, name, _do_set)
  275. def merge_lc(self, lcfile):
  276. lc = LocalConf(lcfile)
  277. groups = lc.groups()
  278. for group, conf in groups:
  279. if group == "local":
  280. for line in lc._section(group, conf):
  281. self.set_local(line)
  282. else:
  283. for section, name, value in lc._conf(group, conf):
  284. self.set(group, conf, section, name, value)