puppet 5.5.18-universal-darwin → 5.5.19-universal-darwin

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +12 -11
  3. data/lib/puppet.rb +5 -2
  4. data/lib/puppet/application/filebucket.rb +13 -0
  5. data/lib/puppet/defaults.rb +60 -33
  6. data/lib/puppet/indirector/catalog/compiler.rb +8 -0
  7. data/lib/puppet/network/http/connection.rb +4 -0
  8. data/lib/puppet/network/http/pool.rb +5 -1
  9. data/lib/puppet/pops/evaluator/access_operator.rb +2 -2
  10. data/lib/puppet/pops/evaluator/evaluator_impl.rb +1 -1
  11. data/lib/puppet/pops/types/type_calculator.rb +24 -0
  12. data/lib/puppet/provider/group/groupadd.rb +19 -19
  13. data/lib/puppet/provider/mailalias/aliases.rb +1 -1
  14. data/lib/puppet/provider/package/apt.rb +14 -3
  15. data/lib/puppet/provider/package/dnfmodule.rb +9 -2
  16. data/lib/puppet/provider/package/dpkg.rb +13 -6
  17. data/lib/puppet/provider/package/fink.rb +20 -3
  18. data/lib/puppet/provider/package/openbsd.rb +13 -1
  19. data/lib/puppet/provider/package/pkg.rb +18 -5
  20. data/lib/puppet/provider/package/yum.rb +9 -5
  21. data/lib/puppet/provider/user/directoryservice.rb +30 -5
  22. data/lib/puppet/provider/user/useradd.rb +5 -6
  23. data/lib/puppet/ssl/certificate.rb +2 -1
  24. data/lib/puppet/ssl/certificate_authority.rb +6 -5
  25. data/lib/puppet/test/test_helper.rb +7 -0
  26. data/lib/puppet/transaction/resource_harness.rb +1 -1
  27. data/lib/puppet/type/file.rb +13 -0
  28. data/lib/puppet/type/package.rb +63 -9
  29. data/lib/puppet/util/plist.rb +6 -0
  30. data/lib/puppet/version.rb +1 -1
  31. data/locales/puppet.pot +100 -92
  32. data/man/man5/puppet.conf.5 +12 -6
  33. data/man/man8/puppet-agent.8 +1 -1
  34. data/man/man8/puppet-apply.8 +1 -1
  35. data/man/man8/puppet-ca.8 +1 -1
  36. data/man/man8/puppet-catalog.8 +1 -1
  37. data/man/man8/puppet-cert.8 +1 -1
  38. data/man/man8/puppet-certificate.8 +1 -1
  39. data/man/man8/puppet-certificate_request.8 +1 -1
  40. data/man/man8/puppet-certificate_revocation_list.8 +1 -1
  41. data/man/man8/puppet-config.8 +1 -1
  42. data/man/man8/puppet-describe.8 +1 -1
  43. data/man/man8/puppet-device.8 +1 -1
  44. data/man/man8/puppet-doc.8 +1 -1
  45. data/man/man8/puppet-epp.8 +1 -1
  46. data/man/man8/puppet-facts.8 +1 -1
  47. data/man/man8/puppet-filebucket.8 +16 -1
  48. data/man/man8/puppet-generate.8 +1 -1
  49. data/man/man8/puppet-help.8 +1 -1
  50. data/man/man8/puppet-key.8 +1 -1
  51. data/man/man8/puppet-lookup.8 +1 -1
  52. data/man/man8/puppet-man.8 +1 -1
  53. data/man/man8/puppet-master.8 +1 -1
  54. data/man/man8/puppet-module.8 +1 -1
  55. data/man/man8/puppet-node.8 +1 -1
  56. data/man/man8/puppet-parser.8 +1 -1
  57. data/man/man8/puppet-plugin.8 +1 -1
  58. data/man/man8/puppet-report.8 +1 -1
  59. data/man/man8/puppet-resource.8 +1 -1
  60. data/man/man8/puppet-script.8 +1 -1
  61. data/man/man8/puppet-status.8 +1 -1
  62. data/man/man8/puppet.8 +2 -2
  63. data/spec/fixtures/integration/provider/mailalias/aliases/test1 +1 -0
  64. data/spec/integration/indirector/facts/facter_spec.rb +4 -0
  65. data/spec/unit/application/apply_spec.rb +2 -12
  66. data/spec/unit/configurer/fact_handler_spec.rb +0 -4
  67. data/spec/unit/configurer_spec.rb +0 -3
  68. data/spec/unit/indirector/catalog/compiler_spec.rb +45 -26
  69. data/spec/unit/network/http/connection_spec.rb +17 -1
  70. data/spec/unit/network/http/pool_spec.rb +32 -0
  71. data/spec/unit/node_spec.rb +7 -4
  72. data/spec/unit/provider/group/groupadd_spec.rb +30 -1
  73. data/spec/unit/provider/package/apt_spec.rb +13 -2
  74. data/spec/unit/provider/package/aptitude_spec.rb +1 -0
  75. data/spec/unit/provider/package/dnfmodule_spec.rb +22 -0
  76. data/spec/unit/provider/package/dpkg_spec.rb +20 -4
  77. data/spec/unit/provider/package/openbsd_spec.rb +17 -0
  78. data/spec/unit/provider/package/pkg_spec.rb +13 -1
  79. data/spec/unit/provider/package/yum_spec.rb +50 -0
  80. data/spec/unit/provider/user/directoryservice_spec.rb +41 -0
  81. data/spec/unit/provider/user/useradd_spec.rb +7 -2
  82. data/spec/unit/puppet_pal_2pec.rb +3 -0
  83. data/spec/unit/ssl/certificate_authority_spec.rb +2 -3
  84. data/spec/unit/ssl/certificate_spec.rb +7 -0
  85. data/spec/unit/type/package_spec.rb +8 -0
  86. data/spec/unit/util/plist_spec.rb +20 -0
  87. metadata +2 -2
