puppet 5.5.6 → 5.5.7

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +12 -12
  4. data/Rakefile +9 -0
  5. data/lib/puppet/application.rb +5 -0
  6. data/lib/puppet/application/apply.rb +1 -0
  7. data/lib/puppet/application/master.rb +9 -7
  8. data/lib/puppet/application/script.rb +1 -1
  9. data/lib/puppet/defaults.rb +51 -31
  10. data/lib/puppet/etc.rb +20 -0
  11. data/lib/puppet/file_serving/fileset.rb +1 -1
  12. data/lib/puppet/functions.rb +123 -0
  13. data/lib/puppet/functions/new.rb +37 -53
  14. data/lib/puppet/functions/warning.rb +1 -1
  15. data/lib/puppet/loaders.rb +1 -0
  16. data/lib/puppet/parser/functions.rb +3 -1
  17. data/lib/puppet/parser/functions/sprintf.rb +12 -1
  18. data/lib/puppet/pops/evaluator/runtime3_converter.rb +16 -0
  19. data/lib/puppet/pops/evaluator/runtime3_support.rb +3 -4
  20. data/lib/puppet/pops/issues.rb +8 -0
  21. data/lib/puppet/pops/loader/loader.rb +2 -2
  22. data/lib/puppet/pops/loader/loader_paths.rb +3 -1
  23. data/lib/puppet/pops/loader/module_loaders.rb +1 -1
  24. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +62 -0
  25. data/lib/puppet/pops/loaders.rb +5 -21
  26. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  27. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  28. data/lib/puppet/pops/validation/checker4_0.rb +31 -6
  29. data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
  30. data/lib/puppet/property/keyvalue.rb +70 -8
  31. data/lib/puppet/provider/aix_object.rb +483 -0
  32. data/lib/puppet/provider/exec.rb +54 -57
  33. data/lib/puppet/provider/group/aix.rb +40 -115
  34. data/lib/puppet/provider/group/pw.rb +4 -8
  35. data/lib/puppet/provider/group/windows_adsi.rb +7 -4
  36. data/lib/puppet/provider/nameservice.rb +1 -25
  37. data/lib/puppet/provider/nameservice/directoryservice.rb +5 -3
  38. data/lib/puppet/provider/package/portage.rb +2 -2
  39. data/lib/puppet/provider/package/windows.rb +2 -2
  40. data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
  41. data/lib/puppet/provider/package/zypper.rb +1 -1
  42. data/lib/puppet/provider/service/launchd.rb +19 -3
  43. data/lib/puppet/provider/service/windows.rb +49 -40
  44. data/lib/puppet/provider/user/aix.rb +180 -246
  45. data/lib/puppet/provider/user/windows_adsi.rb +9 -1
  46. data/lib/puppet/resource/catalog.rb +1 -5
  47. data/lib/puppet/type/augeas.rb +1 -1
  48. data/lib/puppet/type/exec.rb +16 -14
  49. data/lib/puppet/type/file.rb +2 -2
  50. data/lib/puppet/type/file/source.rb +9 -5
  51. data/lib/puppet/type/group.rb +65 -23
  52. data/lib/puppet/type/k5login.rb +2 -2
  53. data/lib/puppet/type/notify.rb +1 -1
  54. data/lib/puppet/type/package.rb +3 -6
  55. data/lib/puppet/type/resources.rb +12 -2
  56. data/lib/puppet/type/schedule.rb +8 -1
  57. data/lib/puppet/type/selboolean.rb +2 -2
  58. data/lib/puppet/type/selmodule.rb +3 -4
  59. data/lib/puppet/type/service.rb +2 -5
  60. data/lib/puppet/type/tidy.rb +1 -1
  61. data/lib/puppet/type/user.rb +15 -20
  62. data/lib/puppet/type/yumrepo.rb +2 -2
  63. data/lib/puppet/type/zone.rb +2 -2
  64. data/lib/puppet/util.rb +7 -3
  65. data/lib/puppet/util/execution.rb +15 -1
  66. data/lib/puppet/util/posix.rb +15 -0
  67. data/lib/puppet/util/storage.rb +12 -0
  68. data/lib/puppet/util/windows.rb +4 -2
  69. data/lib/puppet/util/windows/adsi.rb +235 -205
  70. data/lib/puppet/util/windows/process.rb +23 -3
  71. data/lib/puppet/util/windows/security.rb +14 -0
  72. data/lib/puppet/util/windows/service.rb +977 -0
  73. data/lib/puppet/util/windows/user.rb +3 -5
  74. data/lib/puppet/version.rb +1 -1
  75. data/locales/ja/puppet.po +705 -374
  76. data/locales/puppet.pot +485 -261
  77. data/man/man5/puppet.conf.5 +36 -15
  78. data/man/man8/puppet-agent.8 +1 -1
  79. data/man/man8/puppet-apply.8 +1 -1
  80. data/man/man8/puppet-ca.8 +1 -1
  81. data/man/man8/puppet-catalog.8 +1 -1
  82. data/man/man8/puppet-cert.8 +1 -1
  83. data/man/man8/puppet-certificate.8 +1 -1
  84. data/man/man8/puppet-certificate_request.8 +1 -1
  85. data/man/man8/puppet-certificate_revocation_list.8 +1 -1
  86. data/man/man8/puppet-config.8 +1 -1
  87. data/man/man8/puppet-describe.8 +1 -1
  88. data/man/man8/puppet-device.8 +1 -1
  89. data/man/man8/puppet-doc.8 +1 -1
  90. data/man/man8/puppet-epp.8 +1 -1
  91. data/man/man8/puppet-facts.8 +1 -1
  92. data/man/man8/puppet-filebucket.8 +1 -1
  93. data/man/man8/puppet-generate.8 +1 -1
  94. data/man/man8/puppet-help.8 +1 -1
  95. data/man/man8/puppet-key.8 +1 -1
  96. data/man/man8/puppet-lookup.8 +1 -1
  97. data/man/man8/puppet-man.8 +1 -1
  98. data/man/man8/puppet-master.8 +1 -1
  99. data/man/man8/puppet-module.8 +1 -1
  100. data/man/man8/puppet-node.8 +1 -1
  101. data/man/man8/puppet-parser.8 +1 -1
  102. data/man/man8/puppet-plugin.8 +1 -1
  103. data/man/man8/puppet-report.8 +1 -1
  104. data/man/man8/puppet-resource.8 +1 -1
  105. data/man/man8/puppet-script.8 +1 -1
  106. data/man/man8/puppet-status.8 +1 -1
  107. data/man/man8/puppet.8 +2 -2
  108. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_input.out +1 -0
  109. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_output.out +1 -0
  110. data/spec/fixtures/unit/provider/user/aix/aix_passwd_file.out +32 -0
  111. data/spec/integration/parser/collection_spec.rb +4 -8
  112. data/spec/integration/provider/service/windows_spec.rb +5 -5
  113. data/spec/integration/type/file_spec.rb +6 -6
  114. data/spec/integration/util/windows/adsi_spec.rb +6 -5
  115. data/spec/integration/util/windows/security_spec.rb +10 -7
  116. data/spec/integration/util/windows/user_spec.rb +37 -17
  117. data/spec/spec_helper.rb +0 -1
  118. data/spec/unit/application/apply_spec.rb +41 -2
  119. data/spec/unit/application/master_spec.rb +7 -0
  120. data/spec/unit/application_spec.rb +21 -3
  121. data/spec/unit/defaults_spec.rb +20 -0
  122. data/spec/unit/etc_spec.rb +25 -0
  123. data/spec/unit/file_serving/fileset_spec.rb +11 -11
  124. data/spec/unit/gettext/config_spec.rb +1 -1
  125. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +6 -6
  126. data/spec/unit/pops/loaders/loaders_spec.rb +40 -7
  127. data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
  128. data/spec/unit/pops/validator/validator_spec.rb +129 -10
  129. data/spec/unit/property/keyvalue_spec.rb +97 -6
  130. data/spec/unit/provider/aix_object_spec.rb +805 -0
  131. data/spec/unit/provider/group/aix_spec.rb +57 -0
  132. data/spec/unit/provider/group/pw_spec.rb +0 -6
  133. data/spec/unit/provider/group/windows_adsi_spec.rb +34 -35
  134. data/spec/unit/provider/nameservice/directoryservice_spec.rb +2 -2
  135. data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
  136. data/spec/unit/provider/package/windows_spec.rb +4 -4
  137. data/spec/unit/provider/service/launchd_spec.rb +19 -0
  138. data/spec/unit/provider/service/windows_spec.rb +71 -78
  139. data/spec/unit/provider/user/aix_spec.rb +162 -116
  140. data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
  141. data/spec/unit/resource/catalog_spec.rb +2 -2
  142. data/spec/unit/ssl/certificate_authority_spec.rb +0 -1
  143. data/spec/unit/type/group_spec.rb +111 -13
  144. data/spec/unit/type/resources_spec.rb +18 -0
  145. data/spec/unit/util/execution_spec.rb +77 -0
  146. data/spec/unit/util/posix_spec.rb +28 -0
  147. data/spec/unit/util/storage_spec.rb +107 -0
  148. data/spec/unit/util/windows/adsi_spec.rb +108 -13
  149. data/spec/unit/util/windows/service_spec.rb +669 -0
  150. metadata +17 -5
  151. data/lib/puppet/provider/aixobject.rb +0 -392
  152. data/spec/unit/provider/aixobject_spec.rb +0 -101
