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
@@ -31,7 +31,7 @@ module ModuleLoaders
31
31
  nil,
32
32
  puppet_lib, # may or may not have a 'lib' above 'puppet'
33
33
  'puppet_system',
34
- [:func_4x, :datatype] # only load ruby functions and types from "puppet"
34
+ [:func_4x, :func_3x, :datatype] # only load ruby functions and types from "puppet"
35
35
  )
36
36
  end
37
37
 
@@ -0,0 +1,62 @@
1
+ # The RubyLegacyFunctionInstantiator instantiates a Puppet::Functions::Function given the ruby source
2
+ # that calls Puppet::Functions.create_function.
3
+ #
4
+ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
5
+ # Produces an instance of the Function class with the given typed_name, or fails with an error if the
6
+ # given ruby source does not produce this instance when evaluated.
7
+ #
8
+ # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with
9
+ # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load
10
+ # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate
11
+ # @param ruby_code_string [String] ruby code in a string
12
+ #
13
+ # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader
14
+ #
15
+ def self.create(loader, typed_name, source_ref, ruby_code_string)
16
+ unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Parser\:\:Functions.*newfunction/m
17
+ raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.") % { source_ref: source_ref }
18
+ end
19
+ # make the private loader available in a binding to allow it to be passed on
20
+ loader_for_function = loader.private_loader
21
+ here = get_binding(loader_for_function)
22
+
23
+ # Avoid reloading the function if already loaded via one of the APIs that trigger 3x function loading
24
+ # Check if function is already loaded the 3x way (and obviously not the 4x way since we would not be here in the
25
+ # first place.
26
+ environment = Puppet.lookup(:current_environment)
27
+ func_info = Puppet::Parser::Functions.environment_module(environment).get_function_info(typed_name.name.to_sym)
28
+ if func_info.nil?
29
+ # This will to do the 3x loading and define the "function_<name>" and "real_function_<name>" methods
30
+ # in the anonymous module used to hold function definitions.
31
+ #
32
+ func_info = eval(ruby_code_string, here, source_ref, 1)
33
+
34
+ # Validate what was loaded
35
+ unless func_info.is_a?(Hash)
36
+ raise ArgumentError, _("The code loaded from %{source_ref} did not produce the expected 3x function info Hash when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class }
37
+ end
38
+ unless func_info[:name] == "function_#{typed_name.name()}"
39
+ raise ArgumentError, _("The code loaded from %{source_ref} produced mis-matched name, expected 'function_%{type_name}', got %{created_name}") % {
40
+ source_ref: source_ref, type_name: typed_name.name, created_name: func_info[:name] }
41
+ end
42
+ end
43
+
44
+ created = Puppet::Functions::Function3x.create_function(typed_name.name(), func_info, loader_for_function)
45
+
46
+ # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things
47
+ # when calling functions etc.
48
+ # It should be bound to global scope
49
+
50
+ # Sets closure scope to nil, to let it be picked up at runtime from Puppet.lookup(:global_scope)
51
+ # If function definition used the loader from the binding to create a new loader, that loader wins
52
+ created.new(nil, loader_for_function)
53
+ end
54
+
55
+ # Produces a binding where the given loader is bound as a local variable (loader_injected_arg). This variable can be used in loaded
56
+ # ruby code - e.g. to call Puppet::Function.create_loaded_function(:name, loader,...)
57
+ #
58
+ def self.get_binding(loader_injected_arg)
59
+ binding
60
+ end
61
+ private_class_method :get_binding
62
+ end
@@ -505,8 +505,8 @@ class Loaders
505
505
  nil
506
506
  else
507
507
  module_data.private_loader =
508
- if module_data.restrict_to_dependencies? && !Puppet[:tasks]
509
- create_loader_with_only_dependencies_visible(module_data)
508
+ if module_data.restrict_to_dependencies?
509
+ create_loader_with_dependencies_first(module_data)
510
510
  else
511
511
  create_loader_with_all_modules_visible(module_data)
512
512
  end
@@ -516,29 +516,13 @@ class Loaders
516
516
  private
517
517
 
518
518
  def create_loader_with_all_modules_visible(from_module_data)
519
- Puppet.debug{"ModuleLoader: module '#{from_module_data.name}' has unknown dependencies - it will have all other modules visible"}
520
-
521
519
  @loaders.add_loader_by_name(Loader::DependencyLoader.new(from_module_data.public_loader, "#{from_module_data.name} private", all_module_loaders()))
522
520
  end
523
521
 
524
- def create_loader_with_only_dependencies_visible(from_module_data)
525
- if from_module_data.unmet_dependencies?
526
- if Puppet[:strict] != :off
527
- msg = "ModuleLoader: module '#{from_module_data.name}' has unresolved dependencies" \
528
- " - it will only see those that are resolved." \
529
- " Use 'puppet module list --tree' to see information about modules"
530
- case Puppet[:strict]
531
- when :error
532
- raise LoaderError.new(msg)
533
- when :warning
534
- Puppet.warn_once(:unresolved_module_dependencies,
535
- "unresolved_dependencies_for_module_#{from_module_data.name}",
536
- msg)
537
- end
538
- end
539
- end
522
+ def create_loader_with_dependencies_first(from_module_data)
540
523
  dependency_loaders = from_module_data.dependency_names.collect { |name| @index[name].public_loader }
541
- @loaders.add_loader_by_name(Loader::DependencyLoader.new(from_module_data.public_loader, "#{from_module_data.name} private", dependency_loaders))
524
+ visible_loaders = dependency_loaders + (all_module_loaders() - dependency_loaders)
525
+ @loaders.add_loader_by_name(Loader::DependencyLoader.new(from_module_data.public_loader, "#{from_module_data.name} private", visible_loaders))
542
526
  end
543
527
  end
544
528
  end
@@ -98,8 +98,7 @@ module HeredocSupport
98
98
 
99
99
  # Use a new lexer instance configured with a sub-locator to enable correct positioning
100
100
  sublexer = self.class.new()
101
- locator = Locator::SubLocator.sub_locator(str,
102
- locator.file, heredoc_line, heredoc_offset, leading.length())
101
+ locator = Locator::SubLocator.new(locator, heredoc_line, heredoc_offset, leading.length())
103
102
 
104
103
  # Emit a token that provides the grammar with location information about the lines on which the heredoc
105
104
  # content is based.
@@ -189,7 +189,7 @@ class Lexer2
189
189
  ',' => lambda { emit(TOKEN_COMMA, @scanner.pos) },
190
190
  '[' => lambda do
191
191
  before = @scanner.pos
192
- if (before == 0 || @scanner.string[locator.char_offset(before)-1,1] =~ /[[:blank:]\r\n]+/)
192
+ if (before == 0 || locator.string[locator.char_offset(before)-1,1] =~ /[[:blank:]\r\n]+/)
193
193
  emit(TOKEN_LISTSTART, before)
194
194
  else
195
195
  emit(TOKEN_LBRACK, before)
@@ -48,6 +48,7 @@ class Checker4_0 < Evaluator::LiteralEvaluator
48
48
  # tree iterate the model, and call check for each element
49
49
  @path = []
50
50
  check(model)
51
+ internal_check_top_construct_in_module(model)
51
52
  model._pcore_all_contents(@path) { |element| check(element) }
52
53
  end
53
54
 
@@ -396,7 +397,7 @@ class Checker4_0 < Evaluator::LiteralEvaluator
396
397
  acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name})
