Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(FM-7597) RSAPI Transport register function #146

Merged
merged 1 commit into from
Dec 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/puppet/resource_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require 'puppet/resource_api/property'
require 'puppet/resource_api/puppet_context' unless RUBY_PLATFORM == 'java'
require 'puppet/resource_api/read_only_parameter'
require 'puppet/resource_api/transport'
require 'puppet/resource_api/type_definition'
require 'puppet/resource_api/value_creator'
require 'puppet/resource_api/version'
Expand Down Expand Up @@ -451,6 +452,12 @@ def load_device_provider(class_name, type_name_sym, device_class_name, device_na
end
module_function :load_device_provider # rubocop:disable Style/AccessModifierDeclarations

# keeps the existing register API format. e.g. Puppet::ResourceApi.register_type
def register_transport(schema)
Puppet::ResourceApi::Transport.register(schema)
end
module_function :register_transport # rubocop:disable Style/AccessModifierDeclarations

def self.class_name_from_type_name(type_name)
type_name.to_s.split('_').map(&:capitalize).join
end
Expand Down
15 changes: 15 additions & 0 deletions lib/puppet/resource_api/transport.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Remote target transport API
module Puppet::ResourceApi::Transport
def register(schema)
raise Puppet::DevError, 'requires a hash as schema, not `%{other_type}`' % { other_type: schema.class } unless schema.is_a? Hash
raise Puppet::DevError, 'requires a `:name`' unless schema.key? :name
raise Puppet::DevError, 'requires `:desc`' unless schema.key? :desc
raise Puppet::DevError, 'requires `:connection_info`' unless schema.key? :connection_info
raise Puppet::DevError, '`:connection_info` must be a hash, not `%{other_type}`' % { other_type: schema[:connection_info].class } unless schema[:connection_info].is_a?(Hash)

@transports ||= {}
raise Puppet::DevError, 'Transport `%{name}` is already registered.' % { name: schema[:name] } unless @transports[schema[:name]].nil?
@transports[schema[:name]] = Puppet::ResourceApi::TransportSchemaDef.new(schema)
end
module_function :register # rubocop:disable Style/AccessModifierDeclarations
end
250 changes: 137 additions & 113 deletions lib/puppet/resource_api/type_definition.rb
Original file line number Diff line number Diff line change
@@ -1,144 +1,168 @@
# Provides accessor methods for the type being provided
class Puppet::ResourceApi::TypeDefinition
attr_reader :definition
module Puppet::ResourceApi
# pre-declare class
class BaseTypeDefinition; end

# RSAPI Resource Type
class TypeDefinition < BaseTypeDefinition
def initialize(definition)
super(definition, :attributes)
end

def initialize(definition)
@data_type_cache = {}
validate_schema(definition)
end
def ensurable?
attributes.key?(:ensure)
end

def name
@definition[:name]
end
# rubocop complains when this is named has_feature?
def feature?(feature)
supported = (definition[:features] && definition[:features].include?(feature))
if supported
Puppet.debug("#{definition[:name]} supports `#{feature}`")
else
Puppet.debug("#{definition[:name]} does not support `#{feature}`")
end
supported
end

def attributes
@definition[:attributes]
end
def validate_schema(definition, attr_key)
super(definition, attr_key)
[: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[attr_key].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

def ensurable?
@definition[:attributes].key?(:ensure)
end
Puppet::ResourceApi::DataTypeHandling.validate_ensure(definition)

def namevars
@namevars ||= @definition[:attributes].select { |_name, options|
options.key?(:behaviour) && options[:behaviour] == :namevar
}.keys
end
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?

# rubocop complains when this is named has_feature?
def feature?(feature)
supported = (definition[:features] && definition[:features].include?(feature))
if supported
Puppet.debug("#{definition[:name]} supports `#{feature}`")
else
Puppet.debug("#{definition[:name]} does not support `#{feature}`")
# store the validated definition
@definition = definition
end
supported
end

def validate_schema(definition)
raise Puppet::DevError, 'Type definition must be a Hash, not `%{other_type}`' % { other_type: definition.class } unless definition.is_a?(Hash)
raise Puppet::DevError, 'Type definition must have a name' unless definition.key? :name
raise Puppet::DevError, 'Type definition must have `:attributes`' unless definition.key? :attributes
unless definition[:attributes].is_a?(Hash)
raise Puppet::DevError, '`%{name}.attributes` must be a hash, not `%{other_type}`' % {
name: definition[:name], other_type: definition[:attributes].class
}
end
[: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 }
# RSAPI Transport schema
class TransportSchemaDef < BaseTypeDefinition
def initialize(definition)
super(definition, :connection_info)
end
end

Puppet::ResourceApi::DataTypeHandling.validate_ensure(definition)
# Base RSAPI schema Object
class BaseTypeDefinition
attr_reader :definition, :attributes

def initialize(definition, attr_key)
@data_type_cache = {}
validate_schema(definition, attr_key)
end

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?
def name
@definition[:name]
end

definition[:attributes].each do |key, attr|
raise Puppet::DevError, "`#{definition[:name]}.#{key}` must be a Hash, not a #{attr.class}" unless attr.is_a? Hash
raise Puppet::DevError, "`#{definition[:name]}.#{key}` has no type" unless attr.key? :type
Puppet.warning("`#{definition[:name]}.#{key}` has no docs") unless attr.key? :desc
def namevars
@namevars ||= attributes.select { |_name, options|
options.key?(:behaviour) && options[:behaviour] == :namevar
}.keys
end

# validate the type by attempting to parse into a puppet type
@data_type_cache[definition[:attributes][key][:type]] ||=
Puppet::ResourceApi::DataTypeHandling.parse_puppet_type(
key,
definition[:attributes][key][:type],
)
def validate_schema(definition, attr_key)
raise Puppet::DevError, '%{type_class} must be a Hash, not `%{other_type}`' % { type_class: self.class.name, other_type: definition.class } unless definition.is_a?(Hash)
@attributes = definition[attr_key]
raise Puppet::DevError, '%{type_class} must have a name' % { type_class: self.class.name } unless definition.key? :name
raise Puppet::DevError, '%{type_class} must have `%{attr_key}`' % { type_class: self.class.name, attrs: attr_key } unless definition.key? attr_key
unless attributes.is_a?(Hash)
raise Puppet::DevError, '`%{name}.%{attrs}` must be a hash, not `%{other_type}`' % {
name: definition[:name], attrs: attr_key, other_type: attributes.class
}
end

# fixup any weird behavior ;-)
next unless attr[:behavior]
if attr[:behaviour]
raise Puppet::DevError, "the '#{key}' attribute has both a `behavior` and a `behaviour`, only use one"
attributes.each do |key, attr|
raise Puppet::DevError, "`#{definition[:name]}.#{key}` must be a Hash, not a #{attr.class}" unless attr.is_a? Hash
raise Puppet::DevError, "`#{definition[:name]}.#{key}` has no type" unless attr.key? :type
Puppet.warning("`#{definition[:name]}.#{key}` has no docs") unless attr.key? :desc

# validate the type by attempting to parse into a puppet type
@data_type_cache[attributes[key][:type]] ||=
Puppet::ResourceApi::DataTypeHandling.parse_puppet_type(
key,
attributes[key][:type],
)

# fixup any weird behavior ;-)
next unless attr[:behavior]
if attr[:behaviour]
raise Puppet::DevError, "the '#{key}' attribute has both a `behavior` and a `behaviour`, only use one"
end
attr[:behaviour] = attr[:behavior]
attr.delete(:behavior)
end
attr[:behaviour] = attr[:behavior]
attr.delete(:behavior)
# store the validated definition
@definition = definition
end
# store the validated definition
@definition = definition
end

# validates a resource hash against its type schema
def check_schema(resource)
namevars.each do |namevar|
if resource[namevar].nil?
raise Puppet::ResourceError, "`#{name}.get` did not return a value for the `#{namevar}` namevar attribute"
# validates a resource hash against its type schema
def check_schema(resource)
namevars.each do |namevar|
if resource[namevar].nil?
raise Puppet::ResourceError, "`#{name}.get` did not return a value for the `#{namevar}` namevar attribute"
end
end
end

message = "Provider returned data that does not match the Type Schema for `#{name}[#{resource[namevars.first]}]`"
message = "Provider returned data that does not match the Type Schema for `#{name}[#{resource[namevars.first]}]`"

rejected_keys = check_schema_keys(resource) # removes bad keys
bad_values = check_schema_values(resource)
rejected_keys = check_schema_keys(resource) # removes bad keys
bad_values = check_schema_values(resource)

unless rejected_keys.empty?
message += "\n Unknown attribute:\n"
rejected_keys.each { |key, _value| message += " * #{key}\n" }
end
unless bad_values.empty?
message += "\n Value type mismatch:\n"
bad_values.each { |key, value| message += " * #{key}: #{value}\n" }
end
unless rejected_keys.empty?
message += "\n Unknown attribute:\n"
rejected_keys.each { |key, _value| message += " * #{key}\n" }
end
unless bad_values.empty?
message += "\n Value type mismatch:\n"
bad_values.each { |key, value| message += " * #{key}: #{value}\n" }
end

return if rejected_keys.empty? && bad_values.empty?
return if rejected_keys.empty? && bad_values.empty?

if Puppet.settings[:strict] == :off
Puppet.debug(message)
elsif Puppet.settings[:strict] == :warning
Puppet::ResourceApi.warning_count += 1
Puppet.warning(message) if Puppet::ResourceApi.warning_count <= 100 # maximum number of schema warnings to display in a run
elsif Puppet.settings[:strict] == :error
raise Puppet::DevError, message
if Puppet.settings[:strict] == :off
Puppet.debug(message)
elsif Puppet.settings[:strict] == :warning
Puppet::ResourceApi.warning_count += 1
Puppet.warning(message) if Puppet::ResourceApi.warning_count <= 100 # maximum number of schema warnings to display in a run
elsif Puppet.settings[:strict] == :error
raise Puppet::DevError, message
end
end
end

# Returns an array of keys that where not found in the type schema
# Modifies the resource passed in, leaving only valid attributes
def check_schema_keys(resource)
rejected = []
resource.reject! { |key| rejected << key if key != :title && attributes.key?(key) == false }
rejected
end
# Returns an array of keys that where not found in the type schema
# Modifies the resource passed in, leaving only valid attributes
def check_schema_keys(resource)
rejected = []
resource.reject! { |key| rejected << key if key != :title && attributes.key?(key) == false }
rejected
end

# Returns a hash of keys and values that are not valid
# does not modify the resource passed in
def check_schema_values(resource)
bad_vals = {}
resource.each do |key, value|
next unless attributes[key]
type = @data_type_cache[attributes[key][:type]]
error_message = Puppet::ResourceApi::DataTypeHandling.try_validate(
type,
value,
'',
)
bad_vals[key] = value unless error_message.nil?
# Returns a hash of keys and values that are not valid
# does not modify the resource passed in
def check_schema_values(resource)
bad_vals = {}
resource.each do |key, value|
next unless attributes[key]
type = @data_type_cache[attributes[key][:type]]
error_message = Puppet::ResourceApi::DataTypeHandling.try_validate(
type,
value,
'',
)
bad_vals[key] = value unless error_message.nil?
end
bad_vals
end
bad_vals
end
end
Loading