Permalink
Browse files

Add sysctl type and provider

  • Loading branch information...
1 parent 4bdaeca commit b540657b3fee710c02e7a295b36552da9377ee8e @domcleal committed Aug 27, 2012
View
1 README.md
@@ -57,6 +57,7 @@ The module adds the following new types:
* `sshd_config` for setting configuration entries in OpenSSH's `sshd_config`
* `sshd_config_subsystem` for setting subsystem entries in OpenSSH's `sshd_config`
+ * `sysctl` for entries inside Linux's sysctl.conf
## Requirements
View
159 lib/puppet/provider/sysctl/augeas.rb
@@ -0,0 +1,159 @@
+# Alternative Augeas-based provider for sysctl type
+#
+# Copyright (c) 2012 Dominic Cleal
+# Licensed under the Apache License, Version 2.0
+
+require 'augeasproviders/provider'
+
+Puppet::Type.type(:sysctl).provide(:augeas) do
+ desc "Uses Augeas API to update sysctl settings"
+
+ def self.file(resource = nil)
+ file = "/etc/sysctl.conf"
+ file = resource[:target] if resource and resource[:target]
+ file.chomp("/")
+ end
+
+ confine :feature => :augeas
+ confine :exists => file
+
+ def self.augopen(resource = nil)
+ AugeasProviders::Provider.augopen("Sysctl.lns", file(resource))
+ end
+
+ def self.instances
+ aug = nil
+ path = "/files#{file}"
+ begin
+ resources = []
+ aug = augopen
+ aug.match("#{path}/*").each do |spath|
+ resource = {:ensure => :present}
+
+ basename = spath.split("/")[-1]
+ resource[:name] = basename.split("[")[0]
+ next unless resource[:name]
+ next if resource[:name] == "#comment"
+
+ resource[:value] = aug.get("#{spath}")
+
+ # Only match comments immediately before the entry and prefixed with
+ # the sysctl name
+ cmtnode = aug.match("#{path}/#comment[following-sibling::*[1][self::#{basename}]]")
+ unless cmtnode.empty?
+ comment = aug.get(cmtnode[0])
+ if comment.match(/#{resource[:name]}:/)
+ resource[:comment] = comment.sub(/^#{resource[:name]}:\s*/, "")
+ end
+ end
+
+ resources << new(resource)
+ end
+ resources
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def exists?
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ not aug.match("#{path}/#{resource[:name]}").empty?
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def create
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ aug.set("#{path}/#{resource[:name]}", resource[:value])
+ if resource[:comment]
+ aug.insert("#{path}/#{resource[:name]}", "#comment", true)
+ aug.set("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]]",
+ "#{resource[:name]}: #{resource[:comment]}")
+ end
+ aug.save!
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def destroy
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ aug.rm("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]")
+ aug.rm("#{path}/#{resource[:name]}")
+ aug.save!
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def target
+ self.class.file(resource)
+ end
+
+ def value
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ aug.get("#{path}/#{resource[:name]}")
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def value=(value)
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ aug.set("#{path}/#{resource[:name]}", value)
+ aug.save!
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def comment
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ comment = aug.get("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]")
+ comment.sub(/^#{resource[:name]}:\s*/, "") if comment
+ comment
+ ensure
+ aug.close if aug
+ end
+ end
+
+ def comment=(value)
+ aug = nil
+ path = "/files#{self.class.file(resource)}"
+ begin
+ aug = self.class.augopen(resource)
+ cmtnode = "#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]"
+ if value.empty?
+ aug.rm(cmtnode)
+ else
+ if aug.match(cmtnode).empty?
+ aug.insert("#{path}/#{resource[:name]}", "#comment", true)
+ end
+ aug.set("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]]",
+ "#{resource[:name]}: #{resource[:comment]}")
+ end
+ aug.save!
+ ensure
+ aug.close if aug
+ end
+ end
+end
View
28 lib/puppet/type/sysctl.rb
@@ -0,0 +1,28 @@
+# Manages entries in /etc/sysctl.conf
+#
+# Copyright (c) 2012 Dominic Cleal
+# Licensed under the Apache License, Version 2.0
+
+Puppet::Type.newtype(:sysctl) do
+ @doc = "Manages entries in /etc/sysctl.conf."
+
+ ensurable
+
+ newparam(:name) do
+ desc "The name of the setting, e.g. net.ipv4.ip_forward"
+ isnamevar
+ end
+
+ newproperty(:value) do
+ desc "Value to change the setting to. Settings with multiple values (such as net.ipv4.tcp_mem are represented as a single whitespace separated string."
+ end
+
+ newparam(:target) do
+ desc "The file in which to store the settings, defaults to
+ `/etc/sysctl.conf`."
+ end
+
+ newproperty(:comment) do
+ desc "Text to be stored in a comment immediately above the entry. It will be automatically prepended with the name of the setting in order for the provider to know whether it controls the comment or not."
+ end
+end
View
26 spec/fixtures/unit/puppet/sysctl/broken
@@ -0,0 +1,26 @@
+brokenfile
+# Kernel sysctl configuration file
+#
+# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
+# sysctl.conf(5) for more details.
+
+# Controls IP packet forwarding
+net.ipv4.ip_forward = 0
+
+# Controls source route verification
+net.ipv4.conf.default.rp_filter = 1
+
+# Do not accept source routing
+net.ipv4.conf.default.accept_source_route = 0
+
+# Controls the System Request debugging functionality of the kernel
+kernel.sysrq = 0
+
+# Controls whether core dumps will append the PID to the core filename.
+# Useful for debugging multi-threaded applications.
+kernel.core_uses_pid = 1
+
+# Disable netfilter on bridges.
+net.bridge.bridge-nf-call-ip6tables = 0
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-arptables = 0
View
0 spec/fixtures/unit/puppet/sysctl/empty
No changes.
View
26 spec/fixtures/unit/puppet/sysctl/full
@@ -0,0 +1,26 @@
+# Kernel sysctl configuration file
+#
+# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
+# sysctl.conf(5) for more details.
+
+# Controls IP packet forwarding
+net.ipv4.ip_forward = 0
+
+# Controls source route verification
+net.ipv4.conf.default.rp_filter = 1
+
+# net.ipv4.conf.default.accept_source_route: Do not accept source routing
+net.ipv4.conf.default.accept_source_route = 0
+
+# SysRq setting
+# kernel.sysrq: controls the System Request debugging functionality of the kernel
+kernel.sysrq = 0
+
+# Controls whether core dumps will append the PID to the core filename.
+# Useful for debugging multi-threaded applications.
+kernel.core_uses_pid = 1
+
+# Disable netfilter on bridges.
+net.bridge.bridge-nf-call-ip6tables = 0
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-arptables = 0
View
8 spec/fixtures/unit/puppet/sysctl/small
@@ -0,0 +1,8 @@
+# Kernel sysctl configuration file
+#
+# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
+# sysctl.conf(5) for more details.
+
+# Controls IP packet forwarding
+net.ipv4.ip_forward = 0
+
View
3 spec/lib/augeas_spec/augparse.rb
@@ -10,7 +10,8 @@ def augparse(file, lens, result = "?")
# reprocess file and remove it to make writing tests easier
File.open("#{dir}/input", "w") do |finput|
File.open(file, "r") do |ffile|
- finput.write line unless (line = ffile.readline) == "\n"
+ line = ffile.readline
+ finput.write line unless line == "\n"
ffile.each {|line| finput.write line }
end
end
View
166 spec/unit/puppet/sysctl_spec.rb
@@ -0,0 +1,166 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+provider_class = Puppet::Type.type(:sysctl).provider(:augeas)
+
+describe provider_class do
+ context "with empty file" do
+ let(:tmptarget) { aug_fixture("empty") }
+ let(:target) { tmptarget.path }
+
+ it "should create simple new entry" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "net.ipv4.ip_forward",
+ :value => "1",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ augparse(target, "Sysctl.lns", '
+ { "net.ipv4.ip_forward" = "1" }
+ ')
+ end
+
+ it "should create new entry with comment" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "net.ipv4.ip_forward",
+ :value => "1",
+ :comment => "test",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ augparse(target, "Sysctl.lns", '
+ { "#comment" = "net.ipv4.ip_forward: test" }
+ { "net.ipv4.ip_forward" = "1" }
+ ')
+ end
+ end
+
+ context "with full file" do
+ let(:tmptarget) { aug_fixture("full") }
+ let(:target) { tmptarget.path }
+
+ it "should list instances" do
+ provider_class.stubs(:file).returns(target)
+ inst = provider_class.instances.map { |p|
+ {
+ :name => p.get(:name),
+ :ensure => p.get(:ensure),
+ :value => p.get(:value),
+ :comment => p.get(:comment),
+ }
+ }
+
+ inst.size.should == 8
+ inst[0].should == {:name=>"net.ipv4.ip_forward", :ensure=>:present, :value=>"0", :comment=>:absent}
+ inst[1].should == {:name=>"net.ipv4.conf.default.rp_filter", :ensure=>:present, :value=>"1", :comment=>:absent}
+ inst[2].should == {:name=>"net.ipv4.conf.default.accept_source_route", :ensure=>:present, :value=>"0", :comment=>"Do not accept source routing"}
+ inst[3].should == {:name=>"kernel.sysrq", :ensure=>:present, :value=>"0", :comment=>"controls the System Request debugging functionality of the kernel"}
+ end
+
+ it "should delete entries" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "kernel.sysrq",
+ :ensure => "absent",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ aug_open(target, "Sysctl.lns") do |aug|
+ aug.match("kernel.sysrq").should == []
+ aug.match("#comment[. =~ regexp('kernel.sysrq:.*')]").should == []
+ end
+ end
+
+ it "should update value" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "net.ipv4.ip_forward",
+ :value => "1",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ augparse_filter(target, "Sysctl.lns", "net.ipv4.ip_forward", '
+ { "net.ipv4.ip_forward" = "1" }
+ ')
+ end
+
+ describe "when updating comment" do
+ it "should change comment" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "kernel.sysrq",
+ :comment => "enables the SysRq feature",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ aug_open(target, "Sysctl.lns") do |aug|
+ aug.match("#comment[. = 'SysRq setting']").should_not == []
+ aug.match("#comment[. = 'kernel.sysrq: enables the SysRq feature']").should_not == []
+ end
+ end
+
+ it "should remove comment" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "kernel.sysrq",
+ :comment => "",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ aug_open(target, "Sysctl.lns") do |aug|
+ aug.match("#comment[. =~ regexp('kernel.sysrq:.*')]").should == []
+ aug.match("#comment[. = 'SysRq setting']").should_not == []
+ end
+ end
+ end
+ end
+
+ context "with small file" do
+ let(:tmptarget) { aug_fixture("small") }
+ let(:target) { tmptarget.path }
+
+ describe "when updating comment" do
+ it "should add comment" do
+ apply!(Puppet::Type.type(:sysctl).new(
+ :name => "net.ipv4.ip_forward",
+ :comment => "test comment",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ augparse(target, "Sysctl.lns", '
+ { "#comment" = "Kernel sysctl configuration file" }
+ { }
+ { "#comment" = "For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and" }
+ { "#comment" = "sysctl.conf(5) for more details." }
+ { }
+ { "#comment" = "Controls IP packet forwarding" }
+ { "#comment" = "net.ipv4.ip_forward: test comment" }
+ { "net.ipv4.ip_forward" = "0" }
+ { }
+ ')
+ end
+ end
+ end
+
+ context "with broken file" do
+ let(:tmptarget) { aug_fixture("broken") }
+ let(:target) { tmptarget.path }
+
+ it "should fail to load" do
+ txn = apply(Puppet::Type.type(:sysctl).new(
+ :name => "net.ipv4.ip_forward",
+ :value => "1",
+ :target => target,
+ :provider => "augeas"
+ ))
+
+ txn.any_failed?.should_not == nil
+ @logs.first.level.should == :err
+ @logs.first.message.include?(target).should == true
+ end
+ end
+end

0 comments on commit b540657

Please sign in to comment.