Skip to content

Commit

Permalink
Add new type service_config
Browse files Browse the repository at this point in the history
Solaris 11 has migrated a log of configuration parameters from
configuration files into smf manifests. So instead of manipulating a
`/etc/resolv.conf` file you manipulate properties with the svccfg
utility.

The new type `service_config` can now be used to set such properties,
e.g.:

    service_config { 'network/dns/client:config/nameserver':
      ensure => [ '10.0.0.2', '10.0.0.3' ],
      type   => net_address,
    }

which is the same as

    service_config { 'config/nameserver':
      ensure => [ '10.0.0.2', '10.0.0.3' ],
      fmri   => 'svc:/network/dns/client',
      type   => net_address,
    }
  • Loading branch information
stschulte committed Jan 29, 2013
1 parent 6e1a34d commit 57538ea
Show file tree
Hide file tree
Showing 5 changed files with 544 additions and 0 deletions.
54 changes: 54 additions & 0 deletions lib/puppet/provider/service_config/svccfg.rb
@@ -0,0 +1,54 @@
require 'strscan'
Puppet::Type.type(:service_config).provide(:svccfg) do
desc "Manages smf configuration with svccfg"

commands :svccfg => '/usr/sbin/svccfg'

defaultfor :operatingsystem => :solaris

def ensure
result = [:absent]
svccfg('-s', resource[:fmri], :listprop, resource[:prop]).each_line do |line|
next if /^\s*$/.match(line) # ignore empty lines
next if /^\s*#/.match(line) # ignore comments
name, type, value = line.chomp.split(/\s+/,3)
scanner = StringScanner.new(value)
result = []
while !scanner.eos?
scanner.skip(/\s+/)
# TODO: This will not work if the value itself contains escaped
# characters such as \"
if token = scanner.scan(/".*?"|\S+/)
token.gsub!(/"(.*)"/, '\1')
result << token
else
raise Puppet::Error, "Unable to parse value #{value}"
end
end
break
end
result
end

def ensure=(new_value)
new_value = [new_value] unless new_value.is_a? Array
if new_value == [:absent]
svccfg('-s', resource[:fmri], :delprop, resource[:prop])
else
quoted_values = case resource[:type]
when :astring
new_value.map {|s| "\"#{s}\"" }
else
new_value
end
argument = if quoted_values.size == 1
quoted_values.first
else
"(#{quoted_values.join(' ')})"
end

svccfg('-s', resource[:fmri], :setprop, resource[:prop], '=', "#{resource[:type]}:", argument)
end
end

end
104 changes: 104 additions & 0 deletions lib/puppet/type/service_config.rb
@@ -0,0 +1,104 @@
module Puppet
newtype(:service_config) do

@doc = "Manages smf configuration on Solaris 11.
Solaris 11 and OpenSolaris have moved a lot of configuration aspects
from plaintext files to SMF configuration. The `service_config` type
can be used to set these options with puppet. One serive config is
identified by a service identifier (FMRI) and a property name.
The title of a `service_config` resource can either be the combination
of a fmri and a property name in the format `<fmri>:<property>` or can
be just the property name. In the latter case you have to provider
the fmri as a resource parameter.
E.g. to set the nameserver of your DNS client to `10.0.0.1` and
`10.0.0.2` you can either write
service_config { 'network/dns/client:config/nameserver':
ensure => [ '10.0.0.1', '10.0.0.2' ],
type => net_address,
}
or you can write
service_config { 'config/nameserver':
ensure => [ '10.0.0.1', '10.0.0.2' ],
fmri => 'network/dns/client',
type => net_address,
}
Be aware that in both cases the resource title has to be unique.
Valid values to `ensure` are either a single value, an array or the
value `absent` when you want to make sure the specified property is
absent."

def self.title_patterns
[
# pattern to parse <fmri>:<prop>
[
/^(.*):(.*)$/,
[
[:fmri, lambda{|x| x} ],
[:prop, lambda{|x| x} ]
]
],
# pattern to parse <prop>
[
/^(.*)$/,
[
[:prop, lambda{|x| x}]
]
]
]
end

def name
# I am not sure if puppet relies on the resource having a name.
# In general the name is the value of the namevar of the resource
# Because we use multiple namevars (fmri and prop) this is not going
# to work, so I overwrite the method here. The type may as well work
# without this method but you knows...
"#{self[:fmri]}:#{self[:prop]}"
end

newparam(:fmri) do
desc "The name of the service you want to configure, e.g.
`svc:/system/keymap:default`"

isnamevar
end

newparam(:prop) do
desc "The name of the property you want to configure, e.g.
`keymap/layout`"

isnamevar
end

newparam(:type) do
desc "The type of the property. This is important when changing a setting"

