PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/suite/bootstrap/lib/puppet/provider/augeas/augeas.rb

https://github.com/ody/puppetlabs-bootstrap
Ruby | 363 lines | 289 code | 32 blank | 42 comment | 42 complexity | e03131e87bb689908f5246347913f66d MD5 | raw file
  1. #--
  2. # Copyright (C) 2008 Red Hat Inc.
  3. #
  4. # This library is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public
  6. # License as published by the Free Software Foundation; either
  7. # version 2 of the License, or (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public
  15. # License along with this library; if not, write to the Free Software
  16. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. #
  18. # Author: Bryan Kearney <bkearney@redhat.com>
  19. require 'augeas' if Puppet.features.augeas?
  20. require 'strscan'
  21. Puppet::Type.type(:augeas).provide(:augeas) do
  22. include Puppet::Util
  23. confine :true => Puppet.features.augeas?
  24. has_features :parse_commands, :need_to_run?,:execute_changes
  25. SAVE_NOOP = "noop"
  26. SAVE_OVERWRITE = "overwrite"
  27. COMMANDS = {
  28. "set" => [ :path, :string ],
  29. "rm" => [ :path ],
  30. "clear" => [ :path ],
  31. "insert" => [ :string, :string, :path ],
  32. "get" => [ :path, :comparator, :string ],
  33. "match" => [ :path, :glob ],
  34. "size" => [:comparator, :int],
  35. "include" => [:string],
  36. "not_include" => [:string],
  37. "==" => [:glob],
  38. "!=" => [:glob]
  39. }
  40. COMMANDS["ins"] = COMMANDS["insert"]
  41. COMMANDS["remove"] = COMMANDS["rm"]
  42. attr_accessor :aug
  43. # Extracts an 2 dimensional array of commands which are in the
  44. # form of command path value.
  45. # The input can be
  46. # - A string with one command
  47. # - A string with many commands per line
  48. # - An array of strings.
  49. def parse_commands(data)
  50. context = resource[:context]
  51. # Add a trailing / if it is not there
  52. if (context.length > 0)
  53. context << "/" if context[-1, 1] != "/"
  54. end
  55. if data.is_a?(String)
  56. data = data.split($/)
  57. end
  58. args = []
  59. data.each do |line|
  60. line.strip!
  61. next if line.nil? || line.empty?
  62. argline = []
  63. sc = StringScanner.new(line)
  64. cmd = sc.scan(/\w+|==|!=/)
  65. formals = COMMANDS[cmd]
  66. fail("Unknown command #{cmd}") unless formals
  67. argline << cmd
  68. narg = 0
  69. formals.each do |f|
  70. sc.skip(/\s+/)
  71. narg += 1
  72. if f == :path
  73. start = sc.pos
  74. nbracket = 0
  75. inSingleTick = false
  76. inDoubleTick = false
  77. begin
  78. sc.skip(/([^\]\[\s\\'"]|\\.)+/)
  79. ch = sc.getch
  80. nbracket += 1 if ch == "["
  81. nbracket -= 1 if ch == "]"
  82. inSingleTick = !inSingleTick if ch == "'"
  83. inDoubleTick = !inDoubleTick if ch == "\""
  84. fail("unmatched [") if nbracket < 0
  85. end until ((nbracket == 0 && !inSingleTick && !inDoubleTick && (ch =~ /\s/)) || sc.eos?)
  86. len = sc.pos - start
  87. len -= 1 unless sc.eos?
  88. unless p = sc.string[start, len]
  89. fail("missing path argument #{narg} for #{cmd}")
  90. end
  91. # Rip off any ticks if they are there.
  92. p = p[1, (p.size - 2)] if p[0,1] == "'" || p[0,1] == "\""
  93. p.chomp!("/")
  94. if p[0,1] != "$" && p[0,1] != "/"
  95. argline << context + p
  96. else
  97. argline << p
  98. end
  99. elsif f == :string
  100. delim = sc.peek(1)
  101. if delim == "'" || delim == "\""
  102. sc.getch
  103. argline << sc.scan(/([^\\#{delim}]|(\\.))*/)
  104. sc.getch
  105. else
  106. argline << sc.scan(/[^\s]+/)
  107. end
  108. unless argline[-1]
  109. fail("missing string argument #{narg} for #{cmd}")
  110. end
  111. elsif f == :comparator
  112. argline << sc.scan(/(==|!=|=~|<|<=|>|>=)/)
  113. unless argline[-1]
  114. puts sc.rest
  115. fail("invalid comparator for command #{cmd}")
  116. end
  117. elsif f == :int
  118. argline << sc.scan(/\d+/).to_i
  119. elsif f== :glob
  120. argline << sc.rest
  121. end
  122. end
  123. args << argline
  124. end
  125. return args
  126. end
  127. def open_augeas
  128. unless @aug
  129. flags = Augeas::NONE
  130. flags = Augeas::TYPE_CHECK if resource[:type_check] == :true
  131. root = resource[:root]
  132. load_path = resource[:load_path]
  133. debug("Opening augeas with root #{root}, lens path #{load_path}, flags #{flags}")
  134. @aug = Augeas::open(root, load_path,flags)
  135. if get_augeas_version >= "0.3.6"
  136. debug("Augeas version #{get_augeas_version} is installed")
  137. end
  138. end
  139. @aug
  140. end
  141. def close_augeas
  142. if @aug
  143. @aug.close
  144. debug("Closed the augeas connection")
  145. @aug = nil
  146. end
  147. end
  148. # Used by the need_to_run? method to process get filters. Returns
  149. # true if there is a match, false if otherwise
  150. # Assumes a syntax of get /files/path [COMPARATOR] value
  151. def process_get(cmd_array)
  152. return_value = false
  153. #validate and tear apart the command
  154. fail ("Invalid command: #{cmd_array.join(" ")}") if cmd_array.length < 4
  155. cmd = cmd_array.shift
  156. path = cmd_array.shift
  157. comparator = cmd_array.shift
  158. arg = cmd_array.join(" ")
  159. #check the value in augeas
  160. result = @aug.get(path) || ''
  161. case comparator
  162. when "!="
  163. return_value = (result != arg)
  164. when "=~"
  165. regex = Regexp.new(arg)
  166. return_value = (result =~ regex)
  167. else
  168. return_value = (result.send(comparator, arg))
  169. end
  170. return !!return_value
  171. end
  172. # Used by the need_to_run? method to process match filters. Returns
  173. # true if there is a match, false if otherwise
  174. def process_match(cmd_array)
  175. return_value = false
  176. #validate and tear apart the command
  177. fail("Invalid command: #{cmd_array.join(" ")}") if cmd_array.length < 3
  178. cmd = cmd_array.shift
  179. path = cmd_array.shift
  180. # Need to break apart the clause
  181. clause_array = parse_commands(cmd_array.shift)[0]
  182. verb = clause_array.shift
  183. #Get the values from augeas
  184. result = @aug.match(path) || []
  185. fail("Error trying to match path '#{path}'") if (result == -1)
  186. # Now do the work
  187. case verb
  188. when "size"
  189. fail("Invalid command: #{cmd_array.join(" ")}") if clause_array.length != 2
  190. comparator = clause_array.shift
  191. arg = clause_array.shift
  192. return_value = (result.size.send(comparator, arg))
  193. when "include"
  194. arg = clause_array.shift
  195. return_value = result.include?(arg)
  196. when "not_include"
  197. arg = clause_array.shift
  198. return_value = !result.include?(arg)
  199. when "=="
  200. begin
  201. arg = clause_array.shift
  202. new_array = eval arg
  203. return_value = (result == new_array)
  204. rescue
  205. fail("Invalid array in command: #{cmd_array.join(" ")}")
  206. end
  207. when "!="
  208. begin
  209. arg = clause_array.shift
  210. new_array = eval arg
  211. return_value = (result != new_array)
  212. rescue
  213. fail("Invalid array in command: #{cmd_array.join(" ")}")
  214. end
  215. end
  216. return !!return_value
  217. end
  218. def get_augeas_version
  219. return @aug.get("/augeas/version") || ""
  220. end
  221. def set_augeas_save_mode(mode)
  222. return @aug.set("/augeas/save", mode)
  223. end
  224. def files_changed?
  225. saved_files = @aug.match("/augeas/events/saved")
  226. return saved_files.size > 0
  227. end
  228. # Determines if augeas acutally needs to run.
  229. def need_to_run?
  230. force = resource[:force]
  231. return_value = true
  232. begin
  233. open_augeas
  234. filter = resource[:onlyif]
  235. unless filter == ""
  236. cmd_array = parse_commands(filter)[0]
  237. command = cmd_array[0];
  238. begin
  239. case command
  240. when "get"; return_value = process_get(cmd_array)
  241. when "match"; return_value = process_match(cmd_array)
  242. end
  243. rescue SystemExit,NoMemoryError
  244. raise
  245. rescue Exception => e
  246. fail("Error sending command '#{command}' with params #{cmd_array[1..-1].inspect}/#{e.message}")
  247. end
  248. end
  249. unless force
  250. # If we have a verison of augeas which is at least 0.3.6 then we
  251. # can make the changes now, see if changes were made, and
  252. # actually do the save.
  253. if return_value and get_augeas_version >= "0.3.6"
  254. debug("Will attempt to save and only run if files changed")
  255. set_augeas_save_mode(SAVE_NOOP)
  256. do_execute_changes
  257. save_result = @aug.save
  258. saved_files = @aug.match("/augeas/events/saved")
  259. if save_result and not files_changed?
  260. debug("Skipping because no files were changed")
  261. return_value = false
  262. else
  263. debug("Files changed, should execute")
  264. end
  265. end
  266. end
  267. ensure
  268. close_augeas
  269. end
  270. return return_value
  271. end
  272. def execute_changes
  273. # Re-connect to augeas, and re-execute the changes
  274. begin
  275. open_augeas
  276. if get_augeas_version >= "0.3.6"
  277. set_augeas_save_mode(SAVE_OVERWRITE)
  278. end
  279. do_execute_changes
  280. success = @aug.save
  281. if success != true
  282. fail("Save failed with return code #{success}")
  283. end
  284. ensure
  285. close_augeas
  286. end
  287. return :executed
  288. end
  289. # Actually execute the augeas changes.
  290. def do_execute_changes
  291. commands = parse_commands(resource[:changes])
  292. commands.each do |cmd_array|
  293. fail("invalid command #{cmd_array.join[" "]}") if cmd_array.length < 2
  294. command = cmd_array[0]
  295. cmd_array.shift
  296. begin
  297. case command
  298. when "set"
  299. debug("sending command '#{command}' with params #{cmd_array.inspect}")
  300. rv = aug.set(cmd_array[0], cmd_array[1])
  301. fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (!rv)
  302. when "rm", "remove"
  303. debug("sending command '#{command}' with params #{cmd_array.inspect}")
  304. rv = aug.rm(cmd_array[0])
  305. fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (rv == -1)
  306. when "clear"
  307. debug("sending command '#{command}' with params #{cmd_array.inspect}")
  308. rv = aug.clear(cmd_array[0])
  309. fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (!rv)
  310. when "insert", "ins"
  311. label = cmd_array[0]
  312. where = cmd_array[1]
  313. path = cmd_array[2]
  314. case where
  315. when "before"; before = true
  316. when "after"; before = false
  317. else fail("Invalid value '#{where}' for where param")
  318. end
  319. debug("sending command '#{command}' with params #{[label, where, path].inspect}")
  320. rv = aug.insert(path, label, before)
  321. fail("Error sending command '#{command}' with params #{cmd_array.inspect}") if (rv == -1)
  322. else fail("Command '#{command}' is not supported")
  323. end
  324. rescue SystemExit,NoMemoryError
  325. raise
  326. rescue Exception => e
  327. fail("Error sending command '#{command}' with params #{cmd_array.inspect}/#{e.message}")
  328. end
  329. end
  330. end
  331. end