397
398
  end
398
399
 
399
- internal_check_file_namespace(o, o.name, o.locator.file)
400
+ internal_check_file_namespace(o)
400
401
  internal_check_reserved_type_name(o, o.name)
401
402
  internal_check_future_reserved_word(o, o.name)
402
403
  end
@@ -535,18 +536,42 @@ class Checker4_0 < Evaluator::LiteralEvaluator
535
536
  NO_NAMESPACE = :no_namespace
536
537
  NO_PATH = :no_path
537
538
  BAD_MODULE_FILE = :bad_module_file
538
- def internal_check_file_namespace(o, name, file)
539
+
540
+ def internal_check_file_namespace(o)
541
+ file = o.locator.file
539
542
  return if file.nil? || file == '' #e.g. puppet apply -e '...'
540
543
 
541
544
  file_namespace = namespace_for_file(file)
542
545
  return if file_namespace == NO_NAMESPACE
543
546
 
544
547
  # Downcasing here because check is case-insensitive
545
- if file_namespace == BAD_MODULE_FILE || !name.downcase.start_with?(file_namespace)
546
- acceptor.accept(Issues::ILLEGAL_DEFINITION_LOCATION, o, {:name => name, :file => file})
548
+ if file_namespace == BAD_MODULE_FILE || !o.name.downcase.start_with?(file_namespace)
549
+ acceptor.accept(Issues::ILLEGAL_DEFINITION_LOCATION, o, {:name => o.name, :file => file})
550
+ end
551
+ end
552
+
553
+ def internal_check_top_construct_in_module(prog)
554
+ return unless prog.is_a?(Model::Program) && !prog.body.nil?
555
+
556
+ #Check that this is a module autoloaded file
557
+ file = prog.locator.file
558
+ return if file.nil?
559
+ return if namespace_for_file(file) == NO_NAMESPACE
560
+
561
+ body = prog.body
562
+ return if prog.body.is_a?(Model::Nop) #Ignore empty or comment-only files
563
+
564
+ if(body.is_a?(Model::BlockExpression))
565
+ body.statements.each { |s| acceptor.accept(Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION, s) unless valid_top_construct?(s) }
566
+ else
567
+ acceptor.accept(Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION, body) unless valid_top_construct?(body)
547
568
  end
