puppet 2.6.1 → 2.6.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (79) hide show
  1. data/CHANGELOG +51 -195
  2. data/LICENSE +2 -2
  3. data/conf/suse/puppet.spec +13 -10
  4. data/install.rb +7 -13
  5. data/lib/puppet.rb +1 -1
  6. data/lib/puppet/application.rb +17 -21
  7. data/lib/puppet/defaults.rb +1 -1
  8. data/lib/puppet/dsl.rb +0 -4
  9. data/lib/puppet/dsl/resource_type_api.rb +16 -10
  10. data/lib/puppet/external/event-loop/event-loop.rb +15 -11
  11. data/lib/puppet/feature/base.rb +2 -1
  12. data/lib/puppet/feature/rails.rb +1 -3
  13. data/lib/puppet/network/http/webrick/rest.rb +1 -0
  14. data/lib/puppet/network/http_pool.rb +0 -12
  15. data/lib/puppet/parser/ast/function.rb +1 -1
  16. data/lib/puppet/parser/ast/resource.rb +1 -5
  17. data/lib/puppet/parser/functions/extlookup.rb +1 -1
  18. data/lib/puppet/parser/functions/versioncmp.rb +3 -3
  19. data/lib/puppet/parser/lexer.rb +1 -1
  20. data/lib/puppet/parser/parser_support.rb +4 -2
  21. data/lib/puppet/parser/resource.rb +8 -0
  22. data/lib/puppet/parser/type_loader.rb +50 -48
  23. data/lib/puppet/provider/nameservice.rb +1 -0
  24. data/lib/puppet/provider/nameservice/objectadd.rb +2 -1
  25. data/lib/puppet/provider/service/freebsd.rb +1 -1
  26. data/lib/puppet/provider/service/launchd.rb +16 -9
  27. data/lib/puppet/provider/ssh_authorized_key/parsed.rb +7 -0
  28. data/lib/puppet/provider/user/hpux.rb +0 -1
  29. data/lib/puppet/provider/user/user_role_add.rb +21 -9
  30. data/lib/puppet/provider/user/useradd.rb +44 -3
  31. data/lib/puppet/rails.rb +3 -3
  32. data/lib/puppet/reference/configuration.rb +7 -12
  33. data/lib/puppet/reference/indirection.rb +2 -2
  34. data/lib/puppet/reference/metaparameter.rb +10 -9
  35. data/lib/puppet/reference/type.rb +6 -6
  36. data/lib/puppet/reports/rrdgraph.rb +1 -1
  37. data/lib/puppet/reports/tagmail.rb +1 -1
  38. data/lib/puppet/ssl/certificate_request.rb +1 -1
  39. data/lib/puppet/sslcertificates/ca.rb +4 -10
  40. data/lib/puppet/type.rb +3 -3
  41. data/lib/puppet/type/cron.rb +1 -1
  42. data/lib/puppet/type/tidy.rb +1 -0
  43. data/lib/puppet/type/user.rb +55 -0
  44. data/lib/puppet/type/whit.rb +4 -0
  45. data/lib/puppet/util.rb +8 -0
  46. data/lib/puppet/util/metric.rb +38 -9
  47. data/lib/puppet/util/rdoc/parser.rb +10 -7
  48. data/lib/puppet/util/reference.rb +4 -4
  49. data/lib/puppet/util/zaml.rb +0 -1
  50. data/spec/fixtures/yaml/report0.25.x.yaml +64 -0
  51. data/spec/fixtures/yaml/report2.6.x.yaml +190 -0
  52. data/spec/integration/application/doc_spec.rb +55 -0
  53. data/spec/integration/defaults_spec.rb +1 -1
  54. data/spec/integration/parser/compiler_spec.rb +21 -0
  55. data/spec/integration/parser/ruby_manifest_spec.rb +128 -0
  56. data/spec/lib/puppet_spec/files.rb +1 -0
  57. data/spec/spec_helper.rb +1 -0
  58. data/spec/unit/application_spec.rb +16 -25
  59. data/spec/unit/dsl/resource_type_api_spec.rb +32 -12
  60. data/spec/unit/indirector/node/active_record_spec.rb +0 -1
  61. data/spec/unit/parser/ast/function_spec.rb +14 -4
  62. data/spec/unit/parser/lexer_spec.rb +8 -0
  63. data/spec/unit/parser/parser_spec.rb +0 -9
  64. data/spec/unit/parser/type_loader_spec.rb +3 -16
  65. data/spec/unit/provider/service/launchd_spec.rb +8 -5
  66. data/spec/unit/provider/user/user_role_add_spec.rb +18 -1
  67. data/spec/unit/provider/user/useradd_spec.rb +19 -0
  68. data/spec/unit/rails_spec.rb +10 -4
  69. data/spec/unit/reports/rrdgraph_spec.rb +31 -0
  70. data/spec/unit/reports/tagmail_spec.rb +1 -0
  71. data/spec/unit/sslcertificates/ca_spec.rb +110 -0
  72. data/spec/unit/type/user_spec.rb +19 -1
  73. data/spec/unit/type/whit_spec.rb +11 -0
  74. data/spec/unit/type_spec.rb +7 -0
  75. data/spec/unit/util/rdoc/parser_spec.rb +18 -14
  76. data/spec/unit/util/zaml_spec.rb +2 -1
  77. metadata +11 -6
  78. data/conf/suse/ruby-env.patch +0 -17
  79. data/test/certmgr/ca.rb +0 -87