@@ -33,7 +33,7 @@ class Puppet::Network::HTTP::Pool
33
33
  reuse = false
34
34
  raise detail
35
35
  ensure
36
- if reuse
36
+ if reuse && http.started?
37
37
  release(site, http)
38
38
  else
39
39
  close_connection(site, http)
@@ -56,13 +56,17 @@ class Puppet::Network::HTTP::Pool
56
56
  end
57
57
 
58
58
  # Safely close a persistent connection.
59
+ # Don't try to close a connection that's already closed.
59
60
  #
60
61
  # @api private
61
62
  def close_connection(site, http)
63
+ return false unless http.started?
62
64
  Puppet.debug("Closing connection for #{site}")
63
65
  http.finish
66
+ true
64
67
  rescue => detail
65
68
  Puppet.log_exception(detail, _("Failed to close connection for %{site}: %{detail}") % { site: site, detail: detail })
69
+ nil
66
70
  end
67
71
 
68
72
  # Borrow and take ownership of a persistent connection. If a new
@@ -20,14 +20,14 @@ class AccessOperator
20
20
  @semantic = access_expression
21
21
  end
22
22
 
23
- def access (o, scope, *keys)
23
+ def access(o, scope, *keys)
24
24
  @@access_visitor.visit_this_2(self, o, scope, keys)
25
25
  end
26
26
 
27
27
  protected
28
28
 
29
29
  def access_Object(o, scope, keys)
30
- type = Puppet::Pops::Types::TypeCalculator.infer(o)
30
+ type = Puppet::Pops::Types::TypeCalculator.infer_callable_methods_t(o)
31
31
  if type.is_a?(Puppet::Pops::Types::TypeWithMembers)
32
32
  access_func = type['[]']
33
33
  return access_func.invoke(o, scope, keys) unless access_func.nil?
@@ -947,7 +947,7 @@ class EvaluatorImpl
947
947
  name = name.value # the string function name
948
948
 
949
949
  obj = receiver[0]
950
- receiver_type = Types::TypeCalculator.infer(obj)
950
+ receiver_type = Types::TypeCalculator.infer_callable_methods_t(obj)
951
951
  if receiver_type.is_a?(Types::TypeWithMembers)
952
952
  member = receiver_type[name]
953
953
  unless member.nil?
@@ -120,6 +120,30 @@ class TypeCalculator
120
120
  singleton.infer(o)
121
121
  end
122
122
 
