specified exceotion when trying to change type.
[puppet_sudo.git] / lib / puppet / provider / sudoers / parsed.rb
1 require 'puppet/provider/parsedfile'
2 sudoers = "/etc/sudoers"
3
4 #
5 # crontab does the same thing, it uses a comment to specify uniqueness
6 #
7  
8 Puppet::Type.type(:sudoers).provide(
9   :parsed, 
10   :parent => Puppet::Provider::ParsedFile, 
11   :default_target => '/etc/sudoers', 
12   :filetype => :flat
13 ) do
14  
15   desc "The sudoers provider that uses the ParsedFile class"
16
17   commands :visudo => 'visudo'
18
19   # this is just copied from hosts
20   text_line :comment, :match => %r{^#}, 
21     :post_parse => proc { |record|
22     # shameful NAMEVAR hack :(
23       if record[:line] =~ /Puppet NAMEVAR (.+)\s*$/
24         record[:name] = $1
25       end
26     } 
27
28   text_line :blank, :match => /^\s*$/;
29
30   #
31   # parse lines as either a Defaults, Alias, or User spec.
32   #
33   # match everything and process entire line
34   record_line :parsed, :fields => %w{line},
35     :match => /(.*)/,
36     :post_parse => proc { |hash|
37       Puppet.debug("sudoers post_parse for line #{hash[:line]}")
38       # create records for aliases
39       if (hash[:line] =~ /^\s*((User|Runas|Host|Cmnd)_Alias)\s+(\S+)\s*=\s*(.+)$/)
40         Puppet.debug("parsed line as Alias")
41         Puppet::Type.type(:sudoers).provider(:parsed).parse_alias($1, $3, $4, hash)
42       elsif (hash[:line] =~ /^\s*(Defaults\S*)\s*(.*)$/)
43         Puppet.debug("parsed line as Defaults")
44         Puppet::Type.type(:sudoers).provider(:parsed).parse_defaults($1, $2, hash)
45       elsif (hash[:line] =~ /^\s*(.*)?=(.*)$/)
46         Puppet.debug("parsed line as User Spec")
47         Puppet::Type.type(:sudoers).provider(:parsed).parse_user_spec($1, $2, hash)
48       else 
49         raise Puppet::Error, "invalid line #{hash[:line]}"
50       end
51 #      puts hash.to_yaml
52 #      hash
53     }
54
55   # parse alias lines
56   def self.parse_alias(sudo_alias, name, items, hash)
57     hash[:type] = 'alias'
58     hash[:sudo_alias] = sudo_alias
59     hash[:name] = name
60     hash[:items] = clean_list(items) 
61     hash 
62   end
63
64   # parse existing user spec lines from sudoers
65   def self.parse_user_spec(users_hosts, commands, hash) 
66     hash[:type] = 'user_spec'
67     #hash[:name] = user
68     #hash[:hosts] = hosts.gsub(/\s/, '').split(',')
69     hash[:commands] = clean_list(commands)
70     hash_array = users_hosts.split(',')  
71     # every element will be a user until the hit the delim
72     currentsymbol = :users
73     hash[:users] = Array.new
74     hash[:hosts] = Array.new
75     # parsing users and hosts is kind of complicated, sorry
76     hash_array.each do |element|
77 #puts "!! #{element}"
78     # the element that splits users and hosts will be 2 white space delimited strings 
79       if element =~ /^\s*(\S+)\s+(\S+)\s*$/
80         user, host  = $1, $2
81         if currentsymbol == :hosts
82           raise Exception, 'found more than one whitespace delim in users_hosts' 
83         end
84         # sweet we found the delim between user and host
85         hash[currentsymbol] << user.gsub(/\s/, '')
86         # now everything else will be a host
87         currentsymbol=:hosts
88         hash[currentsymbol] << host.gsub(/\s/, '')
89       elsif element =~ /\s*\S+\s*/
90         hash[currentsymbol] << element.gsub(/\s/, '')
91       else
92         raise Exception, "Malformed user spec line lhs: #{lhs}"
93       end
94     end
95   end 
96
97   # create record for defaults line
98   def self.parse_defaults(default, parameters, hash)
99     hash[:name] = default
100     hash[:type] = 'default'
101     hash[:parameters] = parameters.gsub(/\s/, '').split(',')
102   end
103   
104   # can I override this?
105   def type=(value)
106     raise Puppet::Error, 'not supporting switching NAMEVAR between record types'
107   end
108   
109   # I could use prefetch_hook to support multi-line entries
110   # will use the prefetch_hook to determine if
111   # the line before us is a commented namevar line
112   # only used for user spec.
113   # Most of this code is shameless taken from provider crontab.rb
114   # NAMEVAR comments leave me in need of a shower, but it seems to be the only way.
115   def self.prefetch_hook(records)
116     # store comment name vars when we find them
117     name=nil
118     results = records.each do |record|
119       if(record[:record_type] == :comment)
120         # if we are a namevar comment
121 #puts "found a comment: #{record.to_yaml}"
122         if record[:name]
123 #puts "found a comment with :name"
124           name = record[:name]
125           record[:skip] = true
126         end
127        # if we are a spec record, check the namevar
128       elsif record[:type] == 'user_spec'
129         if name
130 #puts "adding to a record"
131           record[:name] = name
132           name = nil
133         else
134           puts "spec record not created by puppet"
135           # probably a pre-exting record not created by puppet
136         end 
137       end
138     end.reject{|record| record[:skip]}
139     results
140   end
141
142  # overriding how lines are written to the file
143   def self.to_line(hash) 
144     puts "\nEntering self.to_line for #{hash[:name]}"
145     puts "\n#{hash.to_yaml}\n"
146 #    # dynamically call a function based on the value of hash[:type]
147     if(hash[:record_type] == :blank || hash[:record_type] == :comment)
148       hash[:line]
149     elsif(hash[:sudo_alias])
150       self.alias_to_line(hash) 
151     elsif(hash[:commands])
152       self.spec_to_line(hash)
153     elsif(hash[:parameters])
154       self.default_to_line(hash)
155     else
156       raise Puppet::Error, "dont understand how to write out record \n|#{hash.to_yaml}\n|"
157     end
158   end
159
160   # write line for user spec records
161   def self.spec_to_line(hash)
162 #puts hash.to_yaml
163     #required
164     #users=self.array_convert(hash[:users])
165     # required
166     hosts=self.array_convert(hash[:hosts])
167     users=self.array_convert(hash[:users])
168     # required
169     commands=self.array_convert(hash[:commands])
170     str = "#Puppet NAMEVAR #{hash[:name]}"
171     str << "\n#{users} #{hosts}=#{commands}"
172     Puppet.notice "adding line:  #{str}"
173     str
174   end
175
176   # write line for alias records
177   def self.alias_to_line(hash) 
178     # do I need to ensure that the required elements are here?
179     # shouldnt the type do that? check file, its similar
180     # since different attributes make sense based on ensure value (dir/file/symlink)
181     items=self.array_convert(hash[:items])
182     str = "#{hash[:sudo_alias]} #{hash[:name]}=#{items}"
183     Puppet.notice "adding line: #{str}"
184     str
185   end
186
187   # write line for default records
188   # this is not implemented yet.
189   def self.default_to_line(hash)
190     parameters=self.array_convert(hash[:parameters])
191     str = "#{hash[:name]} #{parameters}"
192     Puppet.notice "Adding line #{str}"
193     str
194   end
195
196   # convert arrays into to , joined lists
197   def self.array_convert(list)
198     if list.class == Array
199       list.join(',')
200     else
201       list
202     end
203   end
204
205   # split a list on delim,  trim leading and trailing white-spaces
206   def self.clean_list(list, delim=',')
207     list.split(delim).collect do |x| 
208       x.gsub(/\s*(.*)?\s*/, '\1')
209     end
210   end
211
212   # used to verify files with visudo before they are flushed 
213   # flush seems to be called more than one time?
214   def self.flush_target(target)
215     Puppet.info("We are flushing #{target}")
216     #  a little pre-flush hot visudo action
217     #puts File.read(target)
218     visudo("-cf", target) unless (File.zero?(target) or !File.exists?(target))
219     super(target)
220   end
221 end