@@ -42,17 +42,10 @@ class Puppet::Provider::Package::Windows
42
42
  end
43
43
 
44
44
  def self.install_command(resource)
45
- ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', munge(resource[:source])]
45
+ munge(resource[:source])
46
46
  end
47
47
 
48
48
  def uninstall_command
49
- # 1. Launch using cmd /c start because if the executable is a console
50
- # application Windows will automatically display its console window
51
- # 2. Specify a quoted title, otherwise if uninstall_string is quoted,
52
- # start will interpret that to be the title, and get confused
53
- # 3. Specify /w (wait) to wait for uninstall to finish
54
- command = ['cmd.exe', '/c', 'start', '"puppet-uninstall"', '/w']
55
-
56
49
  # Only quote bare uninstall strings, e.g.
57
50
  # C:\Program Files (x86)\Notepad++\uninstall.exe
58
51
  # Don't quote uninstall strings that are already quoted, e.g.
@@ -60,9 +53,9 @@ class Puppet::Provider::Package::Windows
60
53
  # Don't quote uninstall strings that contain arguments:
61
54
  # "C:\Program Files (x86)\Git\unins000.exe" /SILENT
62
55
  if uninstall_string =~ /\A[^"]*.exe\Z/i
63
- command << "\"#{uninstall_string}\""
56
+ command = "\"#{uninstall_string}\""
64
57
  else
65
- command << uninstall_string
58
+ command = uninstall_string
66
59
  end
67
60
 
68
61
  command
@@ -1,4 +1,4 @@
1
- Puppet::Type.type(:package).provide :zypper, :parent => :rpm do
1
+ Puppet::Type.type(:package).provide :zypper, :parent => :rpm, :source => :rpm do
2
2
  desc "Support for SuSE `zypper` package manager. Found in SLES10sp2+ and SLES11.
3
3
 
4
4
  This provider supports the `install_options` attribute, which allows command-line flags to be passed to zypper.
@@ -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
@@ -16,54 +16,57 @@ Puppet::Type.type(:service).provide :windows, :parent => :service do
16
16
 
17
17
  has_feature :refreshable
18
18
 
19
- commands :net => 'net.exe'
20
-
21
19
  def enable
22
- w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START )
23
- raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
20
+ Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_AUTO_START )
24
21
  rescue => detail
