puppet 6.0.0 → 6.0.1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -4
  3. data/lib/puppet/application/apply.rb +99 -59
  4. data/lib/puppet/application/cert.rb +2 -112
  5. data/lib/puppet/configurer.rb +2 -3
  6. data/lib/puppet/defaults.rb +14 -1
  7. data/lib/puppet/etc.rb +20 -0
  8. data/lib/puppet/module/task.rb +29 -38
  9. data/lib/puppet/parser/catalog_compiler.rb +24 -0
  10. data/lib/puppet/parser/compiler.rb +3 -1
  11. data/lib/puppet/pops/evaluator/deferred_resolver.rb +3 -0
  12. data/lib/puppet/pops/evaluator/runtime3_converter.rb +2 -2
  13. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +18 -10
  14. data/lib/puppet/pops/loader/task_instantiator.rb +13 -70
  15. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  16. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  17. data/lib/puppet/pops/pcore.rb +10 -33
  18. data/lib/puppet/pops/serialization.rb +1 -0
  19. data/lib/puppet/pops/serialization/to_data_converter.rb +9 -1
  20. data/lib/puppet/provider/exec.rb +57 -57
  21. data/lib/puppet/provider/group/aix.rb +1 -15
  22. data/lib/puppet/provider/group/pw.rb +4 -8
  23. data/lib/puppet/provider/group/windows_adsi.rb +7 -4
  24. data/lib/puppet/provider/nameservice.rb +1 -25
  25. data/lib/puppet/provider/nameservice/directoryservice.rb +5 -3
  26. data/lib/puppet/provider/package/portage.rb +2 -2
  27. data/lib/puppet/provider/service/launchd.rb +19 -3
  28. data/lib/puppet/provider/user/aix.rb +48 -2
  29. data/lib/puppet/type/group.rb +62 -18
  30. data/lib/puppet/type/schedule.rb +7 -0
  31. data/lib/puppet/util/execution.rb +14 -1
  32. data/lib/puppet/util/posix.rb +15 -0
  33. data/lib/puppet/util/storage.rb +12 -0
  34. data/lib/puppet/util/windows/adsi.rb +60 -1
  35. data/lib/puppet/util/windows/process.rb +16 -1
  36. data/lib/puppet/util/windows/service.rb +68 -26
  37. data/lib/puppet/version.rb +1 -1
  38. data/lib/puppet_pal.rb +36 -3
  39. data/locales/ja/puppet.po +598 -861
  40. data/locales/puppet.pot +197 -160
  41. data/man/man5/puppet.conf.5 +12 -1
  42. data/man/man8/puppet.8 +1 -1
  43. data/spec/integration/application/apply_spec.rb +4 -1
  44. data/spec/integration/util/windows/adsi_spec.rb +2 -1
  45. data/spec/unit/application/apply_spec.rb +14 -0
  46. data/spec/unit/configurer_spec.rb +11 -0
  47. data/spec/unit/etc_spec.rb +25 -0
  48. data/spec/unit/indirector/catalog/json_spec.rb +9 -3
  49. data/spec/unit/pops/evaluator/runtime3_converter_spec.rb +22 -4
  50. data/spec/unit/pops/loaders/loader_spec.rb +3 -10
  51. data/spec/unit/pops/loaders/loaders_spec.rb +30 -0
  52. data/spec/unit/pops/loaders/module_loaders_spec.rb +7 -7
  53. data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
  54. data/spec/unit/pops/serialization/to_from_hr_spec.rb +9 -0
  55. data/spec/unit/pops/types/task_spec.rb +42 -116
  56. data/spec/unit/provider/group/aix_spec.rb +0 -19
  57. data/spec/unit/provider/group/pw_spec.rb +0 -6
  58. data/spec/unit/provider/group/windows_adsi_spec.rb +34 -35
  59. data/spec/unit/provider/nameservice/directoryservice_spec.rb +2 -2
  60. data/spec/unit/provider/service/launchd_spec.rb +19 -0
  61. data/spec/unit/provider/user/aix_spec.rb +43 -2
  62. data/spec/unit/provider/user/windows_adsi_spec.rb +1 -4
  63. data/spec/unit/puppet_pal_2pec.rb +6 -6
  64. data/spec/unit/puppet_pal_catalog_spec.rb +58 -0
  65. data/spec/unit/task_spec.rb +50 -5
  66. data/spec/unit/type/group_spec.rb +111 -13
  67. data/spec/unit/util/execution_spec.rb +59 -0
  68. data/spec/unit/util/posix_spec.rb +28 -0
  69. data/spec/unit/util/storage_spec.rb +107 -0
  70. data/spec/unit/util/windows/adsi_spec.rb +100 -5
  71. data/spec/unit/util/windows/service_spec.rb +100 -43
  72. metadata +2 -2