@@ -64,6 +64,7 @@ class Puppet::Parser::Resource < Puppet::Resource
64
64
 
65
65
  # Retrieve the associated definition and evaluate it.
66
66
  def evaluate
67
+ return if evaluated?
67
68
  @evaluated = true
68
69
  if klass = resource_type and ! builtin_type?
69
70
  finish
@@ -93,6 +94,7 @@ class Puppet::Parser::Resource < Puppet::Resource
93
94
  @finished = true
94
95
  add_defaults
95
96
  add_metaparams
97
+ add_scope_tags
96
98
  validate
97
99
  end
98
100
 
@@ -259,6 +261,12 @@ class Puppet::Parser::Resource < Puppet::Resource
259
261
  end
260
262
  end
261
263
 
264
+ def add_scope_tags
265
+ if scope_resource = scope.resource
266
+ tag(*scope_resource.tags)
267
+ end
268
+ end
269
+
262
270
  # Accept a parameter from an override.
263
271
  def override_parameter(param)
264
272
  # This can happen if the override is defining a new parameter, rather
@@ -3,25 +3,56 @@ require 'puppet/node/environment'
3
3
  class Puppet::Parser::TypeLoader
4
4
  include Puppet::Node::Environment::Helper
5
5
 
6
- class Helper < Hash
6
+ # Helper class that makes sure we don't try to import the same file
7
+ # more than once from either the same thread or different threads.
8
+ class Helper
7
9
  include MonitorMixin
8
- def done_with(item)
9
- synchronize do
10
- delete(item)[:busy].signal if self.has_key?(item) and self[item][:loader] == Thread.current
11
- end
10
+ def initialize
11
+ super
12
+ # These hashes are indexed by filename
13
+ @state = {} # :doing or :done
14
+ @thread = {} # if :doing, thread that's doing the parsing
15
+ @cond_var = {} # if :doing, condition var that will be signaled when done.
12
16
  end
13
- def owner_of(item)
14
- synchronize do
15
- if !self.has_key? item
16
- self[item] = { :loader => Thread.current, :busy => self.new_cond}
17
- :nobody
18
- elsif self[item][:loader] == Thread.current
19
- :this_thread
17
+
18
+ # Execute the supplied block exactly once per file, no matter how
19
+ # many threads have asked for it to run. If another thread is
20
+ # already executing it, wait for it to finish. If this thread is
21
+ # already executing it, return immediately without executing the
22
+ # block.
23
+ #
24
+ # Note: the reason for returning immediately if this thread is
25
+ # already executing the block is to handle the case of a circular
26
+ # import--when this happens, we attempt to recursively re-parse a
27
+ # file that we are already in the process of parsing. To prevent
28
+ # an infinite regress we need to simply do nothing when the
29
+ # recursive import is attempted.
30
+ def do_once(file)
31
+ need_to_execute = synchronize do
32
+ case @state[file]
33
+ when :doing
34
+ if @thread[file] != Thread.current
35
+ @cond_var[file].wait
36
+ end
37
+ false
38
+ when :done
39
+ false
20
40
  else