25
- raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}", detail )
22
+ raise Puppet::Error.new(_("Cannot enable %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail )
26
23
  end
27
24
 
28
25
  def disable
29
- w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED )
30
- raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? )
26
+ Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_DISABLED )
31
27
  rescue => detail
32
- raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}", detail )
28
+ raise Puppet::Error.new(_("Cannot disable %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail )
33
29
  end
34
30
 
35
31
  def manual_start
36
- w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START )
37
- raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
32
+ Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_DEMAND_START )
38
33
  rescue => detail
39
- raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}", detail )
34
+ raise Puppet::Error.new(_("Cannot enable %{resource_name} for manual start, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail )
40
35
  end
41
36
 
42
37
  def enabled?
43
- w32ss = Win32::Service.config_info( @resource[:name] )
44
- raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) )
45
- debug("Service #{@resource[:name]} start type is #{w32ss.start_type}")
46
- case w32ss.start_type
47
- when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START),
48
- Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START),
49
- Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START)
38
+ return :false unless Puppet::Util::Windows::Service.exists?(@resource[:name])
39
+
40
+ start_type = Puppet::Util::Windows::Service.service_start_type(@resource[:name])
41
+ debug("Service #{@resource[:name]} start type is #{start_type}")
42
+ case start_type
43
+ when :SERVICE_AUTO_START,
44
+ :SERVICE_BOOT_START,
45
+ :SERVICE_SYSTEM_START
50
46
  :true
51
- when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
47
+ when :SERVICE_DEMAND_START
52
48
  :manual
53
- when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
49
+ when :SERVICE_DISABLED
54
50
  :false
55
51
  else
56
- raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}")
52
+ raise Puppet::Error.new(_("Unknown start type: %{start_type}") % { start_type: start_type })
57
53
  end
58
54
  rescue => detail
