class AbideDataProcessor::Parser::ResourceDataParser

Parser class for resource Hiera data. rubocop:disable Metrics/ClassLength

Attributes

control_maps[R]
hiera_data[R]
resources[R]

Public Class Methods

new(hiera_data, control_maps, control_configs: {}, ignore: [], only: []) click to toggle source
# File lib/abide-data-processor/parser.rb, line 78
def initialize(hiera_data, control_maps, control_configs: {}, ignore: [], only: [])
  @hiera_data = validate_hiera_data(hiera_data)
  @control_maps = validate_control_maps(control_maps)
  @control_configs = control_configs
  @ignore = ignore
  @only = only
  @resources = RGL::DirectedAdjacencyGraph.new
  @controls = Set.new
  @filtered = Set.new
  @dependent = {}
end

Public Instance Methods

parse() click to toggle source

Parse the Hiera data into a Hash used by Puppet to create the resources. The way this works is by first creating a DAG and adding all resources to the graph as vertices, with an edge for each resource pointing from a dummy node, :root, to the resource. We then add edges to the graph based on the ‘before_me` and `after_me` lists of each resource and remove the :root-connected edges for each resource that has a `before_me` list, and remove the :root-connected edges for each resource in a `after_me` list. Finally, we sort the graph into an Array populated with a single Hash of ordered resources and return that Hash. @return [Array] A sorted array of resource hashes. rubocop:disable Metrics/MethodLength

# File lib/abide-data-processor/parser.rb, line 100
def parse
  @hiera_data.each do |name, data|
    resource = AbideDataProcessor::Parser.new_resource(name, data, @control_maps)
    add_control_names(resource)
    add_dependent_mapping(resource) # Map any controls this resource depends on
    @resources.add_vertex(resource) # Add all the resources to the graph
    @resources.add_edge(:root, resource) # Establish the root -> resource edges
    add_edge_ordering(resource) # Add resource ordering edges
  end
  # If the resource should be filtered (i.e. only or ignore), remove it from the graph.
  filter_resources!
  # Verify that all dependent resources are in the graph, remove them if not.
  remove_unsatisfied_dependents!
  # Sort the graph and return the array of ordered resource hashes
  sort_resources.map do |r|
    r.add_control_configs(@control_configs)
    resource_data(r)
  end
end

Private Instance Methods

add_after_me_edge(resource) click to toggle source

Adds edges to the graph based on the given Resource’s ‘after_me` list. @param resource [Resource] The Resource to add edges for.

# File lib/abide-data-processor/parser.rb, line 250
def add_after_me_edge(resource)
  resource.after_me.flatten.each do |after|
    next unless after # Skip if this `after` is nil, empty, or falsy (e.g. false, 0, etc.)
    next if after.equal?(resource) # Skip if this `after` is the same as the current resource

    # We remove the edge from root to the `after` resource if it exists because the `after` resource
    # is no longer attached to the root of the graph as this resources comes before it.
    @resources.remove_edge(:root, after) if @resources.has_edge?(:root, after)
    # Add the edge from this resource to the after resource
    @resources.add_edge(resource, after) unless @resources.has_edge?(resource, after)
  end
end
add_before_me_edge(resource) click to toggle source

Adds edges to the graph based on the given Resource’s ‘before_me` list. @param resource [Resource] The Resource to add edges for.

# File lib/abide-data-processor/parser.rb, line 235
def add_before_me_edge(resource)
  resource.before_me.flatten.each do |before|
    next unless before # Skip if this `before` is nil, empty, or falsy (e.g. false, 0, etc.)
    next if before.equal?(resource) # Skip if this `before` is the same as the current resource

    # We remove the edge from root to this resource if it exists because this resource is no longer
    # attached to the root of the graph as it has other resources before it.
    @resources.remove_edge(:root, resource) if @resources.has_edge?(:root, resource)
    # Add the edge from the before resource to this resource
    @resources.add_edge(before, resource) unless @resources.has_edge?(before, resource)
  end
end
add_control_names(resource) click to toggle source

Adds control neames for the given resource to the @controls set. @param resource [Resource] The resource to add control names for.

# File lib/abide-data-processor/parser.rb, line 125
def add_control_names(resource)
  return unless resource.controls

  @controls.merge(resource.control_names).flatten!
  @controls.merge(resource.mapped_control_names).flatten!
end
add_dependent_mapping(resource) click to toggle source

Adds a mapping for a dependent control and the resources that depend on it. @param resource [Resource] The resource to add the mapping for.

# File lib/abide-data-processor/parser.rb, line 177
def add_dependent_mapping(resource)
  return unless resource.dependent

  resource.dependent.each do |control_name|
    @dependent[control_name] = [] unless @dependent.key?(control_name)
    @dependent[control_name] << resource
  end
end
add_edge_ordering(resource) click to toggle source

Adds edges to the graph based on the given Resource’s ‘before_me` and `after_me` lists. @param resource [Resource] The Resource to add edges for.

