Inline a custom file_line provider to fix agent.

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
This commit is contained in:
Dan Prince 2013-08-29 12:56:10 -04:00
parent bfad5a3821
commit a3b324dba2
4 changed files with 171 additions and 4 deletions

View File

@ -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

View File

@ -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

View 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'];
}

View 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]'