<commit_before>require 'cfpropertylist'
require 'securerandom'
require 'fileutils'

class Puppet::Provider::MobileConfig < Puppet::Provider
  
  confine :operatingsystem  => :darwin
  
  class << self
    
    # Returns an Array of a provider instances for every resource discovered
    def instances
      all = get_installed_profiles
      all.collect { |profile| new(get_resource_properties(profile)) }
    end
    
    # Puppet MAGIC
    def prefetch(resources)
      instances.each do |prov|
        if resource = resources[prov.name]
          resource.provider = prov
        end
      end
    end
    
    # Use the profiles command to return an array containing a Hash
    # representation of each of the profiles installed
    # Returns: Array
    def get_installed_profiles
      # Setup a tmp dir we can dump the installed profiles in
      dir  = Dir.mktmpdir
      path = [dir, "profiles#{SecureRandom.hex}.plist"].join("/")
      
      begin
        profiles(['-P', '-o', path])
      rescue Puppet::ExecutionFailure => e
        raise Puppet::Error, "#mobileconfig: command returned non-zero 
          `profiles -P -o #{path}`"
      end
      
      # Parse the plist and remove it
      parsed = parse_propertylist path
      FileUtils.rm_rf path
      
      # Return an empty array if there are no profiles installed
      return [] if parsed.empty?
      
      parsed['_computerlevel']
    end
    
    # Profile read from profile dump goes in, Puppet resource comes out
    def get_resource_properties(profile)
      
      # No profile, empty Hash
      return {} if profile.nil?
      
      # Extract the PayloadType and insert it into the :content Array
      content = profile['ProfileItems'].collect do |item|
        settings = item.delete('PayloadContent')
        item.merge settings
      end
      
      # Ladies and gentleman, the Puppet resource as a Hash
      {
        :name              => profile['ProfileIdentifier'],
        :description       => profile['ProfileDescription'],
        :displayname       => profile['ProfileDisplayName'],
        :organization      => profile['ProfileOrganization'],
        :removaldisallowed => profile['ProfileUninstallPolicy'] == 'allowed' ? 'false' : 'true', # Ridiculous
        :provider          => :mobileconfig,
        :ensure            => :present,
        :content           => content,
      }
    end
    
    # Parse a plist and return a Ruby object
    def parse_propertylist(file)
      plist = CFPropertyList::List.new(:file => file)
      raise Puppet::Error, "Cannot parse: #{file}" if plist.nil?
      CFPropertyList.native_types(plist.value)<commit_msg>- extricate content prep logic into its own method: #prepare_content - refactor so that it removes variable keys - rejects AD activation Flag keys<commit_after>require 'cfpropertylist'
require 'securerandom'
require 'fileutils'

class Puppet::Provider::MobileConfig < Puppet::Provider
  
  confine :operatingsystem  => :darwin
  
  class << self
    
    # Returns an Array of a provider instances for every resource discovered
    def instances
      all = get_installed_profiles
      all.collect { |profile| new(get_resource_properties(profile)) }
    end
    
    # Puppet MAGIC
    def prefetch(resources)
      instances.each do |prov|
        if resource = resources[prov.name]
          resource.provider = prov
        end
      end
    end
    
    # Use the profiles command to return an array containing a Hash
    # representation of each of the profiles installed
    # Returns: Array
    def get_installed_profiles
      # Setup a tmp dir we can dump the installed profiles in
      dir  = Dir.mktmpdir
      path = [dir, "profiles#{SecureRandom.hex}.plist"].join("/")
      
      begin
        profiles(['-P', '-o', path])
      rescue Puppet::ExecutionFailure => e
        raise Puppet::Error, "#mobileconfig: command returned non-zero 
          `profiles -P -o #{path}`"
      end
      
      # Parse the plist and remove it
      parsed = parse_propertylist path
      FileUtils.rm_rf path
      
      # Return an empty array if there are no profiles installed
      return [] if parsed.empty?
      
      parsed['_computerlevel']
    end
    
    # Profile read from profile dump goes in, Puppet resource comes out
    def get_resource_properties(profile)
      
      # No profile, empty Hash
      return {} if profile.nil?
      
      # Prepare the content array for insertion into the resource
      content = prepare_content(profile['ProfileItems'])
      
      # Ladies and gentleman, the Puppet resource as a Hash
      {
        :name              => profile['ProfileIdentifier'],
        :description       => profile['ProfileDescription'],
        :displayname       => profile['ProfileDisplayName'],
        :organization      => profile['ProfileOrganization'],
        :removaldisallowed => profile['ProfileUninstallPolicy'] == 'allowed' ? 'false' : 'true', # Ridiculous
        :provider          => :mobileconfig,
        :ensure            => :present,
        :content           => content,
      }
    end
    
    # Formats the PayloadContent data for use the in the resource
    def prepare_content(content)
      content.collect do |item|
        # Extract the PayloadContent
        settings = item.delete('PayloadContent')
        
        # We do not compare these keys
        variable_keys = ['PayloadDescription',
                         'PayloadDisplayName',
                         'PayloadOrganization',
                         'PayloadRemovalDisallowed',
                         'PayloadScope',
                         'PayloadUUID',
                         'PayloadVersion',]
        
        # Scrub the ignored keys
        variable_keys.each do |key|
          item.delete_if     { |k| k.eql?(key) }
          settings.delete_if { |k| k.eql?(key) }
        end
        
        # Reject AD Flag keys
        # We do this here so the `puppet resource mobileconfig ...` will return
        # the correct :content no matter which provider is used. This feels
        # cheap, but it is economical and may be required by other subclasses 
        # of the mobielconfig provider down the road.
        settings.reject! { |k| k =~ /\AAD.*Flag\z/ }
        
        item.merge settings
      end
    end
    
    # Parse a plist and return a Ruby object
    def parse_propertylist(file)
      plist = CFPropertyList::List.new(:file => file)
      raise Puppet::Error, "Cannot parse: #{file}" if plist.nil?
      CFPropertyList.native_types(plist.value)
{'fraction_non_alphanumeric': 0.08448774547115238, 'fraction_numerical': 0.0, 'mean_word_length': 2.1708494208494207, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3728413', 'n_tokens_mistral': 1735, 'n_tokens_neox': 1710, 'n_words': 555}