59
- raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}", detail )
55
+ raise Puppet::Error.new(_("Cannot get start type %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail )
60
56
  end
61
57
 
62
58
  def start
59
+ if status == :paused
60
+ Puppet::Util::Windows::Service.resume(@resource[:name])
61
+ return
62
+ end
63
+
64
+ # status == :stopped here
65
+
63
66
  if enabled? == :false
64
67
  # If disabled and not managing enable, respect disabled and fail.
65
68
  if @resource[:enable].nil?
66
- raise Puppet::Error, "Will not start disabled service #{@resource[:name]} without managing enable. Specify 'enable => false' to override."
69
+ raise Puppet::Error.new(_("Will not start disabled service %{resource_name} without managing enable. Specify 'enable => false' to override.") % { resource_name: @resource[:name] })
67
70
  # Otherwise start. If enable => false, we will later sync enable and
68
71
  # disable the service again.
69
72
  elsif @resource[:enable] == :true
@@ -72,35 +75,41 @@ Puppet::Type.type(:service).provide :windows, :parent => :service do
72
75
  manual_start
73
76
  end
74
77
  end
75
-
76
- net(:start, @resource[:name])
77
- rescue Puppet::ExecutionFailure => detail
78
- raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}", detail )
78
+ Puppet::Util::Windows::Service.start(@resource[:name])
79
79
  end
80
80
 
81
81
  def stop
82
- net(:stop, @resource[:name])
83
- rescue Puppet::ExecutionFailure => detail
84
- raise Puppet::Error.new("Cannot stop #{@resource[:name]}, error was: #{detail}", detail )
82
+ Puppet::Util::Windows::Service.stop(@resource[:name])
85
83
  end
86
84
 
87
85
  def status
88
- w32ss = Win32::Service.status( @resource[:name] )
89
- raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) )
90
- state = case w32ss.current_state
91
- when "stopped", "pause pending", "stop pending", "paused" then :stopped
92
- when "running", "continue pending", "start pending" then :running
86
+ return :stopped unless Puppet::Util::Windows::Service.exists?(@resource[:name])
87
+
88
+ current_state = Puppet::Util::Windows::Service.service_state(@resource[:name])
89
+ state = case current_state
90
+ when :SERVICE_STOPPED,
91
+ :SERVICE_STOP_PENDING
92
+ :stopped
93
+ when :SERVICE_PAUSED,
94
+ :SERVICE_PAUSE_PENDING
95
+ :paused
96
+ when :SERVICE_RUNNING,
97
+ :SERVICE_CONTINUE_PENDING,
98
+ :SERVICE_START_PENDING
99
+ :running
93
100
  else
94
- raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'")
101
+ raise Puppet::Error.new(_("Unknown service state '%{current_state}' for service '%{resource_name}'") % { current_state: current_state, resource_name: @resource[:name] })
95
102
  end
96
- debug("Service #{@resource[:name]} is #{w32ss.current_state}")
103
+ debug("Service #{@resource[:name]} is #{current_state}")
97
104
  return state
98
- rescue => detail
99
- raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}", detail )
100
105
  end
101
106
 
102
107
  # returns all providers for all existing services and startup state
103
108
  def self.instances
104
- Win32::Service.services.collect { |s| new(:name => s.service_name) }
109
+ services = []
110
+ Puppet::Util::Windows::Service.services.each do |service_name, _|
111
+ services.push(new(:name => service_name))
112
+ end
113
+ services
105
114
  end
106
115
  end
@@ -1,25 +1,22 @@
1
- #
2
1
  # User Puppet provider for AIX. It uses standard commands to manage users:
3
2
  # mkuser, rmuser, lsuser, chuser
4
3
  #
5
4
  # Notes:
6
5
  # - AIX users can have expiry date defined with minute granularity,
7
- # but puppet does not allow it. There is a ticket open for that (#5431)
6
+ # but Puppet does not allow it. There is a ticket open for that (#5431)
7
+ #
8
8
  # - AIX maximum password age is in WEEKs, not days
9
9
  #
10
10
  # See https://puppet.com/docs/puppet/latest/provider_development.html
11
11
  # for more information
12
- #
13
- # Author:: Hector Rivas Gandara <keymon@gmail.com>
14
- #
15
- require 'puppet/provider/aixobject'
12
+ require 'puppet/provider/aix_object'
13
+ require 'puppet/util/posix'
16
14
  require 'tempfile'
17
15
  require 'date'
18
16
 
19
17
  Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
20
18
  desc "User management for AIX."
21
19
 
22
- # This will the default provider for this platform
23
20
  defaultfor :operatingsystem => :aix
24
21
  confine :operatingsystem => :aix
25
22
 
@@ -29,7 +26,6 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
29
26
  commands :delete => "/usr/sbin/rmuser"
