Module: PuppetX::FileMagic

Defined in:
lib/puppet_x/filemagic.rb

Class Method Summary collapse

Class Method Details

.append(path, regex_start, flags, data) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/puppet_x/filemagic.rb', line 284

def self.append(path, regex_start, flags, data)
  # write the new content in one go
  content = readfile(path)
  found_at = get_match_regex(content, regex_start, flags,true, data2lines(data))

  if found_at > -1
    # Discard from the end of the file all lines including and after content[found_at]
    content = content[0..found_at-1]
  end

  # perform the append
  content += data2lines(data)

  File.open(path, "w") do |f|
    f.puts(content)
  end
end

.data2lines(data) ⇒ Object



13
14
15
16
17
18
19
20
21
22
# File 'lib/puppet_x/filemagic.rb', line 13

def self.data2lines(data)
  if data.nil? || data.empty?
    lines = []
  else
    # support windows by chomping (CR)LF for comparison purposes
    lines = data.split(/\r?\n/)
  end

  lines
end

.determine_line_ending(path) ⇒ Object

read the first line of file to determine it's line endings



6
7
8
9
10
# File 'lib/puppet_x/filemagic.rb', line 6

def self.determine_line_ending(path)
  File.open(path, 'r') do |file|
    return file.readline[/\r?\n$/]
  end
end

.exists?(path, data, regex, flags, check_type, check_for_absent) ⇒ Boolean

sandwich position not implemented yet -1 TOP OF file 0 SANDWICH +1 END OF FILE

Parameters:

  • path

    File to inspect

  • data

    Lines of data to look for. If (CR)LF character found, newlines will be inserted or you can just pass an array

  • regex

    Regular expression to match or false to skip

  • position

    Where to look for a match - -1 top of file, 0 sandwich, +1 bottom of file

  • check_type

    what kind of check are we doing? :present, :absent

Returns:

  • (Boolean)


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/puppet_x/filemagic.rb', line 162

def self.exists?(path, data, regex, flags, check_type, check_for_absent)
  data_lines = data2lines(data)
  if ! check_for_absent && data_lines.size == 0
    raise "you must supply a valid string of `data` to check for"
  end

  if ! File.exist?(path)
    Puppet.err("File missing at #{path} (file must already exist to do filemagic on it...)")

    # cannot possibly exist if it doesn't exist
    exists = false
  else
    file_lines = readfile(path)
    case check_type
    when :append, :prepend
      # Check for an exact match on `data` at the beginning or end of file
      pos = if check_type == :prepend
              -1
            else
              1
            end
      all_lines_matched = (get_match_lines(file_lines, data_lines, pos) == -1)

      if ! all_lines_matched
        # if all lines didn't match there might me a regex we need to scan for
        if regex
          partial_match = (get_match_regex(file_lines, regex, flags, true, data_lines) > -1)
        end

        # Nothing found by regex.. last check - do we have a partial match on any data?
        if ! partial_match
          partial_match = (get_match_lines(file_lines, data_lines, pos) > 0)
        end
      else
        partial_match = false
      end

      if all_lines_matched
        exists = true
      elsif partial_match
        exists = check_for_absent
      else
        exists = false
      end


    when :gsub
      exists =
          if (get_match_regex(file_lines, regex, flags, true, data_lines) > -1)
            check_for_absent
          else
            ! check_for_absent
          end
    when :replace, :replace_insert
      # If we find `regex` anywhere in file we need to fire (ensure=>present --> exists=false)
      match_count = get_match_regex(file_lines, regex, flags, true, data_lines)

      if match_count == -1

        if check_for_absent
          # detect exact or partial match on data that needs to be removed
          exists = (get_match_lines(file_lines, data_lines, 0) != 0)
        elsif check_type == :replace_insert
          # detect full match against data
          exists = (get_match_lines(file_lines, data_lines, 0) == -1)
        else
          # we 'exist' because all necessary replacements have been made
          exists = true
        end
      else
        exists = check_for_absent
      end
    else
      raise "Unsupported check type #{check_type}"
    end
  end

  exists
