=?ISO-2022-JP?Q?$B%?"@cybozu.co.jp (B $B%7!<(B?=
2010-May-17 12:27 UTC
[Puppet Users] Writing a properly-formed custom type (configxml)
Please hold my hand. After studying the wiki, several unofficial pages, various mailing list entries, and the operation of the "iptables" type as found on the net, I have created a custom type, lovingly named ``configxml''''. It updates attributes on the first element matching a supplied XPath in a given XML file. The first use case is configuring Atlassian JIRA to use Postgres .. The custom type: - Detects the need to update attribute values - Updates attribute values - Pretty prints to the log, ex. when run with ``puppetd -dtv'''' - Solves today''s immediate need, but I don''t trust its ability to properly integrate into the dependency tree, nor even its return value Initially this was split into a type and a provider, which didn''t work. I don''t clearly understand how provider functionality is invoked, nor its relationship with a type. How should this custom type be better structured and written? I greatly appreciate feedback and pointers. I''ve attached relevant code, sorry if its a little long .. As you might sense from reading the code, today marks my 3rd day as a Ruby newbie .. ---- 8< ---- manifest snippet ---- 8< ---- node ''jira'' inherits ''basenode'' { ... configxml { ''/opt/jira/atlassian-jira/WEB-INF/classes/entityengine.xml'': xpath => "/entity-config/datasource[@name=''defaultDS'']", attributes => ''field-type-name=postgres72,schema-name=public'', } ... } ---- 8< ---- configxml.rb ---- 8< ---- module Puppet newtype(:configxml) do @doc = "Modify attributes of the first element matching the XPath expression in an XML file." require ''rexml/document'' require ''set'' def test_it doc = REXML::Document.new(File.open(value(:name))) # Take the first element matching the provided XPath expression attrs = doc.elements[value(:xpath) + "[1]"].attributes # Split up the comma-separated key-value pairs into an array and then isolate the keys keys = value(:attributes).split(/,/).map { |s| s.split(/=/)[0] } # Build up an array of attributes for the specified keys keys.map { |k| "#{k}=#{attrs[k]}" } end def set_it doc = REXML::Document.new(File.open(value(:name))) # Take the first element matching the provided XPath expression attrs = doc.elements[value(:xpath) + "[1]"].attributes # Split up the list comma-separated key-value pairs and convert the array to hash updates = Hash[*value(:attributes).split(/,/).map { |s| s.split(/=/) }.flatten] # Update this element''s attributes updates.each { |k, v| attrs[k] = v } # Write the changes back doc.write(File.open(value(:name), ''w'')) end newparam(:name) do desc "Full path of the XML document." end newparam(:xpath) do desc "XPath expression selecting target elements; the first of which whose attributes will be set." end newparam(:attributes) do desc "An array of strings, each containing ''key=value'' expressions setting element attributes." end def evaluate cur_attrs = test_it if Set.new(value(:attributes).split(/,/)) == Set.new(cur_attrs) debug("Attributes for element at #{value(:xpath)} already #{value(:attributes)}. No changes necessary.") return [] # TODO what goes here? else notice("Updating attributes for element at #{value(:xpath)} to #{value(:attributes)}.") set_it return [] # TODO what goes here? end end end end -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
Ty C
2010-May-18 01:16 UTC
[Puppet Users] Re: Writing a properly-formed custom type (configxml)
This post enlightened me. http://groups.google.com/group/puppet-users/browse_thread/thread/449e106437c3f673/ca9ff7055797f18c?lnk=gst I''ve split configxml into a provider and a type and it appears to work properly now. In more detail, I rewrote "attributes" as a property. All logic is moved into an eponymous provider in 2 methods: "attributes" and "attributes=(value)". -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
Marc Fournier
2010-May-18 15:04 UTC
Re: [Puppet Users] Re: Writing a properly-formed custom type (configxml)
Hello,> I''ve split configxml into a provider and a type and it appears to work > properly now. > In more detail, I rewrote "attributes" as a property. > All logic is moved into an eponymous provider in 2 methods: > "attributes" and "attributes=(value)".Just wondering if you are planning to publish your work once it''s finished ? I''ve been doing this sort of thing using Exec''s of "xmlstarlet", but a pure puppet type is much smarter ! Marc -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
Ty C
2010-May-20 01:39 UTC
[Puppet Users] Re: Writing a properly-formed custom type (configxml)
On May 19, 12:04 am, Marc Fournier <marc.fourn...@camptocamp.com> wrote:> Just wondering if you are planning to publish your work once it''s > finished ? I''ve been doing this sort of thing using Exec''s of > "xmlstarlet", but a pure puppet type is much smarter !Allow me to publish it here. The code: * Assumes error-free operation; there is no validation, error checking, etc. * Unforgivingly expects the value of the attributes property to conform to the syntax "key=value,key=value,key=value" .. Obviously, the code will benefit from some T.L.C., but it functions in my use case as-is. BTW, I, too, was using xmlstarlet in shell scripts, whose essence are being migrated into Puppet wholesale. ---- 8< ---- Manifest snippet, for your delectation ---- 8< ---- configxml { ''/opt/jira/atlassian-jira/WEB-INF/classes/ entityengine.xml'': xpath => "/entity-config/ datasource[@name=''defaultDS'']", attributes => ''field-type-name=postgres72,schema- name=public'', } configxml { ''/opt/jira/conf/server.xml'': xpath => "/Server/Service/Engine/Host/Context/ Resource[@name=''jdbc/JiraDS'']", attributes => "username=$username,password$password,driverClassName=org.postgresql.Driver,url=jdbc:postgresql:// $host/$database", } ---- 8< ---- /etc/puppet/modules/common/lib/puppet/type/configxml.rb ---- 8< ---- Puppet::Type.newtype(:configxml) do @doc = "Modify attributes of the first element matching the XPath expression in an XML file." newparam(:name) do desc "Full path of the XML document." end newparam(:xpath) do desc "XPath expression selecting target elements; the first of which whose attributes will be set." end newproperty(:attributes) do desc "An array of strings, each containing ''key=value'' expressions setting element attributes." end end ---- 8< ---- /etc/puppet/modules/common/lib/puppet/provider/configxml/ configxml.rb ---- 8< ---- Puppet::Type.type(:configxml).provide(:configxml) do desc "TODO" require ''rexml/document'' require ''set'' def attributes doc = REXML::Document.new(File.open(resource[:name])) # Take the first element matching the provided XPath expression attrs = doc.elements[resource[:xpath] + "[1]"].attributes # Split up the comma-separated key-value pairs into an array and then isolate the keys keys = resource[:attributes].split(/,/).map { |s| s.split(/=/, 2) [0] } # Build up an array of attributes for the specified keys. Arrange keys in order of appearance # in resource[:attributes] so that Puppet can compare the desired and current values. keys.map { |k| "#{k}=#{attrs[k]}" }.join('','') end def attributes=(value) doc = REXML::Document.new(File.open(resource[:name])) # Take the first element matching the provided XPath expression attrs = doc.elements[resource[:xpath] + "[1]"].attributes # Split up the comma-separated key-value pairs and convert the resulting array to a hash updates = Hash[*resource[:attributes].split(/,/).map { |s| s.split(/ =/, 2) }.flatten] # Update this element''s attributes updates.each { |k, v| attrs[k] = v } # Write the changes back doc.write(File.open(resource[:name], ''w'')) end end -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.