# File lib/abide-data-processor/parser.rb, line 228
def add_edge_ordering(resource)
  add_before_me_edge(resource)
  add_after_me_edge(resource)
end
collect_verticies_by_control(control_name) click to toggle source

Gets all verticies in the graph that have the associated control @param control_name [String] The name of the control to check. @return [Array] The verticies that have the associated control.

# File lib/abide-data-processor/parser.rb, line 209
def collect_verticies_by_control(control_name)
  @resources.vertices.select { |r| r.control?(control_name) }
end
control_in?(resource, control_list) click to toggle source

Checks if the given Resource has a control in the given list. @param resource [Resource] The resource to check. @param control_list [Array] The list of controls to check against. @return [Boolean] True if the resource is in the control list, false otherwise.

# File lib/abide-data-processor/parser.rb, line 217
def control_in?(resource, control_list)
  return false if control_list.empty?

  control_list.each do |ignored_control|
    return true if resource.control?(ignored_control)
  end
  false
end
filter_resource?(resource) click to toggle source

Checks whether the resource should be filtered out based on the ignore and only lists. @param resource [Resource] The resource to check. @return [Boolean] True if the resource should be filtered out, false otherwise.

# File lib/abide-data-processor/parser.rb, line 168
def filter_resource?(resource)
  return true if control_in?(resource, @ignore)
  return true unless @only.empty? || control_in?(resource, @only)

  false
end
filter_resources!() click to toggle source

Removes Resources from the graph if they should be filtered.

# File lib/abide-data-processor/parser.rb, line 154
def filter_resources!
  @resources.depth_first_search do |resource|
    next if resource == :root

    if filter_resource?(resource)
      @resources.remove_vertex(resource) # Remove resource's graph vertex
      @filtered.add(resource) # Add resource to filtered set
    end
  end
end
remove_unsatisfied_dependents!() click to toggle source

Checks the dependent controls against all controls after filtered resource controls are removed and removes any dependent resources that are not satisfied. rubocop:disable Metrics/MethodLength, Metrics/AbcSize

# File lib/abide-data-processor/parser.rb, line 189
def remove_unsatisfied_dependents!
  dependent_set = Set.new(@dependent.keys)
  filtered_set = Set.new(@filtered.to_a.map(&:control_names)).flatten
  filtered_mapped = Set.new(@filtered.to_a.map(&:mapped_control_names)).flatten

  all_controls = @controls.subtract(filtered_set + filtered_mapped)
  return if dependent_set.proper_subset?(all_controls) # All dependent controls exist in the graph

  (dependent_set - all_controls).each do |control_name|
    @dependent[control_name].each do |resource|
      @resources.remove_vertex(resource)
      @filtered.add(resource)
    end
  end
end
resource_data(resource) click to toggle source

Calls the given Resource’s ‘resource_data` method, filters out any resource references in metaparameters that references filtered resources, and returns the result. @param resource [Resource] The resource to be filtered. @return [Hash] The filtered resource data. rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity

# File lib/abide-data-processor/parser.rb, line 137
def resource_data(resource)
  data = resource.resource_data.dup
  data.each do |_, res_data|
    res_data.each do |_, params|
      METAPARAMS.each do |param|
        next unless params.key?(param)

        params[param].reject! { |r| @filtered.to_a.map(&:resource_reference).include?(r) }
        params.delete(param) if params[param].empty?
      end
    end
  end
  data
end
sort_resources() click to toggle source

This method validates that the resources graph has no cycles and then returns a topological sort of the graph as an Array of Resource objects. @return [Array] The sorted Resources. @raise [ArgumentError] If the resources graph has any cycles.

# File lib/abide-data-processor/parser.rb, line 267
def sort_resources
  raise "Resource cyclic ordering detected: #{@resources.cycles}" unless @resources.acyclic?

  # We call topsort on the graph to get the sorted list of resources, convert it to an array, and
  # remove the root node.
  @resources.topsort_iterator.to_a.flatten.uniq.reject { |r| r == :root }
end