@@ -4,71 +4,71 @@ require 'puppet/util/execution'
4
4
  class Puppet::Provider::Exec < Puppet::Provider
5
5
  include Puppet::Util::Execution
6
6
 
7
- def run(command, check = false)
8
- output = nil
9
- sensitive = resource.parameters[:command].sensitive
7
+ def environment
8
+ env = {}
10
9
 
11
- checkexe(command)
10
+ if (path = resource[:path])
11
+ env[:PATH] = path.join(File::PATH_SEPARATOR)
12
+ end
12
13
 
13
- if dir = resource[:cwd]
14
- unless File.directory?(dir)
15
- if check
16
- dir = nil
17
- else
18
- self.fail _("Working directory '%{dir}' does not exist") % { dir: dir }
19
- end
14
+ return env unless (envlist = resource[:environment])
15
+
16
+ envlist = [envlist] unless envlist.is_a? Array
17
+ envlist.each do |setting|
18
+ unless (match = /^(\w+)=((.|\n)+)$/.match(setting))
19
+ warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect }
20
+ next
21
+ end
22
+ var = match[1]
23
+ value = match[2]
24
+
25
+ if env.include?(var) || env.include?(var.to_sym)
26
+ warning _("Overriding environment setting '%{var}' with '%{value}'") % { var: var, value: value }
20
27
  end
28
+
29
+ env[var] = value
21
30
  end
22
31
 
23
- dir ||= Dir.pwd
32
+ env
33
+ end
34
+
35
+ def run(command, check = false)
36
+ output = nil
37
+ sensitive = resource.parameters[:command].sensitive
38
+
39
+ checkexe(command)
24
40
 
25
41
  debug "Executing#{check ? " check": ""} '#{sensitive ? '[redacted]' : command}'"
26
- begin
27
- # Do our chdir
28
- Dir.chdir(dir) do
29
- environment = {}
30
-
31
- environment[:PATH] = resource[:path].join(File::PATH_SEPARATOR) if resource[:path]
32
-
33
- if envlist = resource[:environment]
34
- envlist = [envlist] unless envlist.is_a? Array
35
- envlist.each do |setting|
36
- if setting =~ /^(\w+)=((.|\n)+)$/
37
- env_name = $1
38
- value = $2
39
- if environment.include?(env_name) || environment.include?(env_name.to_sym)
40
- warning _("Overriding environment setting '%{env_name}' with '%{value}'") % { env_name: env_name, value: value }
41
- end
42
- environment[env_name] = value
43
- else
44
- warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect }
45
- end
46
- end
47
- end
48
-
49
- # Ruby 2.1 and later interrupt execution in a way that bypasses error
50
- # handling by default. Passing Timeout::Error causes an exception to be
51
- # raised that can be rescued inside of the block by cleanup routines.
52
- #
53
- # This is backwards compatible all the way to Ruby 1.8.7.
54
- Timeout::timeout(resource[:timeout], Timeout::Error) do
55
- # note that we are passing "false" for the "override_locale" parameter, which ensures that the user's
56
- # default/system locale will be respected. Callers may override this behavior by setting locale-related
57
- # environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration.
58
- output = Puppet::Util::Execution.execute(command, :failonfail => false, :combine => true,
59
- :uid => resource[:user], :gid => resource[:group],
60
- :override_locale => false,
61
- :custom_environment => environment,
62
- :sensitive => sensitive)
63
- end
64
- # The shell returns 127 if the command is missing.
65
- if output.exitstatus == 127
66
- raise ArgumentError, output
67
- end
68
42
 