21
- flag = self[item][:busy]
22
- flag.wait
23
- flag.signal
24
- :another_thread
41
+ @state[file] = :doing
42
+ @thread[file] = Thread.current
43
+ @cond_var[file] = new_cond
44
+ true
45
+ end
46
+ end
47
+ if need_to_execute
48
+ begin
49
+ yield
50
+ ensure
51
+ synchronize do
52
+ @state[file] = :done
53
+ @thread.delete(file)
54
+ @cond_var.delete(file).broadcast
55
+ end
25
56
  end
26
57
  end
27
58
  end
@@ -51,8 +82,7 @@ class Puppet::Parser::TypeLoader
51
82
  unless file =~ /^#{File::SEPARATOR}/
52
83
  file = File.join(dir, file)
53
84
  end
54
- unless imported? file
55
- @imported[file] = true
85
+ @loading_helper.do_once(file) do
56
86
  parse_file(file)
57
87
  end
58
88
  end
@@ -60,27 +90,20 @@ class Puppet::Parser::TypeLoader
60
90
  modname
61
91
  end
62
92
 
63
- def imported?(file)
64
- @imported.has_key?(file)
65
- end
66
-
67
93
  def known_resource_types
68
94
  environment.known_resource_types
69
95
  end
70
96
 
71
97
  def initialize(env)
72
98
  self.environment = env
73
- @loaded = {}
74
- @loading = Helper.new
75
-
76
- @imported = {}
99
+ @loading_helper = Helper.new
77
100
  end
78
101
 
79
102
  def load_until(namespaces, name)
80
103
  return nil if name == "" # special-case main.
81
104
  name2files(namespaces, name).each do |filename|
82
105
  modname = begin
83
- import_if_possible(filename)
106
+ import(filename)
84
107
  rescue Puppet::ImportError => detail
85
108
  # We couldn't load the item
86
109
  # I'm not convienced we should just drop these errors, but this
@@ -96,10 +119,6 @@ class Puppet::Parser::TypeLoader
96
119
  nil
97
120
  end
98
121
 
99
- def loaded?(name)
100
- @loaded.include?(name)
101
- end
102
-
103
122
  def name2files(namespaces, name)
104
123
  return [name.sub(/^::/, '').gsub("::", File::SEPARATOR)] if name =~ /^::/
105
124
 
@@ -126,21 +145,4 @@ class Puppet::Parser::TypeLoader
126
145
  parser.file = file
127
146
  parser.parse
128
147
  end
129
-
130
- # Utility method factored out of load for handling thread-safety.
131
- # This isn't tested in the specs, because that's basically impossible.
132
- def import_if_possible(file, current_file = nil)
133
- @loaded[file] || begin
134
- case @loading.owner_of(file)
135
- when :this_thread
136
- nil
137
- when :another_thread
138
- import_if_possible(file,current_file)
139
- when :nobody
140
- @loaded[file] = import(file,current_file)
141
- end
142
- ensure
143
- @loading.done_with(file)
144
- end
145
- end
146
148
  end
@@ -165,6 +165,7 @@ class Puppet::Provider::NameService < Puppet::Provider
165
165
 
166
166
  begin
167
167
  execute(self.addcmd)
168
+ execute(self.passcmd) if self.feature? :manages_password_age
168
169
  rescue Puppet::ExecutionFailure => detail
169
170
  raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}"
170
171
  end
@@ -13,7 +13,8 @@ class ObjectAdd < Puppet::Provider::NameService
13
13
  end
14
14
 
15
15
  def modifycmd(param, value)