548
569
  end
549
570
 
571
+ def valid_top_construct?(o)
572
+ o.is_a?(Model::Definition) && !o.is_a?(Model::NodeDefinition)
573
+ end
574
+
550
575
  # @api private
551
576
  class Puppet::Util::FileNamespaceAdapter < Puppet::Pops::Adaptable::Adapter
552
577
  attr_accessor :file_to_namespace
@@ -600,9 +625,9 @@ class Checker4_0 < Evaluator::LiteralEvaluator
600
625
 
601
626
  def is_parent_dir_of(parent_dir, child_dir)
602
627
  parent_dir_path = Pathname.new(parent_dir)
603
- clean_parent = parent_dir_path.cleanpath
628
+ clean_parent = parent_dir_path.cleanpath.to_s + File::SEPARATOR
604
629
 
605
- return child_dir.to_s.start_with?(clean_parent.to_s)
630
+ return child_dir.to_s.start_with?(clean_parent)
606
631
  end
607
632
 
608
633
  def dir_to_names(relative_path)
@@ -37,6 +37,7 @@ class ValidatorFactory_4_0 < Factory
37
37
  p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
38
38
  p[Issues::CLASS_NOT_VIRTUALIZABLE] = Puppet[:strict] == :off ? :warning : Puppet[:strict]
39
39
  p[Issues::ILLEGAL_DEFINITION_LOCATION] = :deprecation
40
+ p[Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION] = :deprecation
40
41
  p
41
42
  end
42
43
  end
@@ -12,9 +12,20 @@ module Puppet
12
12
  # @todo The node with an important message is not very clear.
13
13
  #
14
14
  class KeyValue < Property
15
+ class << self
16
+ # This is a class-level variable that child properties can override
17
+ # if they wish.
18
+ attr_accessor :log_only_changed_or_new_keys
19
+ end
20
+
21
+ self.log_only_changed_or_new_keys = false
15
22
 
16
23
  def hash_to_key_value_s(hash)
17
- hash.select { |k,v| true }.map { |pair| pair.join(separator) }.join(delimiter)
24
+ if self.class.log_only_changed_or_new_keys
25
+ hash = hash.select { |k, _| @changed_or_new_keys.include?(k) }
26
+ end
27
+
28
+ hash.map { |*pair| pair.join(separator) }.join(delimiter)
18
29
  end
19
30
 
20
31
  def should_to_s(should_value)
@@ -33,11 +44,19 @@ module Puppet
33
44
  @resource[membership] == :inclusive
34
45
  end
35
46
 
36
- def hashify(key_value_array)
37
- #turns string array into a hash
38
- key_value_array.inject({}) do |hash, key_value|
47
+ def hashify_should
48
+ # Puppet casts all should values to arrays. Thus, if the user
49
+ # passed in a hash for our property's should value, the should_value
50
+ # parameter will be a single element array so we just extract our value
51
+ # directly.
52
+ if ! @should.empty? && @should.first.is_a?(Hash)
53
+ return @should.first
54
+ end
55
+
56
+ # Here, should is an array of key/value pairs.
57
+ @should.inject({}) do |hash, key_value|
39
58
  tmp = key_value.split(separator)
40
- hash[tmp[0].intern] = tmp[1]
59
+ hash[tmp[0].strip.intern] = tmp[1]
41
60
  hash
42
61
  end
43
62
  end
@@ -53,11 +72,24 @@ module Puppet
53
72
  def should
