]> review.fuel-infra Code Review - puppet-modules/puppet-ceilometer.git/commitdiff
Inline a custom file_line provider to fix agent.
authorDan Prince <dprince@redhat.com>
Thu, 29 Aug 2013 16:56:10 +0000 (12:56 -0400)
committerDan Prince <dprince@redhat.com>
Tue, 3 Sep 2013 17:17:29 +0000 (13:17 -0400)
Fixes an issue in the ceilometer::agent::compute manifest
where nova.conf config values for the notification_driver
would get added to the wrong section (always to the end of
the file).

As part of the fix a custom file_line 'after' provider which
supports a new after option has been added.
This allows us to have some control over which section *new*
file lines go into. If there are any pre-existing matching
lines in the file the assumption is that they are already in
the correct section and can be edited in place.

NOTE: I've submitted a pull request to the upstream stdlib repo here
to add the new 'after' option:

  https://github.com/puppetlabs/puppetlabs-stdlib/pull/174

Once this (or something better) lands in stdlib we can update
puppet-ceilometer to use it.

Fixes LP Bug #1217867

Change-Id: Ic09f5232b322cde687d663d1ef38ef0fd12f32ff

lib/puppet/provider/file_line_after/ruby.rb [new file with mode: 0644]
lib/puppet/type/file_line_after.rb [new file with mode: 0644]
manifests/agent/compute.pp
spec/classes/ceilometer_agent_compute_spec.rb

diff --git a/lib/puppet/provider/file_line_after/ruby.rb b/lib/puppet/provider/file_line_after/ruby.rb
new file mode 100644 (file)
index 0000000..0d7f287
--- /dev/null
@@ -0,0 +1,83 @@
+Puppet::Type.type(:file_line_after).provide(:ruby) do
+  def exists?
+    lines.find do |line|
+      line.chomp == resource[:line].chomp
+    end
+  end
+
+  def create
+    if resource[:match]
+      handle_create_with_match
+    elsif resource[:after]
+      handle_create_with_after
+    else
+      append_line
+    end
+  end
+
+  def destroy
+    local_lines = lines
+    File.open(resource[:path],'w') do |fh|
+      fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join(''))
+    end
+  end
+
+  private
+  def lines
+    # If this type is ever used with very large files, we should
+    #  write this in a different way, using a temp
+    #  file; for now assuming that this type is only used on
+    #  small-ish config files that can fit into memory without
+    #  too much trouble.
+    @lines ||= File.readlines(resource[:path])
+  end
+
+  def handle_create_with_match()
+    regex = resource[:match] ? Regexp.new(resource[:match]) : nil
+    match_count = lines.select { |l| regex.match(l) }.size
+    if match_count > 1 && resource[:multiple].to_s != 'true'
+     raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'"
+    end
+    File.open(resource[:path], 'w') do |fh|
+      lines.each do |l|
+        fh.puts(regex.match(l) ? resource[:line] : l)
+      end
+
+      if (match_count == 0)
+        fh.puts(resource[:line])
+      end
+    end
+  end
+
+  def handle_create_with_after
+    regex = Regexp.new(resource[:after])
+
+    count = lines.count {|l| l.match(regex)}
+
+    case count
+    when 1 # find the line to put our line after
+      File.open(resource[:path], 'w') do |fh|
+        lines.each do |l|
+          fh.puts(l)
+          if regex.match(l) then
+            fh.puts(resource[:line])
+          end
+        end
+      end
+    when 0 # append the line to the end of the file
+      append_line
+    else
+      raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'.  One or no line must match the pattern."
+    end
+  end
+
+  ##
+  # append the line to the file.
+  #
+  # @api private
+  def append_line
+    File.open(resource[:path], 'a') do |fh|
+      fh.puts resource[:line]
+    end
+  end
+end
diff --git a/lib/puppet/type/file_line_after.rb b/lib/puppet/type/file_line_after.rb
new file mode 100644 (file)
index 0000000..d71d7c2
--- /dev/null
@@ -0,0 +1,79 @@
+Puppet::Type.newtype(:file_line_after) do
+
+  desc <<-EOT
+    Ensures that a given line is contained within a file.  The implementation
+    matches the full line, including whitespace at the beginning and end.  If
+    the line is not contained in the given file, Puppet will add the line to
+    ensure the desired state.  Multiple resources may be declared to manage
+    multiple lines in the same file.
+
+    Example:
+
+        file_line_after { 'sudo_rule':
+          path => '/etc/sudoers',
+          line => '%sudo ALL=(ALL) ALL',
+        }
+        file_line_after { 'sudo_rule_nopw':
+          path => '/etc/sudoers',
+          line => '%sudonopw ALL=(ALL) NOPASSWD: ALL',
+        }
+
+    In this example, Puppet will ensure both of the specified lines are
+    contained in the file /etc/sudoers.
+
+  EOT
+
+  ensurable do
+    defaultvalues
+    defaultto :present
+  end
+
+  newparam(:name, :namevar => true) do
+    desc 'An arbitrary name used as the identity of the resource.'
+  end
+
+  newparam(:match) do
+    desc 'An optional regular expression to run against existing lines in the file;\n' +
+        'if a match is found, we replace that line rather than adding a new line.'
+  end
+
+  newparam(:multiple) do
+    desc 'An optional value to determine if match can change multiple lines.'
+    newvalues(true, false)
+  end
+
+  newparam(:after) do
+    desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)'
+  end
+
+  newparam(:line) do
+    desc 'The line to be appended to the file located by the path parameter.'
+  end
+
+  newparam(:path) do
+    desc 'The file Puppet will ensure contains the line specified by the line parameter.'
+    validate do |value|
+      unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
+        raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'")
+      end
+    end
+  end
+
+  # Autorequire the file resource if it's being managed
+  autorequire(:file) do
+    self[:path]
+  end
+
+  validate do
+    unless self[:line] and self[:path]
+      raise(Puppet::Error, "Both line and path are required attributes")
+    end
+
+    if (self[:match])
+      unless Regexp.new(self[:match]).match(self[:line])
+        raise(Puppet::Error, "When providing a 'match' parameter, the value must be a regex that matches against the value of your 'line' parameter")
+      end
+    end
+
+  end
+end
index 9ce4dace21056630466f2ea255685df17d960754..e5cc3327223981748140ca0d91b00a91fac094fa 100644 (file)
@@ -102,22 +102,27 @@ class ceilometer::agent::compute (
     'DEFAULT/instance_usage_audit_period' : value => 'hour';
   }
 