16
- cmd = [command(:modify), flag(param), value]
16
+ cmd = [command(param.to_s =~ /password_.+_age/ ? :password : :modify)]
17
+ cmd << flag(param) << value
17
18
  if @resource.allowdupe? && ((param == :uid) || (param == :gid and self.class.name == :groupadd))
18
19
  cmd << "-o"
19
20
  end
@@ -78,7 +78,7 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do
78
78
 
79
79
  # Add a new setting to the rc files
80
80
  def rc_add(service, rcvar, yesno)
81
- append = "\n\# Added by Puppet\n#{rcvar}_enable=\"#{yesno}\""
81
+ append = "\# Added by Puppet\n#{rcvar}_enable=\"#{yesno}\"\n"
82
82
  # First, try the one-file-per-service style
83
83
  if File.exists?(@@rcconf_dir)
84
84
  File.open(@@rcconf_dir + "/#{service}", File::WRONLY | File::APPEND | File::CREAT, 0644) {
@@ -38,6 +38,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
38
38
 
39
39
  commands :launchctl => "/bin/launchctl"
40
40
  commands :sw_vers => "/usr/bin/sw_vers"
41
+ commands :plutil => "/usr/bin/plutil"
41
42
 
42
43
  defaultfor :operatingsystem => :darwin
43
44
  confine :operatingsystem => :darwin
@@ -52,6 +53,12 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
52
53
  Launchd_Overrides = "/var/db/launchd.db/com.apple.launchd/overrides.plist"
53
54
 
54
55
 
56
+ # Read a plist, whether its format is XML or in Apple's "binary1"
57
+ # format.
58
+ def self.read_plist(path)
59
+ Plist::parse_xml(plutil('-convert', 'xml1', '-o', '-', path))
60
+ end
61
+
55
62
  # returns a label => path map for either all jobs, or just a single
56
63
  # job if the label is specified
57
64
  def self.jobsearch(label=nil)
@@ -62,8 +69,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
62
69
  next if f =~ /^\..*$/
63
70
  next if FileTest.directory?(f)
64
71
  fullpath = File.join(path, f)
65
- job = Plist::parse_xml(fullpath)
66
- if job and job.has_key?("Label")
72
+ if FileTest.file?(fullpath) and job = read_plist(fullpath) and job.has_key?("Label")
67
73
  if job["Label"] == label
68
74
  return { label => fullpath }
69
75
  else
@@ -118,8 +124,11 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
118
124
  def plist_from_label(label)
119
125
  job = self.class.jobsearch(label)
120
126
  job_path = job[label]
121
- job_plist = Plist::parse_xml(job_path)
122
- raise Puppet::Error.new("Unable to parse launchd plist at path: #{job_path}") if not job_plist
127
+ if FileTest.file?(job_path)
128
+ job_plist = self.class.read_plist(job_path)
129
+ else
130
+ raise Puppet::Error.new("Unable to parse launchd plist at path: #{job_path}")
131
+ end
123
132
  [job_path, job_plist]
124
133
  end
125
134
 
@@ -200,9 +209,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
200
209
  job_plist_disabled = job_plist["Disabled"] if job_plist.has_key?("Disabled")
201
210
 
202
211
  if self.class.get_macosx_version_major == "10.6":
203
- overrides = Plist::parse_xml(Launchd_Overrides)
204
-
205
- unless overrides.nil?
212
+ if FileTest.file?(Launchd_Overrides) and overrides = self.class.read_plist(Launchd_Overrides)
206
213
  if overrides.has_key?(resource[:name])
207
214
  overrides_disabled = overrides[resource[:name]]["Disabled"] if overrides[resource[:name]].has_key?("Disabled")
208
215
  end
@@ -227,7 +234,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
227
234
  # versions this is stored in the job plist itself.
228
235
  def enable
229
236
  if self.class.get_macosx_version_major == "10.6"
230
- overrides = Plist::parse_xml(Launchd_Overrides)
237
+ overrides = self.class.read_plist(Launchd_Overrides)
231
238
  overrides[resource[:name]] = { "Disabled" => false }
232
239
  Plist::Emit.save_plist(overrides, Launchd_Overrides)
233
240
  else
@@ -242,7 +249,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
242
249
 
243
250
  def disable
244
251
  if self.class.get_macosx_version_major == "10.6"
245
- overrides = Plist::parse_xml(Launchd_Overrides)
252
+ overrides = self.class.read_plist(Launchd_Overrides)
246
253
  overrides[resource[:name]] = { "Disabled" => true }
247
254
  Plist::Emit.save_plist(overrides, Launchd_Overrides)
248
255
  else
@@ -61,6 +61,13 @@ require 'puppet/provider/parsedfile'
61
61
  Dir.mkdir(dir, dir_perm)
62
62
  File.chown(uid, nil, dir)
63
63
  end
64
+
65
+ # ParsedFile usually calls backup_target much later in the flush process,
66
+ # but our SUID makes that fail to open filebucket files for writing.
67
+ # Fortunately, there's already logic to make sure it only ever happens once,
68
+ # so calling it here supresses the later attempt by our superclass's flush method.
69
+ self.class.backup_target(target)
70
+
64
71
  Puppet::Util::SUIDManager.asuser(@resource.should(:user)) { super }
65
72
  File.chown(uid, nil, target)
66
73
  File.chmod(file_perm, target)
@@ -26,5 +26,4 @@ Puppet::Type.type(:user).provide :hpuxuseradd, :parent => :useradd do
26
26
  def modifycmd(param,value)
27
27
  super.insert(1,"-F")
28
28
  end
29
-
30
29
  end
@@ -6,13 +6,15 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
6
6
 
7
7
  defaultfor :operatingsystem => :solaris
8
8
 
9
- commands :add => "useradd", :delete => "userdel", :modify => "usermod", :role_add => "roleadd", :role_delete => "roledel", :role_modify => "rolemod"
9
+ commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "chage", :role_add => "roleadd", :role_delete => "roledel", :role_modify => "rolemod"
10
10
  options :home, :flag => "-d", :method => :dir
11
11
  options :comment, :method => :gecos
12
12
  options :groups, :flag => "-G"
13
13
  options :roles, :flag => "-R"
14
14
  options :auths, :flag => "-A"
15
15
  options :profiles, :flag => "-P"
16
+ options :password_min_age, :flag => "-m"
17
+ options :password_max_age, :flag => "-M"
16
18
 
17
19
  verify :gid, "GID must be an integer" do |value|
18
20
  value.is_a? Integer
@@ -22,14 +24,14 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
22
24
  value !~ /\s/
23
25
  end
24
26
 
25
- has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords
27
+ has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords, :manages_password_age
26
28
 
27
29
  #must override this to hand the keyvalue pairs
28
30
  def add_properties
29
31
  cmd = []
30
32
  Puppet::Type.type(:user).validproperties.each do |property|
31
33
  #skip the password because we can't create it with the solaris useradd
32
- next if [:ensure, :password].include?(property)
34
+ next if [:ensure, :password, :password_min_age, :password_max_age].include?(property)
33
35
  # 1680 Now you can set the hashed passwords on solaris:lib/puppet/provider/user/user_role_add.rb
34
36
  # the value needs to be quoted, mostly because -c might
35
37
  # have spaces in it
@@ -79,6 +81,7 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
79
81
  run(transition("normal"), "transition role to")
80
82
  else
81
83
  run(addcmd, "create")
84
+ run(passcmd, "change password policy for")
82
85
  end
83
86
  # added to handle case when password is specified
84
87
  self.password = @resource[:password] if @resource[:password]
@@ -140,14 +143,23 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
140
143
  run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs")
141
144
  end
142
145
 
143
- #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return the hashed pw (the second entry)
146
+ #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it
144
147
  #No abstraction, all esoteric knowledge of file formats, yay
148
+ def shadow_entry
149
+ return @shadow_entry if defined? @shadow_entry
150
+ @shadow_entry = File.readlines("/etc/shadow").reject { |r| r =~ /^[^\w]/ }.collect { |l| l.chomp.split(':') }.find { |user, _| user == @resource[:name] }
151
+ end
152
+
145
153
  def password
146
- #got perl?
147
- if ary = File.readlines("/etc/shadow").reject { |r| r =~ /^[^\w]/}.collect { |l| l.split(':')[0..1] }.find { |user, passwd| user == @resource[:name] }
148
- pass = ary[1]
149
- end
150
- pass
154
+ shadow_entry[1] if shadow_entry
155
+ end
156
+
157
+ def min_age
158
+ shadow_entry ? shadow_entry[3] : :absent
159
+ end
160
+
161
+ def max_age
162
+ shadow_entry ? shadow_entry[4] : :absent
151
163
  end
152
164
 
153
165
  #Read in /etc/shadow, find the line for our used and rewrite it with the new pw
@@ -3,11 +3,13 @@ require 'puppet/provider/nameservice/objectadd'
3
3
  Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do
4
4
  desc "User management via `useradd` and its ilk. Note that you will need to install the `Shadow Password` Ruby library often known as ruby-libshadow to manage user passwords."
5
5
 
6
- commands :add => "useradd", :delete => "userdel", :modify => "usermod"
6
+ commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "chage"
7
7
 
8
8
  options :home, :flag => "-d", :method => :dir
9
9
  options :comment, :method => :gecos
10
10
  options :groups, :flag => "-G"
11
+ options :password_min_age, :flag => "-m"
12
+ options :password_max_age, :flag => "-M"
11
13
 
12
14
  verify :gid, "GID must be an integer" do |value|
13
15
  value.is_a? Integer
@@ -17,9 +19,9 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ
17
19
  value !~ /\s/
18
20
  end
19
21
 
20
- has_features :manages_homedir, :allows_duplicates
22
+ has_features :manages_homedir, :allows_duplicates, :manages_expiry
21
23
 
22
- has_feature :manages_passwords if Puppet.features.libshadow?
24
+ has_features :manages_passwords, :manages_password_age if Puppet.features.libshadow?
23
25
 
24
26
  def check_allow_dup
25
27
  @resource.allowdupe? ? ["-o"] : []
@@ -35,10 +37,20 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ
35
37
  cmd
36
38
  end
37
39
 
40
+ def check_manage_expiry
41
+ cmd = []
42
+ if @resource[:expiry]
43
+ cmd << "-e #{@resource[:expiry]}"
44
+ end
45
+
46
+ cmd
47
+ end
48
+
38
49
  def add_properties
39
50
  cmd = []
40
51
  Puppet::Type.type(:user).validproperties.each do |property|
41
52
  next if property == :ensure
53
+ next if property.to_s =~ /password_.+_age/
42
54
  # the value needs to be quoted, mostly because -c might
43
55
  # have spaces in it
44
56
  if value = @resource.should(property) and value != ""
@@ -53,9 +65,38 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ
53
65
  cmd += add_properties
54
66
  cmd += check_allow_dup
55
67
  cmd += check_manage_home
68
+ cmd += check_manage_expiry
56
69
  cmd << @resource[:name]
57
70
  end
58
71
 
72
+ def passcmd
73
+ cmd = [command(:password)]
74
+ [:password_min_age, :password_max_age].each do |property|
75
+ if value = @resource.should(property)
76
+ cmd << flag(property) << value
77
+ end
78
+ end
79
+ cmd << @resource[:name]
80
+ end
81
+
82
+ def min_age
83
+ if Puppet.features.libshadow?
84
+ if ent = Shadow::Passwd.getspnam(@resource.name)
85
+ return ent.sp_min
86
+ end
87
+ end
88
+ :absent
89
+ end
90
+
91
+ def max_age
92
+ if Puppet.features.libshadow?
93
+ if ent = Shadow::Passwd.getspnam(@resource.name)
94
+ return ent.sp_max
95
+ end
96
+ end
97
+ :absent
98
+ end
99
+
59
100
  # Retrieve the password using the Shadow Password library
60
101
  def password
61
102
  if Puppet.features.libshadow?