30
27
  commands :modify => "/usr/bin/chuser"
31
28
 
32
- commands :lsgroup => "/usr/sbin/lsgroup"
33
29
  commands :chpasswd => "/bin/chpasswd"
34
30
 
35
31
  # Provider features
@@ -37,296 +33,234 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
37
33
  has_features :manages_homedir, :manages_passwords, :manages_shell
38
34
  has_features :manages_expiry, :manages_password_age
39
35
 
40
- # Attribute verification (TODO)
41
- #verify :gid, "GID must be a string or int of a valid group" do |value|
42
- # value.is_a? String || value.is_a? Integer
43
- #end
44
- #
45
- #verify :groups, "Groups must be comma-separated" do |value|
46
- # value !~ /\s/
47
- #end
36
+ class << self
37
+ def group_provider
38
+ @group_provider ||= Puppet::Type.type(:group).provider(:aix)
39
+ end
48
40
 
49
- # User attributes to ignore from AIX output.
50
- def self.attribute_ignore
51
- ["name"]
52
- end
41
+ # Define some Puppet Property => AIX Attribute (and vice versa)
42
+ # conversion functions here.
53
43
 
54
- # AIX attributes to properties mapping.
55
- #
56
- # Valid attributes to be managed by this provider.
57
- # It is a list with of hash
58
- # :aix_attr AIX command attribute name
59
- # :puppet_prop Puppet property name
60
- # :to Method to adapt puppet property to aix command value. Optional.
61
- # :from Method to adapt aix command value to puppet property. Optional
62
- self.attribute_mapping = [
63
- {:aix_attr => :pgrp, :puppet_prop => :gid,
64
- :to => :gid_to_attr,
65
- :from => :gid_from_attr },
66
- {:aix_attr => :id, :puppet_prop => :uid},
67
- {:aix_attr => :groups, :puppet_prop => :groups},
68
- {:aix_attr => :home, :puppet_prop => :home},
69
- {:aix_attr => :shell, :puppet_prop => :shell},
70
- {:aix_attr => :expires, :puppet_prop => :expiry,
71
- :to => :expiry_to_attr,
72
- :from => :expiry_from_attr },
73
- {:aix_attr => :maxage, :puppet_prop => :password_max_age},
74
- {:aix_attr => :minage, :puppet_prop => :password_min_age},
75
- {:aix_attr => :pwdwarntime, :puppet_prop => :password_warn_days},
76
- {:aix_attr => :attributes, :puppet_prop => :attributes},
77
- { :aix_attr => :gecos, :puppet_prop => :comment },
78
- ]
79
-
80
- #--------------
81
- # Command definition
82
-
83
- # Return the IA module arguments based on the resource param ia_load_module
84
- def get_ia_module_args
85
- if @resource[:ia_load_module]
86
- ["-R", @resource[:ia_load_module].to_s]
87
- else
88
- []
44
+ def gid_to_pgrp(provider, gid)
45
+ group = group_provider.find(gid, provider.ia_module_args)
46
+
47
+ group[:name]
89
48
  end
90
- end
91
49
 
92
- # List groups and Ids
93
- def lsgroupscmd(value=@resource[:name])
94
- [command(:lsgroup)] +
95
- self.get_ia_module_args +
96
- ["-a", "id", value]
97
- end
50
+ def pgrp_to_gid(provider, pgrp)
51
+ group = group_provider.find(pgrp, provider.ia_module_args)
98
52
 
99
- def lscmd(value=@resource[:name])
100
- [self.class.command(:list), "-c"] + self.get_ia_module_args + [ value]
101
- end
53
+ group[:gid]
54
+ end
102
55
 
103
- def lsallcmd()
104
- lscmd("ALL")
105
- end
56
+ def expiry_to_expires(expiry)
57
+ return '0' if expiry == "0000-00-00" || expiry.to_sym == :absent
58
+
59
+ DateTime.parse(expiry, "%Y-%m-%d %H:%M")
60
+ .strftime("%m%d%H%M%y")
61
+ end
62
+
63
+ def expires_to_expiry(provider, expires)
64
+ return :absent if expires == '0'
65
+
66
+ unless (match_obj = /\A(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)\z/.match(expires))
67
+ #TRANSLATORS 'AIX' is the name of an operating system and should not be translated
68
+ Puppet.warning(_("Could not convert AIX expires date '%{expires}' on %{class_name}[%{resource_name}]") % { expires: expires, class_name: provider.resource.class.name, resource_name: provider.resource.name })
69
+ return :absent
70
+ end
106
71
 