69
- end
70
- rescue Errno::ENOENT => detail
71
- self.fail Puppet::Error, detail.to_s, detail
43
+ # Ruby 2.1 and later interrupt execution in a way that bypasses error
44
+ # handling by default. Passing Timeout::Error causes an exception to be
45
+ # raised that can be rescued inside of the block by cleanup routines.
46
+ #
47
+ # This is backwards compatible all the way to Ruby 1.8.7.
48
+ Timeout::timeout(resource[:timeout], Timeout::Error) do
49
+ # If we're running a command that's meant to be a check on whether we should run
50
+ # our actual command (e.g. like a command passed to the :onlyif or :unless properties),
51
+ # then we should not set the cwd when executing the check's corresponding command.
52
+ cwd = check ? nil : resource[:cwd]
53
+ cwd ||= Dir.pwd
54
+
55
+ # note that we are passing "false" for the "override_locale" parameter, which ensures that the user's
56
+ # default/system locale will be respected. Callers may override this behavior by setting locale-related
57
+ # environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration.
58
+ output = Puppet::Util::Execution.execute(
59
+ command,
60
+ :failonfail => false,
61
+ :combine => true,
62
+ :cwd => cwd,
63
+ :uid => resource[:user], :gid => resource[:group],
64
+ :override_locale => false,
65
+ :custom_environment => environment(),
66
+ :sensitive => sensitive
67
+ )
68
+ end
69
+ # The shell returns 127 if the command is missing.
70
+ if output.exitstatus == 127
71
+ raise ArgumentError, output
72
72
  end
73
73
 
74
74
  # Return output twice as processstatus was returned before, but only exitstatus was ever called.
@@ -53,24 +53,10 @@ Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject d
53
53
 
54
54
  group_hash
55
55
  end
56
-
57
- # Define some Puppet Property => AIX Attribute (and vice versa)
58
- # conversion functions here. This is so we can unit test them.
59
-
60
- def members_to_users(members)
61
- return members unless members.is_a?(Array)
62
- members.join(',')
63
- end
64
-
65
- def users_to_members(users)
66
- users.split(',')
67
- end
68
56
  end
69
57
 
70
58
  mapping puppet_property: :members,
71
- aix_attribute: :users,
72
- property_to_attribute: method(:members_to_users),
73
- attribute_to_property: method(:users_to_members)
59
+ aix_attribute: :users
74
60
 
75
61
  numeric_mapping puppet_property: :gid,
76
62
  aix_attribute: :id
@@ -9,7 +9,10 @@ Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService:
9
9
  defaultfor :operatingsystem => [:freebsd, :dragonfly]
10
10
  confine :operatingsystem => [:freebsd, :dragonfly]
11
11
 
12
- options :members, :flag => "-M", :method => :mem
12
+ options :members,
13
+ :flag => "-M",
14
+ :method => :mem,
15
+ :unmunge => proc { |members| members.join(',') }
13
16
 
14
17
  verify :gid, _("GID must be an integer") do |value|
15
18
  value.is_a? Integer
@@ -26,9 +29,6 @@ Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService:
26
29
 
27
30
  if members = @resource.should(:members)
28
31
  unless members == :absent
29
- if members.is_a?(Array)
30
- members = members.join(",")
31
- end
32
32
  cmd << "-M" << members
33
33
  end
34
34
  end
@@ -39,10 +39,6 @@ Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService:
39
39
  end
40
40
 
41
41
  def modifycmd(param, value)
42
- # members may be an array, need a comma separated list
43
- if param == :members and value.is_a?(Array)
44
- value = value.join(",")
45
- end
46
42
  super(param, value)