end

.get_match_lines(file_lines, data_lines, pos) ⇒ Object

Return a count of matching lines from the file

Parameters:

  • pos

    -1 match from beginning, 0 match from anywhere, 1 match fom end

Returns:

  • -1 if all lines matched in correct order otherwise count of matching lines



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/puppet_x/filemagic.rb', line 101

def self.get_match_lines(file_lines, data_lines, pos)
  exact_lines_matched = 0

  # use a separate variable for file_lines to do exact vs partial match as we must
  # start matching a different points for each test

  if pos == 1
    # reverse match order if we are checking the end of the file
    data_lines = data_lines.reverse
    file_lines_exact = file_lines.reverse if file_lines
  elsif pos == 0 and data_lines.size > 0
    # scan through `file_lines` to find the fist line from data_lines and start there (or nowhere if its not found)
    index = file_lines.find_index(data_lines[0]) || [file_lines.size() -1, 0].max
    file_lines_exact = file_lines[index, data_lines.size]
  else
    file_lines_exact = file_lines
  end

  # check-off that each line in our data is already in the file, in the exact correct position
  # and short-circuit if file is too short to possibly match
  i = 0
  while file_lines_exact.size >= data_lines.size && i < data_lines.size
    data_line = data_lines[i]
    file_line = file_lines_exact[i]

    if file_line == data_line
      exact_lines_matched += 1
    end

    i += 1
  end

  if exact_lines_matched == data_lines.size and data_lines.size > 0
    # -1 indicates all lines matched
    lines_matched = -1
  else
    partial_matches = 0
    file_lines.each { |file_line|
      data_lines.each { |data_line|
        if file_line == data_line
          partial_matches += 1
        end
      }
    }
    lines_matched = partial_matches
  end

  lines_matched
end

.get_match_regex(lines, regex, flags, first, data_lines) ⇒ Object

return the index of the first/last match of regex in lines or -1 if no match. Be careful with if statements using this! It's very possible that 0 will be returned to indicate a match in the first element of the array which is of course false

Parameters:

  • lines

    data to search

  • regex

    regex to match

  • first

    match the first instance? otherwise match the last



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/puppet_x/filemagic.rb', line 42

def self.get_match_regex(lines, regex, flags, first, data_lines)
  found_at = -1

  if regex
    # parse the string from puppet into a real ruby regex. Flags have to be separate for this
    _regex = Regexp.new(regex, flags)
    i = 0

    if ! first
      lines = lines.reverse
      data_lines = data_lines.reverse
    end

    while found_at == -1 && i < lines.size

      if _regex.match?(lines[i])
        # ignore matches that exactly match the data to be replaced to avoid getting stuck in a
        # rewriting loop every puppet run (eg match `^(no)?compress`, data `compress`)
        # check every line from here on in for exact match against data - if we get one
        # then we didn't match...
        j = 0
        lines_matched = 0
        while found_at == -1 and j < data_lines.size
          if lines[i+j] == data_lines[j]
            lines_matched += 1
          end
          j += 1
        end

        # See if we got a 100% match after doing the full scan
        if lines_matched != data_lines.size || data_lines.size == 0
          found_at = i
        end

      end
      i += 1
    end

    if !first && found_at > -1
      # need to correct the index since we reversed the array we searched
      #
      # `V` points to the element we want in the array of `X`s
      #
      #      V = 5
      # XXXXXXX size == 7
      #
      # want
      #
      #  V=1
      # XXXXXXX
      found_at = lines.size - found_at - 1
    end
  end
  found_at
end

.gsub_match(path, regex, flags, data) ⇒ Object



370
371
372
373
374
375
376
377
378
# File 'lib/puppet_x/filemagic.rb', line 370

