Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add sysctl type and provider

  • Loading branch information...
commit b540657b3fee710c02e7a295b36552da9377ee8e 1 parent 4bdaeca
Dominic Cleal authored
1  README.md
Source Rendered
@@ -57,6 +57,7 @@ The module adds the following new types:
57 57
58 58 * `sshd_config` for setting configuration entries in OpenSSH's `sshd_config`
59 59 * `sshd_config_subsystem` for setting subsystem entries in OpenSSH's `sshd_config`
  60 + * `sysctl` for entries inside Linux's sysctl.conf
60 61
61 62 ## Requirements
62 63
159 lib/puppet/provider/sysctl/augeas.rb
... ... @@ -0,0 +1,159 @@
  1 +# Alternative Augeas-based provider for sysctl type
  2 +#
  3 +# Copyright (c) 2012 Dominic Cleal
  4 +# Licensed under the Apache License, Version 2.0
  5 +
  6 +require 'augeasproviders/provider'
  7 +
  8 +Puppet::Type.type(:sysctl).provide(:augeas) do
  9 + desc "Uses Augeas API to update sysctl settings"
  10 +
  11 + def self.file(resource = nil)
  12 + file = "/etc/sysctl.conf"
  13 + file = resource[:target] if resource and resource[:target]
  14 + file.chomp("/")
  15 + end
  16 +
  17 + confine :feature => :augeas
  18 + confine :exists => file
  19 +
  20 + def self.augopen(resource = nil)
  21 + AugeasProviders::Provider.augopen("Sysctl.lns", file(resource))
  22 + end
  23 +
  24 + def self.instances
  25 + aug = nil
  26 + path = "/files#{file}"
  27 + begin
  28 + resources = []
  29 + aug = augopen
  30 + aug.match("#{path}/*").each do |spath|
  31 + resource = {:ensure => :present}
  32 +
  33 + basename = spath.split("/")[-1]
  34 + resource[:name] = basename.split("[")[0]
  35 + next unless resource[:name]
  36 + next if resource[:name] == "#comment"
  37 +
  38 + resource[:value] = aug.get("#{spath}")
  39 +
  40 + # Only match comments immediately before the entry and prefixed with
  41 + # the sysctl name
  42 + cmtnode = aug.match("#{path}/#comment[following-sibling::*[1][self::#{basename}]]")
  43 + unless cmtnode.empty?
  44 + comment = aug.get(cmtnode[0])
  45 + if comment.match(/#{resource[:name]}:/)
  46 + resource[:comment] = comment.sub(/^#{resource[:name]}:\s*/, "")
  47 + end
  48 + end
  49 +
  50 + resources << new(resource)
  51 + end
  52 + resources
  53 + ensure
  54 + aug.close if aug
  55 + end
  56 + end
  57 +
  58 + def exists?
  59 + aug = nil
  60 + path = "/files#{self.class.file(resource)}"
  61 + begin
  62 + aug = self.class.augopen(resource)
  63 + not aug.match("#{path}/#{resource[:name]}").empty?
  64 + ensure
  65 + aug.close if aug
  66 + end
  67 + end
  68 +
  69 + def create
  70 + aug = nil
  71 + path = "/files#{self.class.file(resource)}"
  72 + begin
  73 + aug = self.class.augopen(resource)
  74 + aug.set("#{path}/#{resource[:name]}", resource[:value])
  75 + if resource[:comment]
  76 + aug.insert("#{path}/#{resource[:name]}", "#comment", true)
  77 + aug.set("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]]",
  78 + "#{resource[:name]}: #{resource[:comment]}")
  79 + end
  80 + aug.save!
  81 + ensure
  82 + aug.close if aug
  83 + end
  84 + end
  85 +
  86 + def destroy
  87 + aug = nil
  88 + path = "/files#{self.class.file(resource)}"
  89 + begin
  90 + aug = self.class.augopen(resource)
  91 + aug.rm("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]")
  92 + aug.rm("#{path}/#{resource[:name]}")
  93 + aug.save!
  94 + ensure
  95 + aug.close if aug
  96 + end
  97 + end
  98 +
  99 + def target
  100 + self.class.file(resource)
  101 + end
  102 +
  103 + def value
  104 + aug = nil
  105 + path = "/files#{self.class.file(resource)}"
  106 + begin
  107 + aug = self.class.augopen(resource)
  108 + aug.get("#{path}/#{resource[:name]}")
  109 + ensure
  110 + aug.close if aug
  111 + end
  112 + end
  113 +
  114 + def value=(value)
  115 + aug = nil
  116 + path = "/files#{self.class.file(resource)}"
  117 + begin
  118 + aug = self.class.augopen(resource)
  119 + aug.set("#{path}/#{resource[:name]}", value)
  120 + aug.save!
  121 + ensure
  122 + aug.close if aug
  123 + end
  124 + end
  125 +
  126 + def comment
  127 + aug = nil
  128 + path = "/files#{self.class.file(resource)}"
  129 + begin
  130 + aug = self.class.augopen(resource)
  131 + comment = aug.get("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]")
  132 + comment.sub(/^#{resource[:name]}:\s*/, "") if comment
  133 + comment
  134 + ensure
  135 + aug.close if aug
  136 + end
  137 + end
  138 +
  139 + def comment=(value)
  140 + aug = nil
  141 + path = "/files#{self.class.file(resource)}"
  142 + begin
  143 + aug = self.class.augopen(resource)
  144 + cmtnode = "#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]][. =~ regexp('#{resource[:name]}:.*')]"
  145 + if value.empty?
  146 + aug.rm(cmtnode)
  147 + else
  148 + if aug.match(cmtnode).empty?
  149 + aug.insert("#{path}/#{resource[:name]}", "#comment", true)
  150 + end
  151 + aug.set("#{path}/#comment[following-sibling::*[1][self::#{resource[:name]}]]",
  152 + "#{resource[:name]}: #{resource[:comment]}")
  153 + end
  154 + aug.save!
  155 + ensure
  156 + aug.close if aug
  157 + end
  158 + end
  159 +end
28 lib/puppet/type/sysctl.rb
... ... @@ -0,0 +1,28 @@
  1 +# Manages entries in /etc/sysctl.conf
  2 +#
  3 +# Copyright (c) 2012 Dominic Cleal
  4 +# Licensed under the Apache License, Version 2.0
  5 +
  6 +Puppet::Type.newtype(:sysctl) do
  7 + @doc = "Manages entries in /etc/sysctl.conf."
  8 +
  9 + ensurable
  10 +
  11 + newparam(:name) do
  12 + desc "The name of the setting, e.g. net.ipv4.ip_forward"
  13 + isnamevar
  14 + end
  15 +
  16 + newproperty(:value) do
  17 + 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."
  18 + end
  19 +
  20 + newparam(:target) do
  21 + desc "The file in which to store the settings, defaults to
  22 + `/etc/sysctl.conf`."
  23 + end
  24 +
  25 + newproperty(:comment) do
  26 + 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."
  27 + end
  28 +end
26 spec/fixtures/unit/puppet/sysctl/broken
... ... @@ -0,0 +1,26 @@
  1 +brokenfile
  2 +# Kernel sysctl configuration file
  3 +#
  4 +# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
  5 +# sysctl.conf(5) for more details.
  6 +
  7 +# Controls IP packet forwarding
  8 +net.ipv4.ip_forward = 0
  9 +
  10 +# Controls source route verification
  11 +net.ipv4.conf.default.rp_filter = 1
  12 +
  13 +# Do not accept source routing
  14 +net.ipv4.conf.default.accept_source_route = 0
  15 +
  16 +# Controls the System Request debugging functionality of the kernel
  17 +kernel.sysrq = 0
  18 +
  19 +# Controls whether core dumps will append the PID to the core filename.
  20 +# Useful for debugging multi-threaded applications.
  21 +kernel.core_uses_pid = 1
  22 +
  23 +# Disable netfilter on bridges.
  24 +net.bridge.bridge-nf-call-ip6tables = 0
  25 +net.bridge.bridge-nf-call-iptables = 0
  26 +net.bridge.bridge-nf-call-arptables = 0
0  spec/fixtures/unit/puppet/sysctl/empty
No changes.
26 spec/fixtures/unit/puppet/sysctl/full
... ... @@ -0,0 +1,26 @@
  1 +# Kernel sysctl configuration file
  2 +#
  3 +# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
  4 +# sysctl.conf(5) for more details.
  5 +
  6 +# Controls IP packet forwarding
  7 +net.ipv4.ip_forward = 0
  8 +
  9 +# Controls source route verification
  10 +net.ipv4.conf.default.rp_filter = 1
  11 +
  12 +# net.ipv4.conf.default.accept_source_route: Do not accept source routing
  13 +net.ipv4.conf.default.accept_source_route = 0
  14 +
  15 +# SysRq setting
  16 +# kernel.sysrq: controls the System Request debugging functionality of the kernel
  17 +kernel.sysrq = 0
  18 +
  19 +# Controls whether core dumps will append the PID to the core filename.
  20 +# Useful for debugging multi-threaded applications.
  21 +kernel.core_uses_pid = 1
  22 +
  23 +# Disable netfilter on bridges.
  24 +net.bridge.bridge-nf-call-ip6tables = 0
  25 +net.bridge.bridge-nf-call-iptables = 0
  26 +net.bridge.bridge-nf-call-arptables = 0
8 spec/fixtures/unit/puppet/sysctl/small
... ... @@ -0,0 +1,8 @@
  1 +# Kernel sysctl configuration file
  2 +#
  3 +# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
  4 +# sysctl.conf(5) for more details.
  5 +
  6 +# Controls IP packet forwarding
  7 +net.ipv4.ip_forward = 0
  8 +
3  spec/lib/augeas_spec/augparse.rb
@@ -10,7 +10,8 @@ def augparse(file, lens, result = "?")
10 10 # reprocess file and remove it to make writing tests easier
11 11 File.open("#{dir}/input", "w") do |finput|
12 12 File.open(file, "r") do |ffile|
13   - finput.write line unless (line = ffile.readline) == "\n"
  13 + line = ffile.readline
  14 + finput.write line unless line == "\n"
14 15 ffile.each {|line| finput.write line }
15 16 end
16 17 end
166 spec/unit/puppet/sysctl_spec.rb
... ... @@ -0,0 +1,166 @@
  1 +#!/usr/bin/env rspec
  2 +
  3 +require 'spec_helper'
  4 +
  5 +provider_class = Puppet::Type.type(:sysctl).provider(:augeas)
  6 +
  7 +describe provider_class do
  8 + context "with empty file" do
  9 + let(:tmptarget) { aug_fixture("empty") }
  10 + let(:target) { tmptarget.path }
  11 +
  12 + it "should create simple new entry" do
  13 + apply!(Puppet::Type.type(:sysctl).new(
  14 + :name => "net.ipv4.ip_forward",
  15 + :value => "1",
  16 + :target => target,
  17 + :provider => "augeas"
  18 + ))
  19 +
  20 + augparse(target, "Sysctl.lns", '
  21 + { "net.ipv4.ip_forward" = "1" }
  22 + ')
  23 + end
  24 +
  25 + it "should create new entry with comment" do
  26 + apply!(Puppet::Type.type(:sysctl).new(
  27 + :name => "net.ipv4.ip_forward",
  28 + :value => "1",
  29 + :comment => "test",
  30 + :target => target,
  31 + :provider => "augeas"
  32 + ))
  33 +
  34 + augparse(target, "Sysctl.lns", '
  35 + { "#comment" = "net.ipv4.ip_forward: test" }
  36 + { "net.ipv4.ip_forward" = "1" }
  37 + ')
  38 + end
  39 + end
  40 +
  41 + context "with full file" do
  42 + let(:tmptarget) { aug_fixture("full") }
  43 + let(:target) { tmptarget.path }
  44 +
  45 + it "should list instances" do
  46 + provider_class.stubs(:file).returns(target)
  47 + inst = provider_class.instances.map { |p|
  48 + {
  49 + :name => p.get(:name),
  50 + :ensure => p.get(:ensure),
  51 + :value => p.get(:value),
  52 + :comment => p.get(:comment),
  53 + }
  54 + }
  55 +
  56 + inst.size.should == 8
  57 + inst[0].should == {:name=>"net.ipv4.ip_forward", :ensure=>:present, :value=>"0", :comment=>:absent}
  58 + inst[1].should == {:name=>"net.ipv4.conf.default.rp_filter", :ensure=>:present, :value=>"1", :comment=>:absent}
  59 + inst[2].should == {:name=>"net.ipv4.conf.default.accept_source_route", :ensure=>:present, :value=>"0", :comment=>"Do not accept source routing"}
  60 + inst[3].should == {:name=>"kernel.sysrq", :ensure=>:present, :value=>"0", :comment=>"controls the System Request debugging functionality of the kernel"}
  61 + end
  62 +
  63 + it "should delete entries" do
  64 + apply!(Puppet::Type.type(:sysctl).new(
  65 + :name => "kernel.sysrq",
  66 + :ensure => "absent",
  67 + :target => target,
  68 + :provider => "augeas"
  69 + ))
  70 +
  71 + aug_open(target, "Sysctl.lns") do |aug|
  72 + aug.match("kernel.sysrq").should == []
  73 + aug.match("#comment[. =~ regexp('kernel.sysrq:.*')]").should == []
  74 + end
  75 + end
  76 +
  77 + it "should update value" do
  78 + apply!(Puppet::Type.type(:sysctl).new(
  79 + :name => "net.ipv4.ip_forward",
  80 + :value => "1",
  81 + :target => target,
  82 + :provider => "augeas"
  83 + ))
  84 +
  85 + augparse_filter(target, "Sysctl.lns", "net.ipv4.ip_forward", '
  86 + { "net.ipv4.ip_forward" = "1" }
  87 + ')
  88 + end
  89 +
  90 + describe "when updating comment" do
  91 + it "should change comment" do
  92 + apply!(Puppet::Type.type(:sysctl).new(
  93 + :name => "kernel.sysrq",
  94 + :comment => "enables the SysRq feature",
  95 + :target => target,
  96 + :provider => "augeas"
  97 + ))
  98 +
  99 + aug_open(target, "Sysctl.lns") do |aug|
  100 + aug.match("#comment[. = 'SysRq setting']").should_not == []
  101 + aug.match("#comment[. = 'kernel.sysrq: enables the SysRq feature']").should_not == []
  102 + end
  103 + end
  104 +
  105 + it "should remove comment" do
  106 + apply!(Puppet::Type.type(:sysctl).new(
  107 + :name => "kernel.sysrq",
  108 + :comment => "",
  109 + :target => target,
  110 + :provider => "augeas"
  111 + ))
  112 +
  113 + aug_open(target, "Sysctl.lns") do |aug|
  114 + aug.match("#comment[. =~ regexp('kernel.sysrq:.*')]").should == []
  115 + aug.match("#comment[. = 'SysRq setting']").should_not == []
  116 + end
  117 + end
  118 + end
  119 + end
  120 +
  121 + context "with small file" do
  122 + let(:tmptarget) { aug_fixture("small") }
  123 + let(:target) { tmptarget.path }
  124 +
  125 + describe "when updating comment" do
  126 + it "should add comment" do
  127 + apply!(Puppet::Type.type(:sysctl).new(
  128 + :name => "net.ipv4.ip_forward",
  129 + :comment => "test comment",
  130 + :target => target,
  131 + :provider => "augeas"
  132 + ))
  133 +
  134 + augparse(target, "Sysctl.lns", '
  135 + { "#comment" = "Kernel sysctl configuration file" }
  136 + { }
  137 + { "#comment" = "For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and" }
  138 + { "#comment" = "sysctl.conf(5) for more details." }
  139 + { }
  140 + { "#comment" = "Controls IP packet forwarding" }
  141 + { "#comment" = "net.ipv4.ip_forward: test comment" }
  142 + { "net.ipv4.ip_forward" = "0" }
  143 + { }
  144 + ')
  145 + end
  146 + end
  147 + end
  148 +
  149 + context "with broken file" do
  150 + let(:tmptarget) { aug_fixture("broken") }
  151 + let(:target) { tmptarget.path }
  152 +
  153 + it "should fail to load" do
  154 + txn = apply(Puppet::Type.type(:sysctl).new(
  155 + :name => "net.ipv4.ip_forward",
  156 + :value => "1",
  157 + :target => target,
  158 + :provider => "augeas"
  159 + ))
  160 +
  161 + txn.any_failed?.should_not == nil
  162 + @logs.first.level.should == :err
  163 + @logs.first.message.include?(target).should == true
  164 + end
  165 + end
  166 +end

0 comments on commit b540657

Please sign in to comment.
Something went wrong with that request. Please try again.