47
43
  end
48
44
  end
@@ -22,10 +22,12 @@ Puppet::Type.type(:group).provide :windows_adsi do
22
22
 
23
23
  # Cannot use munge of the group property to canonicalize @should
24
24
  # since the default array_matching comparison is not commutative
25
-
26
- current_sids = current.map(&:sid)
27
25
  # dupes automatically weeded out when hashes built
28
- specified_sids = Puppet::Util::Windows::ADSI::Group.name_sid_hash(should).keys.to_a
26
+ current_members = Puppet::Util::Windows::ADSI::User.name_sid_hash(current)
27
+ specified_members = Puppet::Util::Windows::ADSI::User.name_sid_hash(should)
28
+
29
+ current_sids = current_members.keys.to_a
30
+ specified_sids = specified_members.keys.to_a
29
31
 
30
32
  if @resource[:auth_membership]
31
33
  current_sids.sort == specified_sids.sort
@@ -63,7 +65,8 @@ Puppet::Type.type(:group).provide :windows_adsi do
63
65
  end
64
66
 
65
67
  def members
66
- group.members
68
+ @members ||= Puppet::Util::Windows::ADSI::User.name_sid_hash(group.members)
69
+ @members.keys
67
70
  end
68
71
 
69
72
  def members=(members)
@@ -247,31 +247,7 @@ class Puppet::Provider::NameService < Puppet::Provider
247
247
  # The list of all groups the user is a member of. Different
248
248
  # user mgmt systems will need to override this method.
249
249
  def groups
250
- groups = []
251
-
252
- # Reset our group list
253
- Puppet::Etc.setgrent
254
-
255
- user = @resource[:name]
256
-
257
- # Now iterate across all of the groups, adding each one our
258
- # user is a member of
259
- while group = Puppet::Etc.getgrent
260
- members = group.mem
261
-
262
- groups << group.name if members.include? user
263
- end
264
-
265
- # We have to close the file, so each listing is a separate
266
- # reading of the file.
267
- Puppet::Etc.endgrent
268
-
269
- uniq_groups = groups.uniq
270
- if groups != uniq_groups
271
- debug("Removing any duplicate group entries")
272
- end
273
- # remove any double listed groups
274
- uniq_groups.join(",")
250
+ Puppet::Util::POSIX.groups_of(@resource[:name]).join(',')
275
251
  end
276
252
 
277
253
  # Convert the Etc struct into a hash.
@@ -116,7 +116,7 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe
116
116
  ds_value = input_hash[key]
117
117
  case ds_to_ns_attribute_map[ds_attribute]
118
118
  when :members
119
- ds_value = ds_value # only members uses arrays so far
119
+ ds_value = ds_value.join(',')
120
120
  when :gid, :uid
121
121
  # OS X stores objects like uid/gid as strings.
122
122
  # Try casting to an integer for these cases to be
@@ -344,8 +344,10 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe
344
344
 
345
345
  def set(param, value)
346
346
  self.class.validate(param, value)
347
- current_members = @property_value_cache_hash[:members]
348
347
  if param == :members
348
+ current_members = @property_value_cache_hash[:members].split(',')
349
+ value = value.split(',')
350
+
349
351
  # If we are meant to be authoritative for the group membership
350
352
  # then remove all existing members who haven't been specified
351
353
  # in the manifest.
@@ -409,7 +411,7 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe
409
411
  end
410
412
  if value != "" and not value.nil?
411
413
  if property == :members
412
- add_members(nil, value)
414
+ add_members(nil, value.split(','))
413
415
  else
414
416
  exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
415
417
  exec_arg_vector << ns_to_ds_attribute_map[property.intern]
@@ -117,7 +117,7 @@ Puppet::Type.type(:package).provide :portage, :parent => Puppet::Provider::Packa
117
117
  if match
118
118
  result_fields.zip(match.captures) do |field, value|