123
+ # Infers a type if given object may have callable members, else returns nil.
124
+ # Caller must check for nil or if returned type supports members.
125
+ # This is a much cheaper call than doing a call to the general infer(o) method.
126
+ #
127
+ # @api private
128
+ def self.infer_callable_methods_t(o)
129
+ # If being a value that cannot have Pcore based methods callable from Puppet Language
130
+ if (o.is_a?(String) ||
131
+ o.is_a?(Numeric) ||
132
+ o.is_a?(TrueClass) ||
133
+ o.is_a?(FalseClass) ||
134
+ o.is_a?(Regexp) ||
135
+ o.instance_of?(Array) ||
136
+ o.instance_of?(Hash) ||
137
+ Types::PUndefType::DEFAULT.instance?(o)
138
+ )
139
+ return nil
140
+ end
141
+ # For other objects (e.g. PObjectType instances, and runtime types) full inference needed, since that will
142
+ # cover looking into the runtime type registry.
143
+ #
144
+ infer(o)
145
+ end
146
+
123
147
  # @api public
124
148
  def self.generalize(o)
125
149
  singleton.generalize(o)
@@ -28,25 +28,9 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe
28
28
  get(:gid)
29
29
  end
30
30
 
31
- def findgroup(key, value)
32
- group_file = "/etc/group"
33
- group_keys = ['group_name', 'password', 'gid', 'user_list']
34
- index = group_keys.index(key)
35
- File.open(group_file) do |f|
36
- f.each_line do |line|
37
- group = line.split(":")
38
- if group[index] == value
39
- f.close
40
- return group
41
- end
42
- end
43
- end
44
- false
45
- end
46
-
47
31
  def localgid
48
- group = findgroup('group_name', resource[:name])
49
- return group[2] if group
32
+ group = findgroup(:group_name, resource[:name])
33
+ return group[:gid] if group
50
34
  false
51
35
  end
52
36
 
@@ -56,7 +40,7 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe
56
40
  # to ensure consistent behaviour of the useradd provider when
57
41
  # using both useradd and luseradd
58
42
  if not @resource.allowdupe? and @resource.forcelocal?
59
- if @resource.should(:gid) and findgroup('gid', @resource.should(:gid).to_s)
43
+ if @resource.should(:gid) and findgroup(:gid, @resource.should(:gid).to_s)
60
44
  raise(Puppet::Error, _("GID %{resource} already exists, use allowdupe to force group creation") % { resource: @resource.should(:gid).to_s })
61
45
  end
62
46
  elsif @resource.allowdupe? and not @resource.forcelocal?
@@ -108,4 +92,20 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe
108
92
  [command(:delete), @resource[:name]]
109
93
  end
110
94
  end
95
+
96
+ private
97
+
98
+ def findgroup(key, value)
99
+ group_file = "/etc/group"
100
+ group_keys = [:group_name, :password, :gid, :user_list]
101
+ index = group_keys.index(key)
102
+ @group_content ||= File.read(group_file)
103
+ @group_content.each_line do |line|
104
+ group = line.split(":")
105
+ if group[index] == value
106
+ return Hash[group_keys.zip(group)]
107
+ end
108
+ end
109
+ false
110
+ end
111
111
  end
