lib/puppet/resource_api.rb in puppet-resource_api-1.6.5 vs lib/puppet/resource_api.rb in puppet-resource_api-1.7.0

- old
+ new

@@ -17,58 +17,25 @@ class << self attr_accessor :warning_count end def register_type(definition) - raise Puppet::DevError, 'requires a hash as definition, not `%{other_type}`' % { other_type: definition.class } unless definition.is_a? Hash - raise Puppet::DevError, 'requires a `:name`' unless definition.key? :name - raise Puppet::DevError, 'requires `:attributes`' unless definition.key? :attributes - raise Puppet::DevError, '`:attributes` must be a hash, not `%{other_type}`' % { other_type: definition[:attributes].class } unless definition[:attributes].is_a?(Hash) - [:title, :provider, :alias, :audit, :before, :consume, :export, :loglevel, :noop, :notify, :require, :schedule, :stage, :subscribe, :tag].each do |name| - raise Puppet::DevError, 'must not define an attribute called `%{name}`' % { name: name.inspect } if definition[:attributes].key? name - end - if definition.key?(:title_patterns) && !definition[:title_patterns].is_a?(Array) - raise Puppet::DevError, '`:title_patterns` must be an array, not `%{other_type}`' % { other_type: definition[:title_patterns].class } - end + # Attempt to create a TypeDefinition from the input hash + # This will validate and throw if its not right + type_def = TypeDefinition.new(definition) - Puppet::ResourceApi::DataTypeHandling.validate_ensure(definition) - - definition[:features] ||= [] - supported_features = %w[supports_noop canonicalize remote_resource simple_get_filter].freeze - unknown_features = definition[:features] - supported_features - Puppet.warning("Unknown feature detected: #{unknown_features.inspect}") unless unknown_features.empty? - - # fixup desc/docs backwards compatibility - if definition.key? :docs - if definition[:desc] - raise Puppet::DevError, '`%{name}` has both `desc` and `docs`, prefer using `desc`' % { name: definition[:name] } - end - definition[:desc] = definition[:docs] - definition.delete(:docs) - end - Puppet.warning('`%{name}` has no documentation, add it using a `desc` key' % { name: definition[:name] }) unless definition.key? :desc - - # fixup any weird behavior ;-) - definition[:attributes].each do |name, attr| - next unless attr[:behavior] - if attr[:behaviour] - raise Puppet::DevError, "the '#{name}' attribute has both a `behavior` and a `behaviour`, only use one" - end - attr[:behaviour] = attr[:behavior] - attr.delete(:behavior) - end - # prepare the ruby module for the provider # this has to happen before Puppet::Type.newtype starts autoloading providers # it also needs to be guarded against the namespace already being defined by something # else to avoid ruby warnings unless Puppet::Provider.const_defined?(class_name_from_type_name(definition[:name]), false) Puppet::Provider.const_set(class_name_from_type_name(definition[:name]), Module.new) end Puppet::Type.newtype(definition[:name].to_sym) do - @docs = definition[:desc] + @docs = definition[:docs] + @type_definition = type_def # Keeps a copy of the provider around. Weird naming to avoid clashes with puppet's own `provider` member define_singleton_method(:my_provider) do @my_provider ||= Hash.new { |hash, key| hash[key] = Puppet::ResourceApi.load_provider(definition[:name]).new } @my_provider[Puppet::Util::NetworkDevice.current.class] @@ -78,22 +45,22 @@ def my_provider self.class.my_provider end define_singleton_method(:type_definition) do - @type_definition ||= TypeDefinition.new(definition) + @type_definition end def type_definition self.class.type_definition end if type_definition.feature?('remote_resource') apply_to_device end - def initialize(attributes) + define_method(:initialize) do |attributes| # $stderr.puts "A: #{attributes.inspect}" if attributes.is_a? Puppet::Resource @title = attributes.title @catalog = attributes.catalog sensitives = attributes.sensitive_parameters @@ -117,11 +84,11 @@ end # the `Puppet::Resource::Ral.find` method, when `instances` does not return a match, uses a Hash with a `:name` key to create # an "absent" resource. This is often hit by `puppet resource`. This needs to work, even if the namevar is not called `name`. # This bit here relies on the default `title_patterns` (see below) to match the title back to the first (and often only) namevar - if type_definition.attributes[:name].nil? && attributes[:title].nil? + if definition[:attributes][:name].nil? && attributes[:title].nil? attributes[:title] = attributes.delete(:name) if attributes[:title].nil? && !type_definition.namevars.empty? attributes[:title] = @title end end @@ -131,29 +98,15 @@ def name title end - def self.build_title(type_definition, resource_hash) - if type_definition.namevars.size > 1 - # use a MonkeyHash to allow searching in Puppet's RAL - Puppet::ResourceApi::MonkeyHash[type_definition.namevars.map { |attr| [attr, resource_hash[attr]] }] - else - resource_hash[type_definition.namevars[0]] - end - end - - def rsapi_title - @rsapi_title ||= self.class.build_title(type_definition, self) - @rsapi_title - end - def to_resource to_resource_shim(super) end - def to_resource_shim(resource) + define_method(:to_resource_shim) do |resource| resource_hash = Hash[resource.keys.map { |k| [k, resource[k]] }] resource_hash[:title] = resource.title ResourceShim.new(resource_hash, type_definition.name, type_definition.namevars, type_definition.attributes, catalog) end @@ -217,18 +170,12 @@ # customizations required by the Resource API applied. Under the hood, # this maps to the relevant DSL methods in Puppet::Type. See # https://puppet.com/docs/puppet/6.0/custom_types.html#reference-5883 # for details. send(param_or_property, name.to_sym, parent: parent) do - unless options[:type] - raise Puppet::DevError, "#{definition[:name]}.#{name} has no type" - end - if options[:desc] desc "#{options[:desc]} (a #{options[:type]})" - else - warn("#{definition[:name]}.#{name} has no docs") end # The initialize method is called when puppet core starts building up # type objects. The core passes in a hash of shape { resource: # #<Puppet::Type::TypeName> }. We use this to pass through the @@ -256,11 +203,11 @@ options, ) end end - def self.instances + define_singleton_method(:instances) do # puts 'instances' # force autoloading of the provider provider(type_definition.name) initial_fetch = if type_definition.feature?('simple_get_filter') @@ -273,44 +220,40 @@ type_definition.check_schema(resource_hash) # allow a :title from the provider to override the default result = if resource_hash.key? :title new(title: resource_hash[:title]) else - new(title: build_title(type_definition, resource_hash)) + new(title: resource_hash[type_definition.namevars.first]) end result.cache_current_state(resource_hash) result end end - def refresh_current_state + define_method(:refresh_current_state) do @rsapi_current_state = if type_definition.feature?('simple_get_filter') - my_provider.get(context, [rsapi_title]).find { |h| namevar_match?(h) } + my_provider.get(context, [title]).find { |h| namevar_match?(h) } else my_provider.get(context).find { |h| namevar_match?(h) } end if @rsapi_current_state type_definition.check_schema(@rsapi_current_state) strict_check(@rsapi_current_state) if type_definition.feature?('canonicalize') else - @rsapi_current_state = if rsapi_title.is_a? Hash - rsapi_title.dup - else - { title: rsapi_title } - end + @rsapi_current_state = { title: title } @rsapi_current_state[:ensure] = :absent if type_definition.ensurable? end end # Use this to set the current state from the `instances` method def cache_current_state(resource_hash) @rsapi_current_state = resource_hash strict_check(@rsapi_current_state) if type_definition.feature?('canonicalize') end - def retrieve + define_method(:retrieve) do refresh_current_state unless @rsapi_current_state Puppet.debug("Current State: #{@rsapi_current_state.inspect}") result = Puppet::Resource.new(self.class, title, parameters: @rsapi_current_state) @@ -320,17 +263,17 @@ raise_missing_attrs result end - def namevar_match?(item) + define_method(:namevar_match?) do |item| context.type.namevars.all? do |namevar| item[namevar] == @parameters[namevar].value if @parameters[namevar].respond_to? :value end end - def flush + define_method(:flush) do raise_missing_attrs # puts 'flush' # skip puppet's injected metaparams actual_params = @parameters.select { |k, _v| type_definition.attributes.key? k } @@ -344,11 +287,11 @@ Puppet.debug("Target State: #{target_state.inspect}") # enforce init_only attributes if Puppet.settings[:strict] != :off && @rsapi_current_state && (@rsapi_current_state[:ensure] == 'present' && target_state[:ensure] == 'present') target_state.each do |name, value| - next unless type_definition.attributes[name][:behaviour] == :init_only && value != @rsapi_current_state[name] + next unless definition[:attributes][name][:behaviour] == :init_only && value != @rsapi_current_state[name] message = "Attempting to change `#{name}` init_only attribute value from `#{@rsapi_current_state[name]}` to `#{value}`" case Puppet.settings[:strict] when :warning Puppet.warning(message) when :error @@ -356,31 +299,31 @@ end end end if type_definition.feature?('supports_noop') - my_provider.set(context, { rsapi_title => { is: @rsapi_current_state, should: target_state } }, noop: noop?) + my_provider.set(context, { title => { is: @rsapi_current_state, should: target_state } }, noop: noop?) else - my_provider.set(context, rsapi_title => { is: @rsapi_current_state, should: target_state }) unless noop? + my_provider.set(context, title => { is: @rsapi_current_state, should: target_state }) unless noop? end raise 'Execution encountered an error' if context.failed? # remember that we have successfully reached our desired state @rsapi_current_state = target_state end - def raise_missing_attrs + define_method(:raise_missing_attrs) do error_msg = "The following mandatory attributes were not provided:\n * " + @missing_attrs.join(", \n * ") raise Puppet::ResourceError, error_msg if @missing_attrs.any? && (value(:ensure) != :absent && !value(:ensure).nil?) end - def raise_missing_params + define_method(:raise_missing_params) do error_msg = "The following mandatory parameters were not provided:\n * " + @missing_params.join(", \n * ") raise Puppet::ResourceError, error_msg end - def strict_check(current_state) + define_method(:strict_check) do |current_state| return if Puppet.settings[:strict] == :off # if strict checking is on we must notify if the values are changed by canonicalize # make a deep copy to perform the operation on and to compare against later state_clone = Marshal.load(Marshal.dump(current_state)) @@ -390,11 +333,11 @@ return unless state_clone && (current_state != state_clone) #:nocov: # codecov fails to register this multiline as covered, even though simplecov does. message = <<MESSAGE.strip -#{type_definition.name}[#{@title}]#get has not provided canonicalized values. +#{definition[:name]}[#{@title}]#get has not provided canonicalized values. Returned values: #{current_state.inspect} Canonicalized values: #{state_clone.inspect} MESSAGE #:nocov: @@ -403,23 +346,23 @@ Puppet.warning(message) when :error raise Puppet::DevError, message end - nil + return nil end define_singleton_method(:context) do @context ||= PuppetContext.new(definition) end def context self.class.context end - def self.title_patterns - @title_patterns ||= if type_definition.definition.key? :title_patterns - parse_title_patterns(type_definition.definition[:title_patterns]) + define_singleton_method(:title_patterns) do + @title_patterns ||= if definition.key? :title_patterns + parse_title_patterns(definition[:title_patterns]) else [[%r{(.*)}m, [[type_definition.namevars.first]]]] end end