119
119
  # some fields can be empty or (null) (if we are not passed a category in the package name for instance)
120
- if value == '(null)'
120
+ if value == '(null)' || value == '<unset>'
121
121
  package_info[field] = nil
122
122
  elsif !value or value.empty?
123
123
  package_info[field] = nil
@@ -133,7 +133,7 @@ Puppet::Type.type(:package).provide :portage, :parent => Puppet::Provider::Packa
133
133
  end
134
134
 
135
135
  def qatom_output_format
136
- '"[%{CATEGORY}] [%{PN}] [%{PV}] [%[PR]] [%[SLOT]] [%[pfx]] [%[sfx]]"'
136
+ '"[%[CATEGORY]] [%[PN]] [%[PV]] [%[PR]] [%[SLOT]] [%[pfx]] [%[sfx]]"'
137
137
  end
138
138
 
139
139
  def qatom_result_format
@@ -196,6 +196,22 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
196
196
  Puppet::Util::Plist.read_plist_file(path)
197
197
  end
198
198
 
199
+ # Read overrides plist, retrying if necessary
200
+ def self.read_overrides
201
+ i = 1
202
+ overrides = nil
203
+ loop do
204
+ Puppet.debug(_("Reading overrides plist, attempt %{i}") % {i: i}) if i > 1
205
+ overrides = read_plist(launchd_overrides)
206
+ break unless overrides.nil?
207
+ raise Puppet::Error.new(_('Unable to read overrides plist, too many attempts')) if i == 20
208
+ Puppet.info(_('Overrides file could not be read, trying again.'))
209
+ Kernel.sleep(0.1)
210
+ i += 1
211
+ end
212
+ overrides
213
+ end
214
+
199
215
  # Clean out the @property_hash variable containing the cached list of services
200
216
  def flush
201
217
  @property_hash.clear
@@ -300,7 +316,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
300
316
  _, job_plist = plist_from_label(resource[:name])
301
317
  job_plist_disabled = job_plist["Disabled"] if job_plist.has_key?("Disabled")
302
318
 
303
- if FileTest.file?(self.class.launchd_overrides) and overrides = self.class.read_plist(self.class.launchd_overrides)
319
+ if FileTest.file?(self.class.launchd_overrides) and overrides = self.class.read_overrides
304
320
  if overrides.has_key?(resource[:name])
305
321
  if self.class.get_os_version < 14
306
322
  overrides_disabled = overrides[resource[:name]]["Disabled"] if overrides[resource[:name]].has_key?("Disabled")
@@ -324,7 +340,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
324
340
  # rather than dealing with launchctl as it is unable to change the Disabled flag
325
341
  # without actually loading/unloading the job.
326
342
  def enable
327
- overrides = self.class.read_plist(self.class.launchd_overrides)
343
+ overrides = self.class.read_overrides
328
344
  if self.class.get_os_version < 14
329
345
  overrides[resource[:name]] = { "Disabled" => false }
330
346
  else
@@ -334,7 +350,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do
334
350
  end
335
351
 
336
352
  def disable
337
- overrides = self.class.read_plist(self.class.launchd_overrides)
353
+ overrides = self.class.read_overrides
338
354
  if self.class.get_os_version < 14
339
355
  overrides[resource[:name]] = { "Disabled" => true }
340
356
  else
@@ -10,6 +10,7 @@
10
10
  # See https://puppet.com/docs/puppet/latest/provider_development.html
11
11
  # for more information
12
12
  require 'puppet/provider/aix_object'
13
+ require 'puppet/util/posix'
13
14
  require 'tempfile'
14
15
  require 'date'
15
16
 
@@ -76,13 +77,23 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
76
77
  # a String, etc. in the property. This routine does a final check to
77
78
  # ensure our value doesn't have whitespace before we convert it to
78
79
  # an attribute.
79
- def groups_to_groups(groups)
80
+ def groups_property_to_attribute(groups)
80
81
  if groups =~ /\s/
