diff options
-rw-r--r-- | examples/mcx_dock_absent.pp | 4 | ||||
-rw-r--r-- | examples/mcx_dock_default.pp | 118 | ||||
-rw-r--r-- | examples/mcx_dock_full.pp | 125 | ||||
-rw-r--r-- | examples/mcx_dock_invalid.pp | 9 | ||||
-rw-r--r-- | examples/mcx_nogroup.pp | 118 | ||||
-rw-r--r-- | examples/mcx_notexists_absent.pp | 4 | ||||
-rw-r--r-- | lib/puppet/provider/mcx/mcxcontent.rb | 199 | ||||
-rw-r--r-- | lib/puppet/type/mcx.rb | 114 | ||||
-rwxr-xr-x | spec/unit/provider/mcx/mcxcontent.rb | 68 | ||||
-rwxr-xr-x | spec/unit/type/mcx.rb | 62 |
10 files changed, 821 insertions, 0 deletions
diff --git a/examples/mcx_dock_absent.pp b/examples/mcx_dock_absent.pp new file mode 100644 index 000000000..ef51897e0 --- /dev/null +++ b/examples/mcx_dock_absent.pp @@ -0,0 +1,4 @@ +mcx { '/Groups/mcx_dock': + ensure => 'absent', + content => 'absent' +} diff --git a/examples/mcx_dock_default.pp b/examples/mcx_dock_default.pp new file mode 100644 index 000000000..1f65a7316 --- /dev/null +++ b/examples/mcx_dock_default.pp @@ -0,0 +1,118 @@ +mcx { '/Groups/mcx_dock': + ensure => 'present', + content => '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.dock</key> + <dict> + <key>AppItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>AppItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-apps</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Mail.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Mail</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Safari.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Safari</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + </array> + </dict> + <key>DocItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>DocItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-others</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>MCXDockSpecialFolders-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>MCXDockSpecialFolders-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>MCXDockSpecialFolders</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>contents-immutable</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + <key>static-only</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + </dict> +</dict> +</plist> +' +} diff --git a/examples/mcx_dock_full.pp b/examples/mcx_dock_full.pp new file mode 100644 index 000000000..60760332a --- /dev/null +++ b/examples/mcx_dock_full.pp @@ -0,0 +1,125 @@ +# Mac MCX Test + +computer { "localhost": } + +mcx { + "mcx_dock": + ensure => "present", + ds_type => "group", + ds_name => "mcx_dock", + content => '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.dock</key> + <dict> + <key>AppItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>AppItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-apps</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Mail.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Mail</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Safari.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Safari</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + </array> + </dict> + <key>DocItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>DocItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-others</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>MCXDockSpecialFolders-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>MCXDockSpecialFolders-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>MCXDockSpecialFolders</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>contents-immutable</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + <key>static-only</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + </dict> +</dict> +</plist> +' +} diff --git a/examples/mcx_dock_invalid.pp b/examples/mcx_dock_invalid.pp new file mode 100644 index 000000000..35f908177 --- /dev/null +++ b/examples/mcx_dock_invalid.pp @@ -0,0 +1,9 @@ +# Mac MCX Test + +computer { "localhost": } + +mcx { + "/Groups/mcx_dock": + ensure => "present", + content => 'invalid plist' +} diff --git a/examples/mcx_nogroup.pp b/examples/mcx_nogroup.pp new file mode 100644 index 000000000..ea689f33c --- /dev/null +++ b/examples/mcx_nogroup.pp @@ -0,0 +1,118 @@ +mcx { '/Groups/doesnotexist': + ensure => 'present', + content => '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.dock</key> + <dict> + <key>AppItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>AppItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-apps</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Mail.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Mail</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + <dict> + <key>mcx_typehint</key> + <integer>1</integer> + <key>tile-data</key> + <dict> + <key>file-data</key> + <dict> + <key>_CFURLString</key> + <string>/Applications/Safari.app</string> + <key>_CFURLStringType</key> + <integer>0</integer> + </dict> + <key>file-label</key> + <string>Safari</string> + </dict> + <key>tile-type</key> + <string>file-tile</string> + </dict> + </array> + </dict> + <key>DocItems-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>DocItems-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>static-others</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>MCXDockSpecialFolders-Raw</key> + <dict> + <key>state</key> + <string>always</string> + <key>upk</key> + <dict> + <key>mcx_input_key_names</key> + <array> + <string>MCXDockSpecialFolders-Raw</string> + </array> + <key>mcx_output_key_name</key> + <string>MCXDockSpecialFolders</string> + <key>mcx_remove_duplicates</key> + <true/> + </dict> + <key>value</key> + <array/> + </dict> + <key>contents-immutable</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + <key>static-only</key> + <dict> + <key>state</key> + <string>always</string> + <key>value</key> + <false/> + </dict> + </dict> +</dict> +</plist> +' +} diff --git a/examples/mcx_notexists_absent.pp b/examples/mcx_notexists_absent.pp new file mode 100644 index 000000000..ef44db10e --- /dev/null +++ b/examples/mcx_notexists_absent.pp @@ -0,0 +1,4 @@ +mcx { '/Groups/foobar': + ensure => 'absent', + content => 'absent' +} diff --git a/lib/puppet/provider/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb new file mode 100644 index 000000000..fdcc8cc5d --- /dev/null +++ b/lib/puppet/provider/mcx/mcxcontent.rb @@ -0,0 +1,199 @@ +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune <mccune.jeff@gmail.com> + +require 'tempfile' + +Puppet::Type.type(:mcx).provide :mcxcontent, :parent => Puppet::Provider do + + desc "MCX Settings management using DirectoryService on OS X. + +This provider manages the entire MCXSettings attribute available +to some directory services nodes. This management is 'all or nothing' +in that discrete application domain key value pairs are not managed +by this provider. + +It is recommended to use WorkGroup Manager to configure Users, Groups, +Computers, or ComputerLists, then use 'ralsh mcx' to generate a puppet +manifest from the resulting configuration. + +Original Author: Jeff McCune (mccune.jeff@gmail.com)" + + # This provides a mapping of puppet types to DirectoryService + # type strings. + TypeMap = { + :user => "Users", + :group => "Groups", + :computer => "Computers", + :computergroup => "ComputerGroups", + } + + class MCXContentProviderException < Exception + + end + + commands :dscl => "/usr/bin/dscl" + confine :operatingsystem => :darwin + defaultfor :operatingsystem => :darwin + + # self.instances is all important. + # This is the only class method, it returns + # an array of instances of this class. + def self.instances + mcx_list = [] + for ds_type in TypeMap.keys + ds_path = "/Local/Default/#{TypeMap[ds_type]}" + output = dscl 'localhost', '-list', ds_path + member_list = output.split + for ds_name in member_list + content = mcxexport(ds_type, ds_name) + if content.empty? + Puppet.debug "/#{TypeMap[ds_type]}/#{ds_name} has no MCX data." + else + # This node has MCX data. + rsrc = self.new(:name => "/#{TypeMap[ds_type]}/#{ds_name}", + :ds_type => ds_type, + :ds_name => ds_name, + :content => content) + mcx_list << rsrc + end + end + end + return mcx_list + end + + private + + # mcxexport is used by instances, and therefore + # a class method. + def self.mcxexport(ds_type, ds_name) + ds_t = TypeMap[ds_type] + ds_n = ds_name.to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_n}" + + dscl 'localhost', '-mcxexport', ds_path + end + + def mcximport(ds_type, ds_name, val) + ds_t = TypeMap[ds_type] + ds_n = ds_name.to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_name}" + + tmp = Tempfile.new('puppet_mcx') + begin + tmp << val + tmp.flush + dscl 'localhost', '-mcximport', ds_path, tmp.path + ensure + tmp.close + tmp.unlink + end + end + + # Given the resource name string, parse ds_type out. + def parse_type(name) + tmp = name.split('/')[1] + if ! tmp.is_a? String + raise MCXContentProviderException, + "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." + end + # De-pluralize and downcase. + tmp = tmp.chop.downcase.to_sym + if not TypeMap.keys.member? tmp + raise MCXContentProviderException, + "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." + end + return tmp + end + + # Given the resource name string, parse ds_name out. + def parse_name(name) + ds_name = name.split('/')[2] + if ! ds_name.is_a? String + raise MCXContentProviderException, + "Could not parse ds_name from resource name '#{name}'. Specify with ds_name parameter." + end + return ds_name + end + + # Gather ds_type and ds_name from resource or + # parse it out of the name. + # This is a private instance method, not a class method. + def get_dsparams + ds_type = resource[:ds_type] + if ds_type.nil? + ds_type = parse_type(resource[:name]) + end + + ds_name = resource[:ds_name] + if ds_name.nil? + ds_name = parse_name(resource[:name]) + end + + rval = { + :ds_type => ds_type.to_sym, + :ds_name => ds_name, + } + + return rval + + end + + public + + def create + self.content=(resource[:content]) + end + + def destroy + ds_parms = get_dsparams + ds_t = TypeMap[ds_parms[:ds_type]] + ds_n = ds_parms[:ds_name].to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_n}" + + dscl 'localhost', '-mcxdelete', ds_path + end + + def exists? + # JJM Just re-use the content method and see if it's empty. + begin + mcx = content + rescue Puppet::ExecutionFailure => e + return false + end + has_mcx = ! mcx.empty? + return has_mcx + end + + def content + ds_parms = get_dsparams + mcx = self.class.mcxexport(ds_parms[:ds_type], + ds_parms[:ds_name]) + return mcx + end + + def content=(value) + # dscl localhost -mcximport + ds_parms = get_dsparams + mcx = mcximport(ds_parms[:ds_type], + ds_parms[:ds_name], + resource[:content]) + return mcx + end + +end diff --git a/lib/puppet/type/mcx.rb b/lib/puppet/type/mcx.rb new file mode 100644 index 000000000..ec33afd13 --- /dev/null +++ b/lib/puppet/type/mcx.rb @@ -0,0 +1,114 @@ +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune <mccune.jeff@gmail.com> + +Puppet::Type.newtype(:mcx) do + + @doc = "MCX object management using DirectoryService on OS X. + +Original Author: Jeff McCune <mccune.jeff@gmail.com> + +The default provider of this type merely manages the XML plist as +reported by the dscl -mcxexport command. This is similar to the +content property of the file type in Puppet. + +The recommended method of using this type is to use Work Group Manager +to manage users and groups on the local computer, record the resulting +puppet manifest using the command 'ralsh mcx' then deploying this +to other machines. +" + feature :manages_content, \ + "The provider can manage MCXSettings as a string.", + :methods => [:content, :content=] + + ensurable do + desc "Create or remove the MCX setting." + + newvalue(:present) do + provider.create + end + + newvalue(:absent) do + provider.destroy + end + + end + + newparam(:name) do + desc "The name of the resource being managed. + The default naming convention follows Directory Service paths: + '/Computers/localhost' + '/Groups/admin' + '/Users/localadmin' + + The ds_type and ds_name type parameters are not necessary if the + default naming convention is followed." + isnamevar + end + + newparam(:ds_type) do + + desc "The DirectoryService type this MCX setting attaches to." + + newvalues(:user, :group, :computer, :computerlist) + + end + + newparam(:ds_name) do + desc "The name to attach the MCX Setting to. + e.g. 'localhost' when ds_type => computer. This setting is not + required, as it may be parsed so long as the resource name is + parseable. e.g. /Groups/admin where 'group' is the dstype." + end + + newproperty(:content, :required_features => :manages_content) do + desc "The XML Plist. The value of MCXSettings in DirectoryService. + This is the standard output from the system command: + dscl localhost -mcxexport /Local/Default/<ds_type>/<ds_name> + Note that ds_type is capitalized and plural in the dscl command." + end + + # JJM Yes, this is not DRY at all. Because of the code blocks + # autorequire must be done this way. I think. + + def setup_autorequire(type) + # value returns a Symbol + name = value(:name) + ds_type = value(:ds_type) + ds_name = value(:ds_name) + if ds_type == type + rval = [ ds_name.to_s ] + else + rval = [ ] + end + rval + end + + autorequire(:user) do + setup_autorequire(:user) + end + + autorequire(:group) do + setup_autorequire(:group) + end + + autorequire(:computer) do + setup_autorequire(:computer) + end + +end diff --git a/spec/unit/provider/mcx/mcxcontent.rb b/spec/unit/provider/mcx/mcxcontent.rb new file mode 100755 index 000000000..6cb3fc78e --- /dev/null +++ b/spec/unit/provider/mcx/mcxcontent.rb @@ -0,0 +1,68 @@ +#! /usr/bin/env ruby +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune <mccune.jeff@gmail.com> + +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:mcx).provider(:mcxcontent) + +describe provider_class do + + before :each do + # Create a mock resource + @resource = stub 'resource' + + @provider = provider_class.new + @attached_to = "/Users/katie" + + # A catch all; no parameters set + @resource.stubs(:[]).returns(nil) + + # But set name, ensure and enable + @resource.stubs(:[]).with(:name).returns @attached_to + @resource.stubs(:[]).with(:enable).returns :true + @resource.stubs(:ref).returns "Mcx[#{@attached_to}]" + + # stub out the provider methods that actually touch the filesystem + # or execute commands + @provider.stubs(:execute).returns("") + @provider.stubs(:resource).returns @resource + end + + it "should have a create method." do + @provider.should respond_to(:create) + end + + it "should have a destroy method." do + @provider.should respond_to(:destroy) + end + + it "should have an exists? method." do + @provider.should respond_to(:exists?) + end + + it "should have an content method." do + @provider.should respond_to(:content) + end + + it "should have an content= method." do + @provider.should respond_to(:content=) + end + +end diff --git a/spec/unit/type/mcx.rb b/spec/unit/type/mcx.rb new file mode 100755 index 000000000..fb67f07d9 --- /dev/null +++ b/spec/unit/type/mcx.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune <mccune.jeff@gmail.com> + +# Most of this code copied from /spec/type/service.rb + +require File.dirname(__FILE__) + '/../../spec_helper' + +require "puppet/type/mcx" + +describe Puppet::Type.type(:mcx), "when validating attributes" do + + [:name, :ds_type, :ds_name].each do |param| + it "should have a #{param} parameter" do + Puppet::Type.type(:mcx).attrtype(param).should == :param + end + end + + [:ensure, :content].each do |param| + it "should have a #{param} property" do + Puppet::Type.type(:mcx).attrtype(param).should == :property + end + end + +end + +describe Puppet::Type.type(:mcx), "when validating attribute values" do + + before do + @provider = stub 'provider', :class => Puppet::Type.type(:mcx).defaultprovider, :clear => nil, :controllable? => false + Puppet::Type.type(:mcx).defaultprovider.stubs(:new).returns(@provider) + end + + after do + Puppet::Type.type(:mcx).clear + end + + it "should support :present as a value to :ensure" do + Puppet::Type.type(:mcx).create(:name => "/Foo/bar", :ensure => :present) + end + + it "should support :absent as a value to :ensure" do + Puppet::Type.type(:mcx).create(:name => "/Foo/bar", :ensure => :absent) + end + +end |