107
- def addcmd(extra_attrs = [])
108
- # Here we use the @resource.to_hash to get the list of provided parameters
109
- # Puppet does not call to self.<parameter>= method if it does not exists.
72
+ month, day, year = match_obj[1], match_obj[2], match_obj[-1]
73
+ return "20#{year}-#{month}-#{day}"
74
+ end
75
+
76
+ # We do some validation before-hand to ensure the value's an Array,
77
+ # a String, etc. in the property. This routine does a final check to
78
+ # ensure our value doesn't have whitespace before we convert it to
79
+ # an attribute.
80
+ def groups_property_to_attribute(groups)
81
+ if groups =~ /\s/
82
+ raise ArgumentError, _("Invalid value %{groups}: Groups must be comma separated!") % { groups: groups }
83
+ end
84
+
85
+ groups
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.
110
92
  #
111
- # It gets an extra list of arguments to add to the user.
112
- [self.class.command(:add)] + self.get_ia_module_args +
113
- self.hash2args(@resource.to_hash) +
114
- extra_attrs + [@resource[:name]]
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
115
97
  end
116
98
 
117
- # Get modify command. Set translate=false if no mapping must be used.
118
- # Needed for special properties like "attributes"
119
- def modifycmd(hash = property_hash)
120
- args = self.hash2args(hash)
121
- return nil if args.empty?
99
+ mapping puppet_property: :comment,
100
+ aix_attribute: :gecos
122
101
 
123
- [self.class.command(:modify)] + self.get_ia_module_args +
124
- args + [@resource[:name]]
125
- end
102
+ mapping puppet_property: :expiry,
103
+ aix_attribute: :expires,
104
+ property_to_attribute: method(:expiry_to_expires),
105
+ attribute_to_property: method(:expires_to_expiry)
126
106
 
127
- def deletecmd
128
- [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]]
129
- end
107
+ mapping puppet_property: :gid,
108
+ aix_attribute: :pgrp,
109
+ property_to_attribute: method(:gid_to_pgrp),
110
+ attribute_to_property: method(:pgrp_to_gid)
130
111
 
131
- #--------------
132
- # We overwrite the create function to change the password after creation.
133
- def create
134
- super
135
- # Reset the password if needed
136
- self.password = @resource[:password] if @resource[:password]
137
- end
112
+ mapping puppet_property: :groups,
113
+ property_to_attribute: method(:groups_property_to_attribute),
114
+ attribute_to_property: method(:groups_attribute_to_property)
138
115
 
116
+ mapping puppet_property: :home
117
+ mapping puppet_property: :shell
139
118
 
140
- def get_arguments(key, value, mapping, objectinfo)
141
- # In the case of attributes, return a list of key=vlaue
142
- if key == :attributes
143
- unless value and value.is_a? Hash
144
- raise Puppet::Error, _("Attributes must be a list of pairs key=value on %{class_name}[%{resource_name}]") %
145
- { class_name: @resource.class.name, resource_name: @resource.name }
146
- end
147
- return value.map { |k,v| k.to_s.strip + "=" + v.to_s.strip}
148
- end
119
+ numeric_mapping puppet_property: :uid,
120
+ aix_attribute: :id
149
121
 
150
- super(key, value, mapping, objectinfo)
151
- end
122
+ numeric_mapping puppet_property: :password_max_age,
123
+ aix_attribute: :maxage
152
124
 
153
- # Get the groupname from its id
154
- def groupname_by_id(gid)
155
- groupname=nil
156
- execute(lsgroupscmd("ALL")).each_line { |entry|
157
- attrs = self.parse_attr_list(entry, nil)
158
- if attrs and attrs.include? :id and gid == attrs[:id].to_i
159
- groupname = entry.split(" ")[0]
160
- end
161
- }
162
- groupname
163
- end
125
+ numeric_mapping puppet_property: :password_min_age,
126
+ aix_attribute: :minage
164
127
 
165
- # Get the groupname from its id
166
- def groupid_by_name(groupname)
167
- attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil)
168
- attrs ? attrs[:id].to_i : nil
169
- end
128
+ numeric_mapping puppet_property: :password_warn_days,
129
+ aix_attribute: :pwdwarntime
170
130
 
171
- # Check that a group exists and is valid
172
- def verify_group(value)
173
- if value.is_a? Integer
174
- groupname = groupname_by_id(value)
175
- #TRANSLATORS 'AIX' is the name of the operating system and should not be translated
176
- raise ArgumentError, _("AIX group must be a valid existing group") unless groupname
177
- else
178
- #TRANSLATORS 'AIX' is the name of the operating system and should not be translated
179
- raise ArgumentError, _("AIX group must be a valid existing group") unless groupid_by_name(value)
180
- groupname = value
181
- end
182
- groupname
183
- end
131
+ # Now that we have all of our mappings, let's go ahead and make
132
+ # the resource methods (property getters + setters for our mapped
133
+ # properties + a getter for the attributes property).
134
+ mk_resource_methods
184
135
 