newvalues :astring
newvalues :boolean
newvalues :integer, :count, :time
newvalues :net_address, :net_address_v4, :net_address_v6
end

newproperty(:ensure, :array_matching => :all) do
desc "The desired value of the property. You can either specify a
single value, an array, or the special string `absent`, if you want
to remove a property"

newvalues :absent
newvalues /.*/

def insync?(is)
is == @should
end
end

end
end
148 changes: 148 additions & 0 deletions spec/integration/provider/service_config/svccfg_spec.rb
@@ -0,0 +1,148 @@
#! /usr/bin/env ruby

require 'spec_helper'

describe Puppet::Type.type(:service_config).provider(:svccfg), '(integration)' do

before :each do
described_class.stubs(:suitable?).returns true
end

let :fmri do
'svc:/network/dns/client'
end

let :prop do
'config/search'
end

let :default_options do
{
:title => "#{fmri}:#{prop}",
:fmri => fmri,
:prop => prop,
:type => :astring
}
end

let :resource_singlevalue do
Puppet::Type.type(:service_config).new(default_options.merge(:ensure => 'example.com'))
end

let :resource_listone do
Puppet::Type.type(:service_config).new(default_options.merge(:ensure => ['test.com']))
end

let :resource_listthree do
Puppet::Type.type(:service_config).new(default_options.merge(:ensure => ['example.com', 'example.de', 'test.com']))
end

let :resource_absent do
Puppet::Type.type(:service_config).new(default_options.merge(:ensure => :absent))
end

def run_in_catalog(resource)
catalog = Puppet::Resource::Catalog.new
catalog.host_config = false
resource.expects(:err).never
catalog.add_resource resource
catalog.apply
end

describe "ensure is a single value" do
it "should do nothing if value is in sync" do
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring example.com\n")
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"example.com"').never
run_in_catalog(resource_singlevalue)
end

it "should create the property if currently absent" do
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("\n\n")
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"example.com"')
run_in_catalog(resource_singlevalue)
end

it "should replace a single value" do
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring wrong.com\n")
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"example.com"')
run_in_catalog(resource_singlevalue)
end

it "should replace a list of values" do
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring \"example.com\" \"wrong.com\"\n")
resource_singlevalue.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"example.com"')
run_in_catalog(resource_singlevalue)
end
end

describe "ensure is a list of values with one element" do
it "should do nothing if value is in sync" do
resource_listone.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring test.com\n")
resource_listone.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"test.com"').never
run_in_catalog(resource_listone)
end

it "should create the property if currently absent" do
resource_listone.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("\n\n")
resource_listone.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"test.com"')
run_in_catalog(resource_listone)
end

it "should replace a single value" do
resource_listone.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring wrong.com\n")
resource_listone.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"test.com"')
run_in_catalog(resource_listone)
end

it "should replace a list of values" do
resource_listone.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring \"example.com\" \"wrong.com\"\n")
resource_listone.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '"test.com"')
run_in_catalog(resource_listone)
end
end

describe "ensure is a list of values with more than one element" do
it "should do nothing if value is in sync" do
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring \"example.com\" \"example.de\" \"test.com\"\n")
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '("example.com" "example.de" "test.com")').never
run_in_catalog(resource_listthree)
end

it "should create the property if currently absent" do
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("\n\n")
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '("example.com" "example.de" "test.com")')
run_in_catalog(resource_listthree)
end

it "should replace a single value" do
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring wrong.com\n")
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '("example.com" "example.de" "test.com")')
run_in_catalog(resource_listthree)
end

it "should replace a list of values" do
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring \"example.com\" \"test.com\" \"example.de\"\n")
resource_listthree.provider.expects(:svccfg).with('-s', fmri, :setprop, prop, '=', 'astring:', '("example.com" "example.de" "test.com")')
run_in_catalog(resource_listthree)
end
end

describe "ensure is absent" do
it "should to nothing if property is already absent" do
resource_absent.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("\n\n")
resource_absent.provider.expects(:svccfg).never
run_in_catalog(resource_absent)
end
it "should remove the property if it has a single value" do
resource_absent.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring wrong.com\n")
resource_absent.provider.expects(:svccfg).with('-s', fmri, :delprop, prop)
run_in_catalog(resource_absent)
end

it "should remove the property if it has a list of values" do
resource_absent.provider.expects(:svccfg).with('-s', fmri, :listprop, prop).returns("config/search astring \"example.com\" \"test.com\" \"example.de\"\n")
resource_absent.provider.expects(:svccfg).with('-s', fmri, :delprop, prop)
run_in_catalog(resource_absent)
end
end
end

0 comments on commit 57538ea

Please sign in to comment.