concatfragments.rb 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env ruby
  2. # Script to concat files to a config file.
  3. #
  4. # Given a directory like this:
  5. # /path/to/conf.d
  6. # |-- fragments
  7. # | |-- 00_named.conf
  8. # | |-- 10_domain.net
  9. # | `-- zz_footer
  10. #
  11. # The script supports a test option that will build the concat file to a temp location and
  12. # use /usr/bin/cmp to verify if it should be run or not. This would result in the concat happening
  13. # twice on each run but gives you the option to have an unless option in your execs to inhibit rebuilds.
  14. #
  15. # Without the test option and the unless combo your services that depend on the final file would end up
  16. # restarting on each run, or in other manifest models some changes might get missed.
  17. #
  18. # OPTIONS:
  19. # -o The file to create from the sources
  20. # -d The directory where the fragments are kept
  21. # -t Test to find out if a build is needed, basically concats the files to a temp
  22. # location and compare with what's in the final location, return codes are designed
  23. # for use with unless on an exec resource
  24. # -w Add a shell style comment at the top of the created file to warn users that it
  25. # is generated by puppet
  26. # -f Enables the creation of empty output files when no fragments are found
  27. # -n Sort the output numerically rather than the default alpha sort
  28. #
  29. # the command:
  30. #
  31. # concatfragments.rb -o /path/to/conffile.cfg -d /path/to/conf.d
  32. #
  33. # creates /path/to/conf.d/fragments.concat and copies the resulting
  34. # file to /path/to/conffile.cfg. The files will be sorted alphabetically
  35. # pass the -n switch to sort numerically.
  36. #
  37. # The script does error checking on the various dirs and files to make
  38. # sure things don't fail.
  39. require 'optparse'
  40. require 'fileutils'
  41. settings = {
  42. :outfile => "",
  43. :workdir => "",
  44. :test => false,
  45. :force => false,
  46. :warn => "",
  47. :sortarg => "",
  48. :newline => false
  49. }
  50. OptionParser.new do |opts|
  51. opts.banner = "Usage: #{$0} [options]"
  52. opts.separator "Specific options:"
  53. opts.on("-o", "--outfile OUTFILE", String, "The file to create from the sources") do |o|
  54. settings[:outfile] = o
  55. end
  56. opts.on("-d", "--workdir WORKDIR", String, "The directory where the fragments are kept") do |d|
  57. settings[:workdir] = d
  58. end
  59. opts.on("-t", "--test", "Test to find out if a build is needed") do
  60. settings[:test] = true
  61. end
  62. opts.separator "Other options:"
  63. opts.on("-w", "--warn WARNMSG", String,
  64. "Add a shell style comment at the top of the created file to warn users that it is generated by puppet") do |w|
  65. settings[:warn] = w
  66. end
  67. opts.on("-f", "--force", "Enables the creation of empty output files when no fragments are found") do
  68. settings[:force] = true
  69. end
  70. opts.on("-n", "--sort", "Sort the output numerically rather than the default alpha sort") do
  71. settings[:sortarg] = "-n"
  72. end
  73. opts.on("-l", "--line", "Append a newline") do
  74. settings[:newline] = true
  75. end
  76. end.parse!
  77. # do we have -o?
  78. raise 'Please specify an output file with -o' unless !settings[:outfile].empty?
  79. # do we have -d?
  80. raise 'Please specify fragments directory with -d' unless !settings[:workdir].empty?
  81. # can we write to -o?
  82. if File.file?(settings[:outfile])
  83. if !File.writable?(settings[:outfile])
  84. raise "Cannot write to #{settings[:outfile]}"
  85. end
  86. else
  87. if !File.writable?(File.dirname(settings[:outfile]))
  88. raise "Cannot write to dirname #{File.dirname(settings[:outfile])} to create #{settings[:outfile]}"
  89. end
  90. end
  91. # do we have a fragments subdir inside the work dir?
  92. if !File.directory?(File.join(settings[:workdir], "fragments")) && !File.executable?(File.join(settings[:workdir], "fragments"))
  93. raise "Cannot access the fragments directory"
  94. end
  95. # are there actually any fragments?
  96. if (Dir.entries(File.join(settings[:workdir], "fragments")) - %w{ . .. }).empty?
  97. if !settings[:force]
  98. raise "The fragments directory is empty, cowardly refusing to make empty config files"
  99. end
  100. end
  101. Dir.chdir(settings[:workdir])
  102. if settings[:warn].empty?
  103. File.open("fragments.concat", 'w') { |f| f.write("") }
  104. else
  105. File.open("fragments.concat", 'w') { |f| f.write("#{settings[:warn]}\n") }
  106. end
  107. # find all the files in the fragments directory, sort them numerically and concat to fragments.concat in the working dir
  108. open('fragments.concat', 'a') do |f|
  109. fragments = Dir.entries("fragments").sort
  110. if settings[:sortarg] == '-n'
  111. fragments = fragments.sort_by { |v| v.split('_').map(&:to_i) }
  112. end
  113. fragments.each { |entry|
  114. if File.file?(File.join("fragments", entry))
  115. f << File.read(File.join("fragments", entry))
  116. # append a newline if we were asked to (invoked with -l)
  117. if settings[:newline]
  118. f << "\n"
  119. end
  120. end
  121. }
  122. end
  123. if !settings[:test]
  124. # This is a real run, copy the file to outfile
  125. FileUtils.cp 'fragments.concat', settings[:outfile]
  126. else
  127. # Just compare the result to outfile to help the exec decide
  128. if FileUtils.cmp 'fragments.concat', settings[:outfile]
  129. exit 0
  130. else
  131. exit 1
  132. end
  133. end