81
82
  raise ArgumentError, _("Invalid value %{groups}: Groups must be comma separated!") % { groups: groups }
82
83
  end
83
84
 
84
85
  groups
85
86
  end
87
+
88
+ # We do not directly use the groups attribute value because that will
89
+ # always include the primary group, even if our user is not one of its
90
+ # members. Instead, we retrieve our property value by parsing the etc/group file,
91
+ # which matches what we do on our other POSIX platforms like Linux and Solaris.
92
+ #
93
+ # See https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/group_security.htm
94
+ def groups_attribute_to_property(provider, _groups)
95
+ Puppet::Util::POSIX.groups_of(provider.resource[:name]).join(',')
96
+ end
86
97
  end
87
98
 
88
99
  mapping puppet_property: :comment,
@@ -99,7 +110,8 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
99
110
  attribute_to_property: method(:pgrp_to_gid)
100
111
 
101
112
  mapping puppet_property: :groups,
102
- property_to_attribute: method(:groups_to_groups)
113
+ property_to_attribute: method(:groups_property_to_attribute),
114
+ attribute_to_property: method(:groups_attribute_to_property)
103
115
 
104
116
  mapping puppet_property: :home
105
117
  mapping puppet_property: :shell
@@ -121,6 +133,24 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
121
133
  # properties + a getter for the attributes property).
122
134
  mk_resource_methods
123
135
 
136
+ # Setting the primary group (pgrp attribute) on AIX causes both the
137
+ # current and new primary groups to be included in our user's groups,
138
+ # which is undesirable behavior. Thus, this custom setter resets the
139
+ # 'groups' property back to its previous value after setting the primary
140
+ # group.
141
+ def gid=(value)
142
+ old_pgrp = gid
143
+ cur_groups = groups
144
+
145
+ set(:gid, value)
146
+
147
+ begin
148
+ self.groups = cur_groups
149
+ rescue Puppet::Error => detail
150
+ raise Puppet::Error, _("Could not reset the groups property back to %{cur_groups} after setting the primary group on %{resource}[%{name}]. This means that the previous primary group of %{old_pgrp} and the new primary group of %{new_pgrp} have been added to %{cur_groups}. You will need to manually reset the groups property if this is undesirable behavior. Detail: %{detail}") % { cur_groups: cur_groups, resource: @resource.class.name, name: @resource.name, old_pgrp: old_pgrp, new_pgrp: value, detail: detail }, detail.backtrace
151
+ end
152
+ end
153
+
124
154
  # Helper function that parses the password from the given
125
155
  # password filehandle. This is here to make testing easier
126
156
  # for #password since we cannot configure Mocha to mock out
@@ -213,6 +243,22 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
213
243
  def create
214
244
  super
215
245
 
246
+ # We specify the 'groups' AIX attribute in AixObject's create method
247
+ # when creating our user. However, this does not always guarantee that
248
+ # our 'groups' property is set to the right value. For example, the
249
+ # primary group will always be included in the 'groups' property. This is
250
+ # bad if we're explicitly managing the 'groups' property under inclusive
251
+ # membership, and we are not specifying the primary group in the 'groups'
252
+ # property value.
253
+ #
254
+ # Setting the groups property here a second time will ensure that our user is
255
+ # created and in the right state. Note that this is an idempotent operation,
256
+ # so if AixObject's create method already set it to the right value, then this
257
+ # will noop.
258
+ if (groups = @resource.should(:groups))
259
+ self.groups = groups
260
+ end
261
+
216
262
  if (password = @resource.should(:password))
217
263
  self.password = password
218
264
  end
@@ -1,6 +1,7 @@
1
1
  require 'etc'
2
2
  require 'facter'
3
3
  require 'puppet/property/keyvalue'
4
+ require 'puppet/property/list'
4
5
  require 'puppet/parameter/boolean'
5
6
 
6
7
  module Puppet
@@ -81,41 +82,84 @@ module Puppet
81
82
  end
82
83
  end
83
84
 