54
73
  return nil unless @should
55
74
 
56
- members = hashify(@should)
75
+ members = hashify_should
57
76
  current = process_current_hash(retrieve)
58
77
 
59
78
  #shared keys will get overwritten by members
60
- current.merge(members)
79
+ should_value = current.merge(members)
80
+
81
+ # Figure out the keys that will actually change in our Puppet run.
82
+ # This lets us reduce the verbosity of Puppet's logging for instances
83
+ # of this class when we want to.
84
+ #
85
+ # NOTE: We use ||= here because we only need to compute the
86
+ # changed_or_new_keys once (since this property will only be synced once).
87
+ #
88
+ @changed_or_new_keys ||= should_value.keys.select do |key|
89
+ ! current.key?(key) || current[key] != should_value[key]
90
+ end
91
+
92
+ should_value
61
93
  end
62
94
 
63
95
  # @return [String] Returns a default separator of "="
@@ -84,12 +116,42 @@ module Puppet
84
116
 
85
117
  # Returns true if there is no _is_ value, else returns if _is_ is equal to _should_ using == as comparison.
86
118
  # @return [Boolean] whether the property is in sync or not.
87
- #
88
119
  def insync?(is)
89
120
  return true unless is
90
121
 
91
122
  (is == self.should)
92
123
  end
124
+
125
+ # We only accept an array of key/value pairs (strings), a single
126
+ # key/value pair (string) or a Hash as valid values for our property.
127
+ # Note that for an array property value, the 'value' passed into the
128
+ # block corresponds to the array element.
129
+ validate do |value|
130
+ unless value.is_a?(String) || value.is_a?(Hash)
131
+ raise ArgumentError, _("The %{name} property must be specified as a hash or an array of key/value pairs (strings)!") % { name: name }
132
+ end
133
+
134
+ next if value.is_a?(Hash)
135
+
136
+ unless value.include?("#{separator}")
137
+ raise ArgumentError, _("Key/value pairs must be separated by '%{separator}'") % {separator: separator}
138
+ end
139
+ end
140
+
141
+ # The validate step ensures that our passed-in value is
142
+ # either a String or a Hash. If our value's a string,
143
+ # then nothing else needs to be done. Otherwise, we need
144
+ # to stringify the hash's keys and values to match our
145
+ # internal representation of the property's value.
146
+ munge do |value|
147
+ next value if value.is_a?(String)
148
+
149
+ munged_value = value.to_a.map! do |hash_key, hash_value|
150
+ [hash_key.to_s.strip.to_sym, hash_value.to_s]
151
+ end
152
+
153
+ Hash[munged_value]
154
+ end
93
155
  end
94
156
  end
95
157
  end