+  #NOTE(dprince): This is using a custom (inline) file_line provider
+  # until this lands upstream:
+  # https://github.com/puppetlabs/puppetlabs-stdlib/pull/174
   Nova_config<| |> {
-    before +> File_line[
+    before +> File_line_after[
       'nova-notification-driver-common',
       'nova-notification-driver-ceilometer'
     ],
   }
 
-  file_line {
+  file_line_after {
     'nova-notification-driver-common':
       line   =>
         'notification_driver=nova.openstack.common.notifier.rpc_notifier',
       path   => '/etc/nova/nova.conf',
+      after  => '\[DEFAULT\]',
       notify => Service['nova-compute'];
     'nova-notification-driver-ceilometer':
       line   => 'notification_driver=ceilometer.compute.nova_notifier',
       path   => '/etc/nova/nova.conf',
+      after  => '\[DEFAULT\]',
       notify => Service['nova-compute'];
   }
 
index c09b479810e2adf86f12e454f09ff1c4a1f87e35..a6092fab6cc71d5ab0d49f34066771c0e7ea8636 100644 (file)
@@ -74,12 +74,12 @@ describe 'ceilometer::agent::compute' do
     end
 
     it 'configures nova notification driver' do
-      should contain_file_line('nova-notification-driver-common').with(
+      should contain_file_line_after('nova-notification-driver-common').with(
         :line   => 'notification_driver=nova.openstack.common.notifier.rpc_notifier',
         :path   => '/etc/nova/nova.conf',
         :notify => 'Service[nova-compute]'
       )
-      should contain_file_line('nova-notification-driver-ceilometer').with(
+      should contain_file_line_after('nova-notification-driver-ceilometer').with(
         :line   => 'notification_driver=ceilometer.compute.nova_notifier',
         :path   => '/etc/nova/nova.conf',
         :notify => 'Service[nova-compute]'