84
- newproperty(:members, :array_matching => :all, :required_features => :manages_members) do
85
+ newproperty(:members, :parent => Puppet::Property::List, :required_features => :manages_members) do
85
86
  desc "The members of the group. For platforms or directory services where group
86
87
  membership is stored in the group objects, not the users. This parameter's
87
88
  behavior can be configured with `auth_membership`."
88
89
 
89
- def change_to_s(currentvalue, newvalue)
90
- currentvalue = currentvalue.join(",") if currentvalue != :absent
91
- newvalue = newvalue.join(",")
92
- super(currentvalue, newvalue)
90
+ validate do |value|
91
+ unless value.is_a?(String)
92
+ raise ArgumentError, _("The members property must be specified as either an array of strings, or as a single string consisting of a comma-separated list of members")
93
+ end
94
+
95
+ if value.is_a?(Integer) || value =~ /^\d+$/
96
+ raise ArgumentError, _("User names must be provided, not UID numbers.")
97
+ end
98
+
99
+ if value.empty?
100
+ raise ArgumentError, _("User names must not be empty. If you want to specify \"no users\" pass an empty array")
101
+ end
102
+
103
+ if provider.respond_to?(:member_valid?)
104
+ return provider.member_valid?(value)
105
+ end
93
106
  end
94
107
 
95
- def insync?(current)
96
- if provider.respond_to?(:members_insync?)
97
- return provider.members_insync?(current, @should)
108
+ def inclusive?
109
+ @resource[:auth_membership]
110
+ end
111
+
112
+ def change_to_s(currentvalue, newvalue)
113
+ newvalue = newvalue.split(",") if newvalue != :absent
114
+
115
+ if provider.respond_to?(:members_to_s)
116
+ # for Windows ADSI
117
+ # de-dupe the "newvalue" when the sync event message is generated,
118
+ # due to final retrieve called after the resource has been modified
119
+ newvalue = provider.members_to_s(newvalue).split(',').uniq
98
120
  end
99
121
 
100
- super(current)
122
+ super(currentvalue, newvalue)
101
123
  end
102
124
 
103
- def is_to_s(currentvalue)
125
+ # override Puppet::Property::List#retrieve
126
+ def retrieve
104
127
  if provider.respond_to?(:members_to_s)
105
- currentvalue = '' if currentvalue.nil?
106
- currentvalue = currentvalue.is_a?(Array) ? currentvalue : currentvalue.split(',')
128
+ # Windows ADSI members returns SIDs, but retrieve needs names
129
+ # must return qualified names for SIDs for "is" value and puppet resource
130
+ return provider.members_to_s(provider.members).split(',')
131
+ end
132
+
133
+ super
134
+ end
107
135
 
108
- return provider.members_to_s(currentvalue)
136
+ # The members property should also accept a comma separated
137
+ # list of members (a String parameter) for backwards
138
+ # compatibility. Unfortunately, the List property would treat
139
+ # our comma separated list of members as a single-element Array.
140
+ # This override of should= ensures that a comma separated list of
141
+ # members is munged to an array of members, which is what we want.
142
+ # Note that we cannot use `munge` because that will pass in each
143
+ # array element instead of the entire array if the members property
144
+ # is specified as an array of members, which would cause each member
145
+ # to be munged into an array for that case. This is undesirable
146
+ # behavior.
147
+ def should=(values)
148
+ super(values)
149
+
150
+ if @should.length == 1 && @should.first.include?(delimiter)
151
+ @should = @should.first.split(delimiter)
109
152
  end
110
153
 
111
- super(currentvalue)
154
+ @should
112
155
  end
113
- alias :should_to_s :is_to_s
114
156
 
115
- validate do |value|
116
- if provider.respond_to?(:member_valid?)
117
- return provider.member_valid?(value)
157
+ def insync?(current)
158
+ if provider.respond_to?(:members_insync?)
159
+ return provider.members_insync?(current, @should)
118
160
  end
161
+
162
+ super(current)
119
163
  end
120
164
  end
121
165