@@ -0,0 +1,483 @@
1
+ # Common code for AIX user/group providers.
2
+ class Puppet::Provider::AixObject < Puppet::Provider
3
+ desc "Generic AIX resource provider"
4
+
5
+ # Class representing a MappedObject, which can either be an
6
+ # AIX attribute or a Puppet property. This class lets us
7
+ # write something like:
8
+ #
9
+ # attribute = mappings[:aix_attribute][:uid]
10
+ # attribute.name
11
+ # attribute.convert_property_value(uid)
12
+ #
13
+ # property = mappings[:puppet_property][:id]
14
+ # property.name
15
+ # property.convert_attribute_value(id)
16
+ #
17
+ # NOTE: This is an internal class specific to AixObject. It is
18
+ # not meant to be used anywhere else. That's why we do not have
19
+ # any validation code in here.
20
+ #
21
+ # NOTE: See the comments in the class-level mappings method to
22
+ # understand what we mean by pure and impure conversion functions.
23
+ #
24
+ # NOTE: The 'mapping' code, including this class, could possibly
25
+ # be moved to a separate module so that it can be re-used in some
26
+ # of our other providers. See PUP-9082.
27
+ class MappedObject
28
+ attr_reader :name
29
+
30
+ def initialize(name, conversion_fn, conversion_fn_code)
31
+ @name = name
32
+ @conversion_fn = conversion_fn
33
+ @conversion_fn_code = conversion_fn_code
34
+
35
+ return unless pure_conversion_fn?
36
+
37
+ # Our conversion function is pure, so we can go ahead
38
+ # and define it. This way, we can use this MappedObject
39
+ # at the class-level as well as at the instance-level.
40
+ define_singleton_method(@conversion_fn) do |value|
41
+ @conversion_fn_code.call(value)
42
+ end
43
+ end
44
+
45
+ def pure_conversion_fn?
46
+ @conversion_fn_code.arity == 1
47
+ end
48
+
49
+ # Sets our MappedObject's provider. This only makes sense
50
+ # if it has an impure conversion function. We will call this
51
+ # in the instance-level mappings method after the provider
52
+ # instance has been created to define our conversion function.
53
+ # Note that a MappedObject with an impure conversion function
54
+ # cannot be used at the class level.
55
+ def set_provider(provider)
56
+ define_singleton_method(@conversion_fn) do |value|
57
+ @conversion_fn_code.call(provider, value)
58
+ end
59
+ end
60
+ end
61
+
62
+ class << self
63
+ #-------------
64
+ # Mappings
65
+ # ------------
66
+
67
+ def mappings
68
+ return @mappings if @mappings
69
+
70
+ @mappings = {}
71
+ @mappings[:aix_attribute] = {}
72
+ @mappings[:puppet_property] = {}
73
+
74
+ @mappings
75
+ end
76
+
77
+ # Add a mapping from a Puppet property to an AIX attribute. The info must include:
78
+ #
79
+ # * :puppet_property -- The puppet property corresponding to this attribute
80
+ # * :aix_attribute -- The AIX attribute corresponding to this attribute. Defaults
81
+ # to puppet_property if this is not provided.
82
+ # * :property_to_attribute -- A lambda that converts a Puppet Property to an AIX attribute
83
+ # value. Defaults to the identity function if not provided.
84
+ # * :attribute_to_property -- A lambda that converts an AIX attribute to a Puppet property.
85
+ # Defaults to the identity function if not provided.
86
+ #
87
+ # NOTE: The lambdas for :property_to_attribute or :attribute_to_property can be 'pure'
88
+ # or 'impure'. A 'pure' lambda is one that needs only the value to do the conversion,
89
+ # while an 'impure' lambda is one that requires the provider instance along with the
90
+ # value. 'Pure' lambdas have the interface 'do |value| ...' while 'impure' lambdas have
91
+ # the interface 'do |provider, value| ...'.
92
+ #
93
+ # NOTE: 'Impure' lambdas are useful in case we need to generate more specific error
94
+ # messages or pass-in instance-specific command-line arguments.
95
+ def mapping(info = {})
96
+ identity_fn = lambda { |x| x }
97
+ info[:aix_attribute] ||= info[:puppet_property]
98
+ info[:property_to_attribute] ||= identity_fn
99
+ info[:attribute_to_property] ||= identity_fn
100
+
101
+ mappings[:aix_attribute][info[:puppet_property]] = MappedObject.new(
102
+ info[:aix_attribute],
103
+ :convert_property_value,
104
+ info[:property_to_attribute]
105
+ )
106
+ mappings[:puppet_property][info[:aix_attribute]] = MappedObject.new(
107
+ info[:puppet_property],
108
+ :convert_attribute_value,
109
+ info[:attribute_to_property]
110
+ )
111
+ end
112
+
113
+ # Creates a mapping from a purely numeric Puppet property to
114
+ # an attribute
115
+ def numeric_mapping(info = {})
116
+ property = info[:puppet_property]
117
+
118
+ # We have this validation here b/c not all numeric properties
119
+ # handle this at the property level (e.g. like the UID). Given
120
+ # that, we might as well go ahead and do this validation for all
121
+ # of our numeric properties. Doesn't hurt.
122
+ info[:property_to_attribute] = lambda do |value|
123
+ unless value.is_a?(Integer)
124
+ raise ArgumentError, _("Invalid value %{value}: %{property} must be an Integer!") % { value: value, property: property }
125
+ end
126
+
127
+ value.to_s
128
+ end
129
+
130
+ # AIX will do the right validation to ensure numeric attributes
131
+ # can't be set to non-numeric values, so no need for the extra clutter.
132
+ info[:attribute_to_property] = lambda do |value|
133
+ value.to_i
134
+ end
135
+
136
+ mapping(info)
137
+ end
138
+
139
+ #-------------
140
+ # Useful Class Methods
141
+ # ------------
142
+
143
+ # Defines the getter and setter methods for each Puppet property that's mapped
144
+ # to an AIX attribute. We define only a getter for the :attributes property.
145
+ #
146
+ # Provider subclasses should call this method after they've defined all of
147
+ # their <puppet_property> => <aix_attribute> mappings.
148
+ def mk_resource_methods
149
+ # Define the Getter methods for each of our properties + the attributes
150
+ # property
151
+ properties = [:attributes]
152
+ properties += mappings[:aix_attribute].keys
153
+ properties.each do |property|
154
+ # Define the getter
155
+ define_method(property) do
156
+ get(property)
157
+ end
158
+
159
+ # We have a custom setter for the :attributes property,
160
+ # so no need to define it.
161
+ next if property == :attributes
162
+
163
+ # Define the setter
164
+ define_method("#{property}=".to_sym) do |value|
165
+ set(property, value)
166
+ end
167
+ end
168
+ end
169
+
170
+ # This helper splits a list separated by sep into its corresponding
171
+ # items. Note that a key precondition here is that none of the items
172
+ # in the list contain sep.
173
+ #
174
+ # Let A be the return value. Then one of our postconditions is:
175
+ # A.join(sep) == list
176
+ #
177
+ # NOTE: This function is only used by the parse_colon_separated_list
178
+ # function below. It is meant to be an inner lambda. The reason it isn't
179
+ # here is so we avoid having to create a proc. object for the split_list
180
+ # lambda each time parse_colon_separated_list is invoked. This will happen
181
+ # quite often since it is used at the class level and at the instance level.
182
+ # Since this function is meant to be an inner lambda and thus not exposed
183
+ # anywhere else, we do not have any unit tests for it. These test cases are
184
+ # instead covered by the unit tests for parse_colon_separated_list
185
+ def split_list(list, sep)
186
+ return [""] if list.empty?
187
+
188
+ list.split(sep, -1)
189
+ end
190
+
191
+ # Parses a colon-separated list. Example includes something like:
192
+ # <item1>:<item2>:<item3>:<item4>
193
+ #
194
+ # Returns an array of the parsed items, e.g.
195
+ # [ <item1>, <item2>, <item3>, <item4> ]
196
+ #
197
+ # Note that colons inside items are escaped by #!
198
+ def parse_colon_separated_list(colon_list)
199
+ # ALGORITHM:
200
+ # Treat the colon_list as a list separated by '#!:' We will get
201
+ # something like:
202
+ # [ <chunk1>, <chunk2>, ... <chunkn> ]
203
+ #
204
+ # Each chunk is now a list separated by ':' and none of the items
205
+ # in each chunk contains an escaped ':'. Now, split each chunk on
206
+ # ':' to get:
207
+ # [ [<piece11>, ..., <piece1n>], [<piece21>, ..., <piece2n], ... ]
208
+ #
209
+ # Now note that <item1> = <piece11>, <item2> = <piece12> in our original
210
+ # list, and that <itemn> = <piece1n>#!:<piece21>. This is the main idea
211
+ # behind what our inject method is trying to do at the end, except that
212
+ # we replace '#!:' with ':' since the colons are no longer escaped.
213
+ chunks = split_list(colon_list, '#!:')
214
+ chunks.map! { |chunk| split_list(chunk, ':') }
215
+
216
+ chunks.inject do |accum, chunk|
217
+ left = accum.pop
218
+ right = chunk.shift
219
+
220
+ accum.push("#{left}:#{right}")
221
+ accum += chunk
222
+
223
+ accum
224
+ end
225
+ end
226
+
227
+ # Parses the AIX objects from the command output, returning an array of
228
+ # hashes with each hash having the following schema:
229
+ # {
230
+ # :name => <object_name>
231
+ # :attributes => <object_attributes>
232
+ # }
233
+ #
234
+ # Output should be of the form
235
+ # #name:<attr1>:<attr2> ...
236
+ # <name>:<value1>:<value2> ...
237
+ # #name:<attr1>:<attr2> ...
238
+ # <name>:<value1>:<value2> ...
239
+ #
240
+ # NOTE: We need to parse the colon-formatted output in case we have
241
+ # space-separated attributes (e.g. 'gecos'). ":" characters are escaped
242
+ # with a "#!".
243
+ def parse_aix_objects(output)
244
+ # Object names cannot begin with '#', so we are safe to
245
+ # split individual users this way. We do not have to worry
246
+ # about an empty list either since there is guaranteed to be
247
+ # at least one instance of an AIX object (e.g. at least one
248
+ # user or one group on the system).
249
+ _, *objects = output.chomp.split(/^#/)
250
+
251
+ objects.map! do |object|
252
+ attributes_line, values_line = object.chomp.split("\n")
253
+
254
+ attributes = parse_colon_separated_list(attributes_line.chomp)
255
+ attributes.map!(&:to_sym)
256
+
257
+ values = parse_colon_separated_list(values_line.chomp)
258
+
259
+ attributes_hash = Hash[attributes.zip(values)]
260
+
261
+ object_name = attributes_hash.delete(:name)
262
+
263
+ Hash[[[:name, object_name.to_s], [:attributes, attributes_hash]]]
264
+ end
265
+
266
+ objects
267
+ end
268
+
269
+ # Lists all instances of the given object, taking in an optional set
270
+ # of ia_module arguments. Returns an array of hashes, each hash
271
+ # having the schema
272
+ # {
273
+ # :name => <object_name>
274
+ # :id => <object_id>
275
+ # }
276
+ def list_all(ia_module_args = [])
277
+ cmd = [command(:list), '-c', *ia_module_args, '-a', 'id', 'ALL']
278
+ parse_aix_objects(execute(cmd)).to_a.map do |object|
279
+ name = object[:name]
280
+ id = object[:attributes].delete(:id)
281
+
282
+ Hash[[[:name, name,],[:id, id]]]
283
+ end
284
+ end
285
+
286
+ #-------------
287
+ # Provider API
288
+ # ------------
289
+
290
+ def instances
291
+ list_all.to_a.map! do |object|
292
+ new({ :name => object[:name] })
293
+ end
294
+ end
295
+ end
296
+
297
+ # Instantiate our mappings. These need to be at the instance-level
298
+ # since some of our mapped objects may have impure conversion functions
299
+ # that need our provider instance.
300
+ def mappings
301
+ return @mappings if @mappings
302
+
303
+ @mappings = {}
304
+ self.class.mappings.each do |type, mapped_objects|
305
+ @mappings[type] = {}
306
+ mapped_objects.each do |input, mapped_object|
307
+ if mapped_object.pure_conversion_fn?
308
+ # Our mapped_object has a pure conversion function so we
309
+ # can go ahead and use it as-is.
310
+ @mappings[type][input] = mapped_object
311
+ next
312
+ end
313
+
314
+ # Otherwise, we need to dup it and set its provider to our
315
+ # provider instance. The dup is necessary so that we do not
316
+ # touch the class-level mapped object.
317
+ @mappings[type][input] = mapped_object.dup
318
+ @mappings[type][input].set_provider(self)
319
+ end
320
+ end
321
+
322
+ @mappings
323
+ end
324
+
325
+ # Converts the given attributes hash to CLI args.
326
+ def attributes_to_args(attributes)
327
+ attributes.map do |attribute, value|
328
+ "#{attribute}=#{value}"
329
+ end
330
+ end
331
+
332
+ def ia_module_args
333
+ return [] unless @resource[:ia_load_module]
334
+ ["-R", @resource[:ia_load_module].to_s]
335
+ end
336
+
337
+ def lscmd
338
+ [self.class.command(:list), '-c'] + ia_module_args + [@resource[:name]]
339
+ end
340
+
341
+ def addcmd(attributes)
342
+ attribute_args = attributes_to_args(attributes)
343
+ [self.class.command(:add)] + ia_module_args + attribute_args + [@resource[:name]]
344
+ end
345
+
346
+ def deletecmd
347
+ [self.class.command(:delete)] + ia_module_args + [@resource[:name]]
348
+ end
349
+
350
+ def modifycmd(new_attributes)
351
+ attribute_args = attributes_to_args(new_attributes)
352
+ [self.class.command(:modify)] + ia_module_args + attribute_args + [@resource[:name]]
353
+ end
354
+
355
+ # Modifies the AIX object by setting its new attributes.
356
+ def modify_object(new_attributes)
357
+ execute(modifycmd(new_attributes))
358
+ object_info(true)
359
+ end
360
+
361
+ # Gets a Puppet property's value from object_info
362
+ def get(property)
363
+ return :absent unless exists?
364
+ object_info[property] || :absent
365
+ end
366
+
367
+ # Sets a mapped Puppet property's value.
368
+ def set(property, value)
369
+ aix_attribute = mappings[:aix_attribute][property]
370
+ modify_object(
371
+ { aix_attribute.name => aix_attribute.convert_property_value(value) }
372
+ )
373
+ rescue Puppet::ExecutionFailure => detail
374
+ raise Puppet::Error, _("Could not set %{property} on %{resource}[%{name}]: %{detail}") % { property: property, resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
375
+ end
376
+
377
+ # This routine validates our new attributes property value to ensure
378
+ # that it does not contain any Puppet properties.
379
+ def validate_new_attributes(new_attributes)
380
+ # Gather all of the <puppet property>, <aix attribute> conflicts to print
381
+ # them all out when we create our error message. This makes it easy for the
382
+ # user to update their manifest based on our error message.
383
+ conflicts = {}
384
+ mappings[:aix_attribute].each do |property, aix_attribute|
385
+ next unless new_attributes.key?(aix_attribute.name)
386
+
387
+ conflicts[:properties] ||= []
388
+ conflicts[:properties].push(property)
389
+
390
+ conflicts[:attributes] ||= []
391
+ conflicts[:attributes].push(aix_attribute.name)
392
+ end
393
+
394
+ return if conflicts.empty?
395
+
396
+ properties, attributes = conflicts.keys.map do |key|
397
+ conflicts[key].map! { |name| "'#{name}'" }.join(', ')
398
+ end
399
+
400
+ detail = _("attributes is setting the %{properties} properties via. the %{attributes} attributes, respectively! Please specify these property values in the resource declaration instead.") % { properties: properties, attributes: attributes }
401
+
402
+ raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }
403
+ end
404
+
405
+ # Modifies the attribute property. Note we raise an error if the user specified
406
+ # an AIX attribute corresponding to a Puppet property.
407
+ def attributes=(new_attributes)
408
+ validate_new_attributes(new_attributes)
409
+ modify_object(new_attributes)
410
+ rescue Puppet::ExecutionFailure => detail
411
+ raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
412
+ end
413
+
414
+ # Collects the current property values of all mapped properties +
415
+ # the attributes property.
416
+ def object_info(refresh = false)
417
+ return @object_info if @object_info && ! refresh
418
+ @object_info = nil
419
+
420
+ begin
421
+ output = execute(lscmd)
422
+ rescue Puppet::ExecutionFailure
423
+ Puppet.debug(_("aix.object_info(): Could not find %{resource}[%{name}]") % { resource: @resource.class.name, name: @resource.name })
424
+
425
+ return @object_info
426
+ end
427
+
428
+ # If lscmd succeeds, then output will contain our object's information.
429
+ # Thus, .parse_aix_objects will always return a single element array.
430
+ aix_attributes = self.class.parse_aix_objects(output).first[:attributes]
431
+ aix_attributes.each do |attribute, value|
432
+ @object_info ||= {}
433
+
434
+ # If our attribute has a Puppet property, then we store that. Else, we store it as part
435
+ # of our :attributes property hash
436
+ if (property = mappings[:puppet_property][attribute])
437
+ @object_info[property.name] = property.convert_attribute_value(value)
438
+ else
439
+ @object_info[:attributes] ||= {}
440
+ @object_info[:attributes][attribute] = value
441
+ end
442
+ end
443
+
444
+ @object_info
445
+ end
446
+
447
+ #-------------
448
+ # Methods that manage the ensure property
449
+ # ------------
450
+
451
+ # Check that the AIX object exists
452
+ def exists?
453
+ ! object_info.nil?
454
+ end
455
+
456
+ # Creates a new instance of the resource
457
+ def create
458
+ attributes = @resource.should(:attributes) || {}
459
+ validate_new_attributes(attributes)
460
+
461
+ mappings[:aix_attribute].each do |property, aix_attribute|
462
+ property_should = @resource.should(property)
463
+ next if property_should.nil?
464
+ attributes[aix_attribute.name] = aix_attribute.convert_property_value(property_should)
465
+ end
466
+
467
+ execute(addcmd(attributes))
468
+ rescue Puppet::ExecutionFailure => detail
469
+ raise Puppet::Error, _("Could not create %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
470
+ end
471
+
472
+ # Deletes this instance resource
473
+ def delete
474
+ execute(deletecmd)
475
+
476
+ # Recollect the object info so that our current properties reflect
477
+ # the actual state of the system. Otherwise, puppet resource reports
478
+ # the wrong info. at the end. Note that this should return nil.
479
+ object_info(true)
480
+ rescue Puppet::ExecutionFailure => detail
481
+ raise Puppet::Error, _("Could not delete %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace
482
+ end
483
+ end