@@ -12,7 +12,7 @@ Puppet::Type.type(:mailalias).provide(
12
12
  record_line :aliases, :fields => %w{name recipient}, :separator => /\s*:\s*/, :block_eval => :instance do
13
13
  def post_parse(record)
14
14
  if record[:recipient]
15
- record[:recipient] = record[:recipient].split(/\s*,\s*/).collect { |d| d.gsub(/^['"]|['"]$/, '') }
15
+ record[:recipient] = record[:recipient].split(/\s*,\s*(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/).collect { |d| d.gsub(/^['"]|['"]$/, '') }
16
16
  end
17
17
  record
18
18
  end
@@ -70,7 +70,12 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
70
70
  cmd += install_options if @resource[:install_options]
71
71
  cmd << :install << str
72
72
 
73
- aptget(*cmd)
73
+ self.unhold if self.properties[:mark] == :hold
74
+ begin
75
+ aptget(*cmd)
76
+ ensure
77
+ self.hold if @resource[:mark] == :hold
78
+ end
74
79
  end
75
80
 
76
81
  # What's the latest package version available?
@@ -100,12 +105,18 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
100
105
 
101
106
  def uninstall
102
107
  self.run_preseed if @resource[:responsefile]
103
- aptget "-y", "-q", :remove, @resource[:name]
108
+ args = ['-y', '-q']
109
+ args << '--allow-change-held-packages' if self.properties[:mark] == :hold
110
+ args << :remove << @resource[:name]
111
+ aptget(*args)
104
112
  end
105
113
 
106
114
  def purge
107
115
  self.run_preseed if @resource[:responsefile]
108
- aptget '-y', '-q', :remove, '--purge', @resource[:name]
116
+ args = ['-y', '-q']
117
+ args << '--allow-change-held-packages' if self.properties[:mark] == :hold
118
+ args << :remove << '--purge' << @resource[:name]
119
+ aptget(*args)
109
120
  # workaround a "bug" in apt, that already removed packages are not purged
110
121
  super
111
122
  end
@@ -12,7 +12,7 @@ require 'puppet/provider/package'
12
12
 
13
13
  Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
14
14
 
15
- has_feature :installable, :uninstallable, :versionable
15
+ has_feature :installable, :uninstallable, :versionable, :supports_flavors
16
16
  #has_feature :upgradeable
17
17
  # it's not (yet) feasible to make this upgradeable since module streams don't
18
18
  # always have matching version types (i.e. idm has streams DL1 and client,
@@ -83,5 +83,12 @@ Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
83
83
  execute([command(:dnf), 'module', 'remove', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
84
84
  reset # reset module to the default stream
85
85
  end
86
- end
87
86
 
87
+ def flavor
88
+ @property_hash[:flavor]
89
+ end
90
+
91
+ def flavor=(value)
92
+ install if flavor != @resource.should(:flavor)
93
+ end
94
+ end
@@ -44,7 +44,7 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
44
44
  # Note: self:: is required here to keep these constants in the context of what will
45
45
  # eventually become this Puppet::Type::Package::ProviderDpkg class.
46
46
  self::DPKG_QUERY_FORMAT_STRING = %Q{'${Status} ${Package} ${Version}\\n'}
47
- self::FIELDS_REGEX = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$}
47
+ self::FIELDS_REGEX = %r{^'?(\S+) +(\S+) +(\S+) (\S+) (\S*)$}
48
48
  self::FIELDS= [:desired, :error, :status, :name, :ensure]
49
49
 
50
50
  # @param line [String] one line of dpkg-query output
@@ -67,7 +67,7 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
67
67
  elsif ['config-files', 'half-installed', 'unpacked', 'half-configured'].include?(hash[:status])
68
68
  hash[:ensure] = :absent
69
69
  end
70
- hash[:ensure] = :held if hash[:desired] == 'hold'
70
+ hash[:mark] = :hold if hash[:desired] == 'hold'
71
71
  else
72
72
  Puppet.debug("Failed to match dpkg-query line #{line.inspect}")
73
73
  end
@@ -83,8 +83,6 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
83
83
  end
84
84
  args = []
85
85
 
86
- # We always unhold when installing to remove any prior hold.
87
- self.unhold
88
86
 
89
87
  if @resource[:configfiles] == :keep
90
88
  args << '--force-confold'
@@ -93,7 +91,12 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
93
91
  end
94
92
  args << '-i' << file
95
93
 
96
- dpkg(*args)
94
+ self.unhold if self.properties[:mark] == :hold
95
+ begin
96
+ dpkg(*args)
97
+ ensure
98
+ self.hold if @resource[:mark] == :hold
99
+ end
97
100
  end
98
101
 
99
102
  def update
@@ -144,10 +147,14 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
144
147
  dpkg "--purge", @resource[:name]
145
148
  end
146
149
 
147
- def hold
150
+ def deprecated_hold
148
151
  if package_not_installed?
149
152
  self.install
150
153
  end
154
+ hold
155
+ end
156
+
157
+ def hold
151
158
  Tempfile.open('puppet_dpkg_set_selection') do |tmpfile|
152
159
  tmpfile.write("#{@resource[:name]} hold\n")
153
160
  tmpfile.flush
@@ -37,7 +37,12 @@ Puppet::Type.type(:package).provide :fink, :parent => :dpkg, :source => :dpkg do
37
37
 
38
38
  cmd << :install << str
39
39
 
40
- finkcmd(cmd)
40
+ self.unhold if self.properties[:mark] == :hold
41
+ begin
42
+ finkcmd(cmd)
43
+ ensure
44
+ self.hold if @resource[:mark] == :hold
45
+ end
41
46
  end
42
47
 
43
48
  # What's the latest package version available?
@@ -70,10 +75,22 @@ Puppet::Type.type(:package).provide :fink, :parent => :dpkg, :source => :dpkg do
70
75
  end
71
76
 
72
77
  def uninstall
73
- finkcmd "-y", "-q", :remove, @model[:name]
78
+ self.unhold if self.properties[:mark] == :hold
79
+ begin
80
+ finkcmd "-y", "-q", :remove, @model[:name]
81
+ rescue StandardError, LoadError => e
82
+ self.hold if self.properties[:mark] == :hold
83
+ raise e
84
+ end
74
85
  end
75
86
 
76
87
  def purge
77
- aptget '-y', '-q', 'remove', '--purge', @resource[:name]
88
+ self.unhold if self.properties[:mark] == :hold
89
+ begin
90
+ aptget '-y', '-q', 'remove', '--purge', @resource[:name]
91
+ rescue StandardError, LoadError => e
92
+ self.hold if self.properties[:mark] == :hold
93
+ raise e
94
+ end
78
95
  end
79
96
  end
@@ -20,6 +20,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
20
20
  has_feature :install_options
21
21
  has_feature :uninstall_options
22
22
  has_feature :upgradeable
23
+ has_feature :supports_flavors
23
24
 
24
25
  def self.instances
25
26
  packages = []
@@ -27,7 +28,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
27
28
  begin
28
29
  execpipe(listcmd) do |process|
29
30
  # our regex for matching pkg_info output
30
- regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/
31
+ regex = /^(.*)-(\d[^-]*)[-]?([\w-]*)(.*)$/
31
32
  fields = [:name, :ensure, :flavor ]
32
33
  hash = {}
33
34
 
@@ -239,4 +240,15 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
239
240
  def purge
240
241
  pkgdelete "-c", "-q", @resource[:name]
241
242
  end
243
+
244
+ def flavor
245
+ @property_hash[:flavor]
246
+ end
247
+
248
+ def flavor=(value)
249
+ if flavor != @resource.should(:flavor)
250
+ uninstall
251
+ install
252
+ end
253
+ end
242
254
  end
@@ -49,7 +49,7 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d
49
49
  ).merge(
50
50
  case flags[1..1]
51
51
  when 'f'
52
- {:ensure => 'held'}
52
+ {:mark => :hold}
53
53
  when '-'
54
54
  {}
55
55
  else
@@ -107,6 +107,10 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d
107
107
  end).merge({:provider => self.name})
108
108
  end
109
109
 
110
+ def deprecated_hold
111
+ hold
112
+ end
113
+
110
114
  def hold
111
115
  pkg(:freeze, @resource[:name])
112
116
  end
@@ -201,8 +205,6 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d
201
205
  def install(nofail = false)
202
206
  name = @resource[:name]
203
207
  should = @resource[:ensure]
204
- # always unhold if explicitly told to install/update
205
- self.unhold
206
208
  is = self.query
207
209
  if is[:ensure].to_sym == :absent
208
210
  command = 'install'
@@ -216,7 +218,12 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d
216
218
  unless should.is_a? Symbol
217
219
  name += "@#{should}"
218
220
  end
219
- r = exec_cmd(command(:pkg), command, *args, name)
221
+ self.unhold if self.properties[:mark] == :hold
222
+ begin
223
+ r = exec_cmd(command(:pkg), command, *args, name)
224
+ ensure
225
+ self.hold if @resource[:mark] == :hold
226
+ end
220
227
  return r if nofail
221
228
  raise Puppet::Error, _("Unable to update %{package}") % { package: r[:out] } if r[:exit] != 0
222
229
  end
@@ -230,7 +237,13 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d
230
237
  cmd << '-r'
231
238
  end
232
239
  cmd << @resource[:name]
233
- pkg cmd
240
+ self.unhold if self.properties[:mark] == :hold
241
+ begin
242
+ pkg cmd
243
+ rescue StandardError, LoadError => e
244
+ self.hold if self.properties[:mark] == :hold
245
+ raise e
246
+ end
234
247
  end
235
248
 
236
249
  # update the package to the latest version available
@@ -286,12 +286,16 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
286
286
  # @param key [String] The key to look for in all contained hashes
287
287
  # @return [Array<String>] All hash values with the given key.
288
288
  def scan_options(options, key)
289
- return [] if options.nil?
290
- options.inject([]) do |repos, opt|
291
- if opt.is_a? Hash and opt[key]
292
- repos << opt[key]
289
+ return [] unless options.is_a?(Enumerable)
290
+ values = options.map do | repo |
291
+ value = if repo.is_a?(String)
292
+ next unless repo.include?('=')
293
+ Hash[*repo.strip.split('=')] # make it a hash
294
+ else
295
+ repo
293
296
  end
294
- repos
297
+ value[key]
295
298
  end
299
+ values.compact.uniq
296
300
  end
297
301
  end
@@ -386,7 +386,7 @@ Puppet::Type.type(:user).provide :directoryservice do
386
386
  if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0)
387
387
  assert_full_pbkdf2_password
388
388
 
389
- sleep 2
389
+ sleep 3
390
390
  flush_dscl_cache
391
391
  users_plist = get_users_plist(@resource.name)
392
392
  shadow_hash_data = get_shadow_hash_data(users_plist)
@@ -403,7 +403,7 @@ Puppet::Type.type(:user).provide :directoryservice do
403
403
  if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0)
404
404
  assert_full_pbkdf2_password
405
405
 
406
- sleep 2
406
+ sleep 3
407
407
  flush_dscl_cache
408
408
  users_plist = get_users_plist(@resource.name)
409
409
  shadow_hash_data = get_shadow_hash_data(users_plist)
@@ -434,8 +434,8 @@ Puppet::Type.type(:user).provide :directoryservice do
434
434
  ['home', 'uid', 'gid', 'comment', 'shell'].each do |setter_method|
435
435
  define_method("#{setter_method}=") do |value|
436
436
  if @property_hash[setter_method.intern]
437
- if self.class.get_os_version == '10.14' && %w(home uid).include?(setter_method)
438
- raise Puppet::Error, "OS X version 10\.14 does not allow changing #{setter_method} using puppet"
437
+ if self.class.get_os_version.split('.').last.to_i >= 14 && %w(home uid).include?(setter_method)
438
+ raise Puppet::Error, "OS X version #{self.class.get_os_version} does not allow changing #{setter_method} using puppet"
439
439
  end
440
440
  begin
441
441
  dscl '.', '-change', "/Users/#{resource.name}", self.class.ns_to_ds_attribute_map[setter_method.intern], @property_hash[setter_method.intern], value
@@ -571,7 +571,32 @@ Puppet::Type.type(:user).provide :directoryservice do
571
571
  else
572
572
  users_plist['ShadowHashData'] = [binary_plist]
573
573
  end
574
- write_users_plist_to_disk(users_plist)
574
+ if Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.15') < 0
575
+ write_users_plist_to_disk(users_plist)
576
+ else
577
+ write_and_import_shadow_hash_data(users_plist['ShadowHashData'].first)
578
+ end
579
+ end
580
+
581
+ # This method writes the ShadowHashData plist in a temporary file,
582
+ # then imports it using dsimport. macOS versions 10.15 and newer do
583
+ # not support directly managing binary plists, so we have to use an
584
+ # intermediary.
585
+ # dsimport is an archaic utilitary with hard-to-find documentation
586
+ #
587
+ # See http://web.archive.org/web/20090106120111/http://support.apple.com/kb/TA21305?viewlocale=en_US
588
+ # for information regarding the dsimport syntax
589
+ def write_and_import_shadow_hash_data(data_plist)
590
+ Tempfile.create("dsimport_#{@resource.name}", :encoding => Encoding::ASCII) do |dsimport_file|
591
+ dsimport_file.write <<-DSIMPORT
592
+ 0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 2 dsAttrTypeStandard:RecordName base64:dsAttrTypeNative:ShadowHashData
593
+ #{@resource.name}:#{Base64.strict_encode64(data_plist)}
594
+ DSIMPORT
595
+ dsimport_file.flush
596
+ # Delete the user's existing ShadowHashData, since dsimport appends, not replaces
597
+ dscl('.', 'delete', "/Users/#{@resource.name}", 'ShadowHashData')
598
+ dsimport(dsimport_file.path, '/Local/Default', 'M')
599
+ end
575
600
  end
576
601
 
577
602
  # This method accepts an argument of a hex password hash, and base64