def self.gsub_match(path, regex, flags, data)
  # Read the file and flatten it based on the FILE's encoding (not the os...)
  content = readfile(path).join(determine_line_ending(path))
  _regex = Regexp.new(regex, flags)
  content = content.gsub(_regex, data)
  File.open(path, "w") do |f|
    f.puts(content)
  end
end

.prepend(path, regex_end, flags, data) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/puppet_x/filemagic.rb', line 242

def self.prepend(path, regex_end, flags, data)
  # read the old content into an array and prepend the required lines
  content = readfile(path)
  found_at = get_match_regex(content, regex_end, flags, false, data2lines(data))
  if found_at > -1
    # Discard from the beginning of the file all lines before and including content[found_at]
    content = content[found_at+1..content.size-1]
  end

  # insert the lines at the start of the file
  content.unshift(data2lines(data))

  # write the new content in one go
  File.open(path, "w") do |f|
    f.puts(content)
  end
end

.readfile(path) ⇒ Object

read a text file and get rid of newlines



25
26
27
28
29
30
31
32
33
# File 'lib/puppet_x/filemagic.rb', line 25

def self.readfile(path)
  if File.exists?(path)
    file_lines = File.readlines(path).each {|l| l.chomp!}
  else
    Puppet.error("FileMagic unable to find file at #{path} - make sure it exists")
    file_lines = []
  end
  file_lines
end

.remove_match(path, regex, flags) ⇒ Object



328
329
330
331
332
333
334
335
336
337
# File 'lib/puppet_x/filemagic.rb', line 328

def self.remove_match(path, regex, flags)
  content = readfile(path).reject { |line|
    _regex = Regexp.new(regex, flags)
    _regex.match?(line)
  }
  File.open(path, "w") do |f|
    f.puts(content)
  end

end

.replace_match(path, regex, flags, data, insert_if_missing) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/puppet_x/filemagic.rb', line 339

def self.replace_match(path, regex, flags, data, insert_if_missing)
  content = []
  inserted = false
  found = false
  _regex = Regexp.new(regex, flags)

  readfile(path).each { |line|

    if _regex.match?(line)
      if ! inserted
        content << data
        inserted = true
      end
    else
      if line == data
        found = true
      end
      content << line
    end
  }

  if insert_if_missing && (! (found || inserted))
    content << data
  end

  File.open(path, "w") do |f|
    f.puts(content)
  end

end

.unappend(path, regex_start, flags, data) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/puppet_x/filemagic.rb', line 302

def self.unappend(path, regex_start, flags, data)
  content = readfile(path)
  found_at = get_match_regex(content, regex_start, flags, true, data2lines(data))
  if found_at > -1
    # Delete based on regexp (match)
    #
    # Discard from the end of the file all lines including and after content[found_at]
    content = content[0..found_at-1]
  else
    # Delete based on exact match for `data`
    lines = data2lines(data)
    if lines
      data2lines(data).reverse.each { |line|
        if content[-1].strip == line.strip
          content.pop
        end
      }
    end
  end
  # write the new content in one go
  File.open(path, "w") do |f|
    f.puts(content)
  end
end

.unprepend(path, regex_end, flags, data) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/puppet_x/filemagic.rb', line 260

def self.unprepend(path, regex_end, flags, data)
  # read the old content into an array and remove the required lines
  content = readfile(path)
  found_at = get_match_regex(content, regex_end, flags,false, data2lines(data))
  if found_at > -1
    # Discard from the beginning of the file all lines before and including content[found_at]
    content = content[found_at+1..content.size-1]
  else
    # remove as many lines as we are told to
    data2lines(data).each { |line|
      # we double check that the lines read are still valid since we
      # checked with exists? to prevent possible race conditions (although
      # we could still encounter them since there's no locking)
      if content[0] == line
        content.shift
      end
    }
  end
  # write the new content in one go
  File.open(path, "w") do |f|
    f.puts(content)
  end
end