185
- # The user's primary group. Can be specified numerically or by name.
186
- def gid_to_attr(value)
187
- verify_group(value)
188
- end
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
189
144
 
190
- # Get the group gid from its name
191
- def gid_from_attr(value)
192
- groupid_by_name(value)
193
- end
145
+ set(:gid, value)
194
146
 
195
- # The expiry date for this user. Must be provided in
196
- # a zero padded YYYY-MM-DD HH:MM format
197
- def expiry_to_attr(value)
198
- # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format
199
- # that is,"%m%d%H%M%y"
200
- newdate = '0'
201
- if value.is_a? String and value!="0000-00-00"
202
- d = DateTime.parse(value, "%Y-%m-%d %H:%M")
203
- newdate = d.strftime("%m%d%H%M%y")
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
204
151
  end
205
- newdate
206
152
  end
207
153
 
208
- def expiry_from_attr(value)
209
- if value =~ /(..)(..)(..)(..)(..)/
210
- #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}")
211
- #expiry_date = d.strftime("%Y-%m-%d %H:%M")
212
- #expiry_date = d.strftime("%Y-%m-%d")
213
- expiry_date = "20#{$5}-#{$1}-#{$2}"
214
- else
215
- unless value == '0'
216
- #TRANSLATORS 'AIX' is the name of an operating system and should not be translated
217
- Puppet.warn(_("Could not convert AIX expires date '%{value}' on %{class_name}[%{resource_name}]") %
218
- { value: value, class_name: @resource.class.name, resource_name: @resource.name })
219
- end
220
- expiry_date = :absent
154
+ # Helper function that parses the password from the given
155
+ # password filehandle. This is here to make testing easier
156
+ # for #password since we cannot configure Mocha to mock out
157
+ # a method and have it return a block's value, meaning we
158
+ # cannot test #password directly (not in a simple and obvious
159
+ # way, at least).
160
+ # @api private
161
+ def parse_password(f)
162
+ # From the docs, a user stanza is formatted as (newlines are explicitly
163
+ # stated here for clarity):
164
+ # <user>:\n
165
+ # <attribute1>=<value1>\n
166
+ # <attribute2>=<value2>\n
167
+ #
168
+ # First, find our user stanza
169
+ stanza = f.each_line.find { |line| line =~ /\A#{@resource[:name]}:/ }
170
+ return :absent unless stanza
171
+
172
+ # Now find the password line, if it exists. Note our call to each_line here
173
+ # will pick up right where we left off.
174
+ match_obj = nil
175
+ f.each_line.find do |line|
176
+ # Break if we find another user stanza. This means our user
177
+ # does not have a password.
178
+ break if line =~ /^\S+:$/
179
+
180
+ match_obj = /password = (\S+)/.match(line)
221
181
  end
222
- expiry_date
223
- end
182
+ return :absent unless match_obj
224
183
 
225
- def open_security_passwd
226
- # helper method for tests
227
- # AIX reference indicates this file is ASCII
228
- # https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/passwd_security.htm
229
- Puppet::FileSystem.open("/etc/security/passwd", nil, "r:ASCII")
184
+ match_obj[1]
230
185
  end
231
186
 
232
- #--------------------------------
233
- # Getter and Setter
234
- # When the provider is initialized, create getter/setter methods for each
235
- # property our resource type supports.
236
- # If setter or getter already defined it will not be overwritten
237
-
238
187
  #- **password**
239
188
  # The user's password, in whatever encrypted format the local machine
240
189
  # requires. Be sure to enclose any value that includes a dollar sign ($)
241
190
  # in single quotes ('). Requires features manages_passwords.
242
191
  #
243
- # Retrieve the password parsing directly the /etc/security/passwd
192
+ # Retrieve the password parsing the /etc/security/passwd file.
244
193
  def password
245
- password = :absent
246
- user = @resource[:name]
247
- f = open_security_passwd
248
- # Skip to the user
249
- f.each_line { |l| break if l =~ /^#{user}:\s*$/ }
250
- if ! f.eof?
251
- f.each_line { |l|
252
- # If there is a new user stanza, stop
253
- break if l =~ /^\S*:\s*$/
254
- # If the password= entry is found, return it, stripping trailing space
255
- if l =~ /^\s*password\s*=\s*(\S*)\s*$/
256
- password = $1; break;
257
- end
258
- }
194
+ # AIX reference indicates this file is ASCII
195
+ # https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/passwd_security.htm
196
+ Puppet::FileSystem.open("/etc/security/passwd", nil, "r:ASCII") do |f|
197
+ parse_password(f)
259
198
  end
260
- f.close()
261
- return password
262
199
  end
263
200
 
264
201
  def password=(value)
265
202
  user = @resource[:name]
266
203
 
267
- # Puppet execute does not support strings as input, only files.
268
- # The password is expected to be in an encrypted format given -e is specified:
269
- # https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.cmds1/chpasswd.htm
270
- # /etc/security/passwd is specified as an ASCII file per the AIX documentation
271
- tmpfile = Tempfile.new("puppet_#{user}_pw", :encoding => Encoding::ASCII)
272
- tmpfile << "#{user}:#{value}\n"
273
- tmpfile.close()
274
-
275
- # Options '-e', '-c', use encrypted password and clear flags
276
- # Must receive "user:enc_password" as input
277
- # command, arguments = {:failonfail => true, :combine => true}
278
- # Fix for bugs #11200 and #10915
279
- cmd = [self.class.command(:chpasswd), get_ia_module_args, '-e', '-c'].flatten
280
204
  begin
281
- output = execute(cmd, {:failonfail => false, :combine => true, :stdinfile => tmpfile.path })
282
- # chpasswd can return 1, even on success (at least on AIX 6.1); empty output indicates success
205
+ # Puppet execute does not support strings as input, only files.
206
+ # The password is expected to be in an encrypted format given -e is specified:
207
+ # https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.cmds1/chpasswd.htm
208
+ # /etc/security/passwd is specified as an ASCII file per the AIX documentation
209
+ tempfile = nil
210
+ tempfile = Tempfile.new("puppet_#{user}_pw", :encoding => Encoding::ASCII)
211
+ tempfile << "#{user}:#{value}\n"
212
+ tempfile.close()
213
+
214
+ # Options '-e', '-c', use encrypted password and clear flags
215
+ # Must receive "user:enc_password" as input
216
+ # command, arguments = {:failonfail => true, :combine => true}
217
+ # Fix for bugs #11200 and #10915
218
+ cmd = [self.class.command(:chpasswd), *ia_module_args, '-e', '-c']
219
+ execute_options = {
220
+ :failonfail => false,
221
+ :combine => true,
222
+ :stdinfile => tempfile.path
223
+ }
224
+ output = execute(cmd, execute_options)
225
+
226
+ # chpasswd can return 1, even on success (at least on AIX 6.1); empty output
227
+ # indicates success
283
228
  if output != ""
284
229
  raise Puppet::ExecutionFailure, "chpasswd said #{output}"
285
230
  end
286
231
  rescue Puppet::ExecutionFailure => detail
287
- raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace
232
+ raise Puppet::Error, "Could not set password on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace
288
233
  ensure
289
- tmpfile.delete()
234
+ if tempfile
235
+ # Extra close will noop. This is in case the write to our tempfile
236
+ # fails.
237
+ tempfile.close()
238
+ tempfile.delete()
239
+ end
290
240
  end
291
241
  end
292
242
 
293
- def managed_attribute_keys(hash)
294
- managed_attributes ||= @resource.original_parameters[:attributes] || hash.keys.map{|k| k.to_s}
295
- managed_attributes = [managed_attributes] unless managed_attributes.is_a?(Array)
296
- managed_attributes.map {|attr| key, _ = attr.split("="); key.strip.to_sym}
297
- end
298
-
299
- def should_include?(key, managed_keys)
300
- !self.class.attribute_mapping_from.include?(key) and
301
- !self.class.attribute_ignore.include?(key) and
302
- managed_keys.include?(key)
303
- end
304
-
305
- def filter_attributes(hash)
306
- # Return only managed attributes.
307
- managed_keys = managed_attribute_keys(hash)
308
- results = hash.select {
309
- |k,v| should_include?(k, managed_keys)
310
- }.inject({}) {
311
- |h, array| h[array[0]] = array[1]; h
312
- }
313
- results
314
- end
243
+ def create
244
+ super
315
245
 
316
- def attributes
317
- filter_attributes(getosinfo(false))
318
- end
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
319
261
 
320
- def attributes=(attr_hash)
321
- #self.class.validate(param, value)
322
- param = :attributes
323
- cmd = modifycmd({param => filter_attributes(attr_hash)})
324
- if cmd
325
- begin
326
- execute(cmd)
327
- rescue Puppet::ExecutionFailure => detail
328
- raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace
329
- end
262
+ if (password = @resource.should(:password))
263
+ self.password = password
330
264
  end
331
265
  end
332
266